From f06f86226ba5a10a49016ec56e583abd8a9b59b2 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Sat, 28 Feb 2026 17:49:43 +0100 Subject: [PATCH 1/9] Git - file-system provider should open the repository if not already opened (#297783) * Git - file-system provider should open the repository if not already opened * Git - only open the repository in the empty/session window * Add logging --- extensions/git/src/fileSystemProvider.ts | 30 +++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/extensions/git/src/fileSystemProvider.ts b/extensions/git/src/fileSystemProvider.ts index 19928863832a5..a09d00bfc2268 100644 --- a/extensions/git/src/fileSystemProvider.ts +++ b/extensions/git/src/fileSystemProvider.ts @@ -131,6 +131,26 @@ export class GitFileSystemProvider implements FileSystemProvider { this.cache = cache; } + private async getOrOpenRepository(uri: string | Uri): Promise { + let repository = this.model.getRepository(uri); + if (repository) { + return repository; + } + + // In case of the empty window, or the agent sessions window, no repositories are open + // so we need to explicitly open a repository before we can serve git content for the + // given git resource. + if (workspace.workspaceFolders === undefined || workspace.isAgentSessionsWorkspace) { + const fsPath = typeof uri === 'string' ? uri : fromGitUri(uri).path; + this.logger.info(`[GitFileSystemProvider][getOrOpenRepository] Opening repository for ${fsPath}`); + + await this.model.openRepository(fsPath, true, true); + repository = this.model.getRepository(uri); + } + + return repository; + } + watch(): Disposable { return EmptyDisposable; } @@ -139,7 +159,11 @@ export class GitFileSystemProvider implements FileSystemProvider { await this.model.isInitialized; const { submoduleOf, path, ref } = fromGitUri(uri); - const repository = submoduleOf ? this.model.getRepository(submoduleOf) : this.model.getRepository(uri); + + const repository = submoduleOf + ? await this.getOrOpenRepository(submoduleOf) + : await this.getOrOpenRepository(uri); + if (!repository) { this.logger.warn(`[GitFileSystemProvider][stat] Repository not found - ${uri.toString()}`); throw FileSystemError.FileNotFound(); @@ -175,7 +199,7 @@ export class GitFileSystemProvider implements FileSystemProvider { const { path, ref, submoduleOf } = fromGitUri(uri); if (submoduleOf) { - const repository = this.model.getRepository(submoduleOf); + const repository = await this.getOrOpenRepository(submoduleOf); if (!repository) { throw FileSystemError.FileNotFound(); @@ -190,7 +214,7 @@ export class GitFileSystemProvider implements FileSystemProvider { } } - const repository = this.model.getRepository(uri); + const repository = await this.getOrOpenRepository(uri); if (!repository) { this.logger.warn(`[GitFileSystemProvider][readFile] Repository not found - ${uri.toString()}`); From aa70284f9cc359b672f4cf88831f167a2947cdd8 Mon Sep 17 00:00:00 2001 From: David Dossett <25163139+daviddossett@users.noreply.github.com> Date: Sat, 28 Feb 2026 09:30:37 -0800 Subject: [PATCH 2/9] Clean up model picker input styles (#298460) * Clean up model picker input styles: remove background, add filter spacing * Use CSS :has() instead of imperative class for filter container --- src/vs/workbench/contrib/chat/browser/widget/media/chat.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/widget/media/chat.css b/src/vs/workbench/contrib/chat/browser/widget/media/chat.css index 47b58886cdebd..c3ec962151a34 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/widget/media/chat.css @@ -1441,6 +1441,11 @@ have to be updated for changes to the rules above, or to support more deeply nes outline: none; box-shadow: none; border-color: transparent; + background-color: transparent; +} + +.action-widget .action-list-filter:has(.chat-model-picker-filter-input) { + margin-bottom: 4px; } .interactive-session .chat-input-toolbars .codicon-debug-stop { From c60fe511345a7fa465c48c1ceb4fa46595250a9b Mon Sep 17 00:00:00 2001 From: David Dossett <25163139+daviddossett@users.noreply.github.com> Date: Sat, 28 Feb 2026 09:46:16 -0800 Subject: [PATCH 3/9] Minor chat input refinements (#298462) * Chat input refinements: increase height and update placeholder text * Extract shared constants for input editor line height and padding --- .../experiments/agentTitleBarStatusWidget.ts | 2 +- .../contrib/chat/browser/widget/input/chatInputPart.ts | 9 ++++++--- src/vs/workbench/contrib/chat/common/chatModes.ts | 2 +- .../promptSyntax/languageProviders/promptHovers.test.ts | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/experiments/agentTitleBarStatusWidget.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/experiments/agentTitleBarStatusWidget.ts index 9b9ea511fdf93..86ec453b950af 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/experiments/agentTitleBarStatusWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/experiments/agentTitleBarStatusWidget.ts @@ -432,7 +432,7 @@ export class AgentTitleBarStatusWidget extends BaseActionViewItem { label.classList.add('has-progress'); } - const hoverLabel = localize('askAnythingPlaceholder', "Ask anything or describe what to build next"); + const hoverLabel = localize('askAnythingPlaceholder', "Ask anything or describe what to build"); label.textContent = defaultLabel; pill.appendChild(label); diff --git a/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts index 44ad592b87206..dd3d09b6a248f 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts @@ -131,6 +131,8 @@ import { EnhancedModelPickerActionItem } from './modelPickerActionItem2.js'; const $ = dom.$; const INPUT_EDITOR_MAX_HEIGHT = 250; +const INPUT_EDITOR_LINE_HEIGHT = 20; +const INPUT_EDITOR_PADDING = { compact: { top: 2, bottom: 2 }, default: { top: 12, bottom: 12 } }; const CachedLanguageModelsKey = 'chat.cachedLanguageModels.v2'; export interface IChatInputStyles { @@ -562,7 +564,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.dnd = this._register(this.instantiationService.createInstance(ChatDragAndDrop, () => this._widget, this._attachmentModel, styles)); this.inputEditorMaxHeight = this.options.renderStyle === 'compact' ? INPUT_EDITOR_MAX_HEIGHT / 3 : INPUT_EDITOR_MAX_HEIGHT; - this.inputEditorMinHeight = this.options.inputEditorMinLines ? this.options.inputEditorMinLines * 20 + (this.options.renderStyle === 'compact' ? 4 : 16) : undefined; // lineHeight is 20, padding is top+bottom + const padding = this.options.renderStyle === 'compact' ? INPUT_EDITOR_PADDING.compact : INPUT_EDITOR_PADDING.default; + this.inputEditorMinHeight = this.options.inputEditorMinLines ? this.options.inputEditorMinLines * INPUT_EDITOR_LINE_HEIGHT + padding.top + padding.bottom : undefined; this.inputEditorHasText = ChatContextKeys.inputHasText.bindTo(contextKeyService); this.chatCursorAtTop = ChatContextKeys.inputCursorAtTop.bindTo(contextKeyService); @@ -2053,8 +2056,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge options.ariaLabel = this._getAriaLabel(); options.fontFamily = DEFAULT_FONT_FAMILY; options.fontSize = 13; - options.lineHeight = 20; - options.padding = this.options.renderStyle === 'compact' ? { top: 2, bottom: 2 } : { top: 8, bottom: 8 }; + options.lineHeight = INPUT_EDITOR_LINE_HEIGHT; + options.padding = this.options.renderStyle === 'compact' ? INPUT_EDITOR_PADDING.compact : INPUT_EDITOR_PADDING.default; options.cursorWidth = 1; options.wrappingStrategy = 'advanced'; options.bracketPairColorization = { enabled: false }; diff --git a/src/vs/workbench/contrib/chat/common/chatModes.ts b/src/vs/workbench/contrib/chat/common/chatModes.ts index 25a9ef1bb2769..f6501831c4cd6 100644 --- a/src/vs/workbench/contrib/chat/common/chatModes.ts +++ b/src/vs/workbench/contrib/chat/common/chatModes.ts @@ -538,7 +538,7 @@ export class BuiltinChatMode implements IChatMode { export namespace ChatMode { export const Ask = new BuiltinChatMode(ChatModeKind.Ask, 'Ask', localize('chatDescription', "Explore and understand your code"), Codicon.question); export const Edit = new BuiltinChatMode(ChatModeKind.Edit, 'Edit', localize('editsDescription', "Edit or refactor selected code"), Codicon.edit); - export const Agent = new BuiltinChatMode(ChatModeKind.Agent, 'Agent', localize('agentDescription', "Describe what to build next"), Codicon.agent); + export const Agent = new BuiltinChatMode(ChatModeKind.Agent, 'Agent', localize('agentDescription', "Describe what to build"), Codicon.agent); } export function isBuiltinChatMode(mode: IChatMode): boolean { diff --git a/src/vs/workbench/contrib/chat/test/browser/promptSyntax/languageProviders/promptHovers.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptSyntax/languageProviders/promptHovers.test.ts index f8042fe069e53..6f2a25d8ce3f6 100644 --- a/src/vs/workbench/contrib/chat/test/browser/promptSyntax/languageProviders/promptHovers.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/promptSyntax/languageProviders/promptHovers.test.ts @@ -405,7 +405,7 @@ suite('PromptHoverProvider', () => { 'The agent to use when running this prompt.', '', '**Built-in agents:**', - '- `agent`: Describe what to build next', + '- `agent`: Describe what to build', '- `ask`: Explore and understand your code', '- `edit`: Edit or refactor selected code', '', From a837f16fbe459f3a067aa94da0ddb9b9ae04ebe0 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Sat, 28 Feb 2026 19:14:13 +0100 Subject: [PATCH 4/9] Report Event.Buffer leaks when running from source (#298468) * Report Event.Buffer leaks when running from source * Address feedback --- src/vs/base/common/event.ts | 47 ++++++++++++++++++- src/vs/base/parts/ipc/common/ipc.ts | 4 +- src/vs/base/test/common/event.test.ts | 6 +-- .../common/extensionManagementIpc.ts | 10 ++-- .../platform/mcp/common/mcpManagementIpc.ts | 10 ++-- .../api/common/extHostExtensionService.ts | 2 +- .../common/embedderTerminalService.ts | 2 +- 7 files changed, 62 insertions(+), 19 deletions(-) diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index de0fce1d4fdd0..09d0ed7c530c0 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -11,6 +11,7 @@ import { createSingleCallFunction } from './functional.js'; import { combinedDisposable, Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from './lifecycle.js'; import { LinkedList } from './linkedList.js'; import { IObservable, IObservableWithChange, IObserver } from './observable.js'; +import { env } from './process.js'; import { StopWatch } from './stopwatch.js'; import { MicrotaskDelay } from './symbols.js'; @@ -31,6 +32,14 @@ const _enableSnapshotPotentialLeakWarning = false // || Boolean("TRUE") // causes a linter warning so that it cannot be pushed ; + +const _bufferLeakWarnCountThreshold = 100; +const _bufferLeakWarnTimeThreshold = 60_000; // 1 minute + +function _isBufferLeakWarningEnabled(): boolean { + return !!env['VSCODE_DEV']; +} + /** * An event with zero or one parameters that can be subscribed to. The event is a function itself. */ @@ -490,6 +499,7 @@ export namespace Event { * returned event causes this utility to leak a listener on the original event. * * @param event The event source for the new event. + * @param debugName A name for this buffer, used in leak detection warnings. * @param flushAfterTimeout Determines whether to flush the buffer after a timeout immediately or after a * `setTimeout` when the first event listener is added. * @param _buffer Internal: A source event array used for tests. @@ -499,15 +509,46 @@ export namespace Event { * // Start accumulating events, when the first listener is attached, flush * // the event after a timeout such that multiple listeners attached before * // the timeout would receive the event - * this.onInstallExtension = Event.buffer(service.onInstallExtension, true); + * this.onInstallExtension = Event.buffer(service.onInstallExtension, 'onInstallExtension', true); * ``` */ - export function buffer(event: Event, flushAfterTimeout = false, _buffer: T[] = [], disposable?: DisposableStore): Event { + export function buffer(event: Event, debugName: string, flushAfterTimeout = false, _buffer: T[] = [], disposable?: DisposableStore): Event { let buffer: T[] | null = _buffer.slice(); + // Dev-only leak detection: track when buffer was created and warn + // if events accumulate without ever being consumed. + let bufferLeakWarningData: { stack: Stacktrace; timerId: ReturnType; warned: boolean } | undefined; + if (_isBufferLeakWarningEnabled()) { + bufferLeakWarningData = { + stack: Stacktrace.create(), + timerId: setTimeout(() => { + if (buffer && buffer.length > 0 && bufferLeakWarningData && !bufferLeakWarningData.warned) { + bufferLeakWarningData.warned = true; + console.warn(`[Event.buffer][${debugName}] potential LEAK detected: ${buffer.length} events buffered for ${_bufferLeakWarnTimeThreshold / 1000}s without being consumed. Buffered here:`); + bufferLeakWarningData.stack.print(); + } + }, _bufferLeakWarnTimeThreshold), + warned: false + }; + if (disposable) { + disposable.add(toDisposable(() => clearTimeout(bufferLeakWarningData!.timerId))); + } + } + + const clearLeakWarningTimer = () => { + if (bufferLeakWarningData) { + clearTimeout(bufferLeakWarningData.timerId); + } + }; + let listener: IDisposable | null = event(e => { if (buffer) { buffer.push(e); + if (_isBufferLeakWarningEnabled() && bufferLeakWarningData && !bufferLeakWarningData.warned && buffer.length >= _bufferLeakWarnCountThreshold) { + bufferLeakWarningData.warned = true; + console.warn(`[Event.buffer][${debugName}] potential LEAK detected: ${buffer.length} events buffered without being consumed. Buffered here:`); + bufferLeakWarningData.stack.print(); + } } else { emitter.fire(e); } @@ -520,6 +561,7 @@ export namespace Event { const flush = () => { buffer?.forEach(e => emitter.fire(e)); buffer = null; + clearLeakWarningTimer(); }; const emitter = new Emitter({ @@ -547,6 +589,7 @@ export namespace Event { listener.dispose(); } listener = null; + clearLeakWarningTimer(); } }); diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index 8e061681e5c51..9d62cc8d5fae7 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -1118,7 +1118,7 @@ export namespace ProxyChannel { const mapEventNameToEvent = new Map>(); for (const key in handler) { if (propertyIsEvent(key)) { - mapEventNameToEvent.set(key, Event.buffer(handler[key] as Event, true, undefined, disposables)); + mapEventNameToEvent.set(key, Event.buffer(handler[key] as Event, key, true, undefined, disposables)); } } @@ -1137,7 +1137,7 @@ export namespace ProxyChannel { } if (propertyIsEvent(event)) { - mapEventNameToEvent.set(event, Event.buffer(handler[event] as Event, true, undefined, disposables)); + mapEventNameToEvent.set(event, Event.buffer(handler[event] as Event, event, true, undefined, disposables)); return mapEventNameToEvent.get(event) as Event; } diff --git a/src/vs/base/test/common/event.test.ts b/src/vs/base/test/common/event.test.ts index 4f0369b28b979..cf3c252b90902 100644 --- a/src/vs/base/test/common/event.test.ts +++ b/src/vs/base/test/common/event.test.ts @@ -1006,7 +1006,7 @@ suite('Event utils', () => { const result: number[] = []; const emitter = ds.add(new Emitter()); const event = emitter.event; - const bufferedEvent = Event.buffer(event); + const bufferedEvent = Event.buffer(event, 'test'); emitter.fire(1); emitter.fire(2); @@ -1028,7 +1028,7 @@ suite('Event utils', () => { const result: number[] = []; const emitter = ds.add(new Emitter()); const event = emitter.event; - const bufferedEvent = Event.buffer(event, true); + const bufferedEvent = Event.buffer(event, 'test', true); emitter.fire(1); emitter.fire(2); @@ -1050,7 +1050,7 @@ suite('Event utils', () => { const result: number[] = []; const emitter = ds.add(new Emitter()); const event = emitter.event; - const bufferedEvent = Event.buffer(event, false, [-2, -1, 0]); + const bufferedEvent = Event.buffer(event, 'test', false, [-2, -1, 0]); emitter.fire(1); emitter.fire(2); diff --git a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts index 47f9736ff5af5..63a60f3a3d9ca 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts @@ -54,11 +54,11 @@ export class ExtensionManagementChannel; constructor(private service: IExtensionManagementService, private getUriTransformer: (requestContext: TContext) => IURITransformer | null) { - this.onInstallExtension = Event.buffer(service.onInstallExtension, true); - this.onDidInstallExtensions = Event.buffer(service.onDidInstallExtensions, true); - this.onUninstallExtension = Event.buffer(service.onUninstallExtension, true); - this.onDidUninstallExtension = Event.buffer(service.onDidUninstallExtension, true); - this.onDidUpdateExtensionMetadata = Event.buffer(service.onDidUpdateExtensionMetadata, true); + this.onInstallExtension = Event.buffer(service.onInstallExtension, 'onInstallExtension', true); + this.onDidInstallExtensions = Event.buffer(service.onDidInstallExtensions, 'onDidInstallExtensions', true); + this.onUninstallExtension = Event.buffer(service.onUninstallExtension, 'onUninstallExtension', true); + this.onDidUninstallExtension = Event.buffer(service.onDidUninstallExtension, 'onDidUninstallExtension', true); + this.onDidUpdateExtensionMetadata = Event.buffer(service.onDidUpdateExtensionMetadata, 'onDidUpdateExtensionMetadata', true); } // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/src/vs/platform/mcp/common/mcpManagementIpc.ts b/src/vs/platform/mcp/common/mcpManagementIpc.ts index 733319fd2161a..570ede9d027df 100644 --- a/src/vs/platform/mcp/common/mcpManagementIpc.ts +++ b/src/vs/platform/mcp/common/mcpManagementIpc.ts @@ -46,11 +46,11 @@ export class McpManagementChannel; 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); - this.onUninstallMcpServer = Event.buffer(service.onUninstallMcpServer, true); - this.onDidUninstallMcpServer = Event.buffer(service.onDidUninstallMcpServer, true); + this.onInstallMcpServer = Event.buffer(service.onInstallMcpServer, 'onInstallMcpServer', true); + this.onDidInstallMcpServers = Event.buffer(service.onDidInstallMcpServers, 'onDidInstallMcpServers', true); + this.onDidUpdateMcpServers = Event.buffer(service.onDidUpdateMcpServers, 'onDidUpdateMcpServers', true); + this.onUninstallMcpServer = Event.buffer(service.onUninstallMcpServer, 'onUninstallMcpServer', true); + this.onDidUninstallMcpServer = Event.buffer(service.onDidUninstallMcpServer, 'onDidUninstallMcpServer', true); } listen(context: TContext, event: string): Event { diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index a0178f266d4a8..36cec4d29bc69 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -557,7 +557,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme return undefined; } - const onDidReceiveMessage = Event.buffer(Event.fromDOMEventEmitter(messagePort, 'message', e => e.data)); + const onDidReceiveMessage = Event.buffer(Event.fromDOMEventEmitter(messagePort, 'message', e => e.data), 'onDidReceiveMessage'); messagePort.start(); messagePassingProtocol = { onDidReceiveMessage, diff --git a/src/vs/workbench/services/terminal/common/embedderTerminalService.ts b/src/vs/workbench/services/terminal/common/embedderTerminalService.ts index 0d0bd4f349d8b..3665fe2bbe666 100644 --- a/src/vs/workbench/services/terminal/common/embedderTerminalService.ts +++ b/src/vs/workbench/services/terminal/common/embedderTerminalService.ts @@ -56,7 +56,7 @@ class EmbedderTerminalService implements IEmbedderTerminalService { declare _serviceBrand: undefined; private readonly _onDidCreateTerminal = new Emitter(); - readonly onDidCreateTerminal = Event.buffer(this._onDidCreateTerminal.event); + readonly onDidCreateTerminal = Event.buffer(this._onDidCreateTerminal.event, 'onDidCreateTerminal'); createTerminal(options: IEmbedderTerminalOptions): void { const slc: EmbedderTerminal = { From 39b8df7f24d78456fc12a5884db843efbe1e0124 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Sat, 28 Feb 2026 10:36:15 -0800 Subject: [PATCH 5/9] fix jump in terminal headers (#298473) --- .../media/chatThinkingContent.css | 63 ++++++++++--------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/chatThinkingContent.css b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/chatThinkingContent.css index f6732a06bb70e..df4551eaa3531 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/chatThinkingContent.css +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/chatThinkingContent.css @@ -111,45 +111,45 @@ .chat-thinking-tool-wrapper { .chat-used-context { - margin-bottom: 0px; - margin-left: 4px; - padding-left: 2px; - } - - .chat-used-context { - .monaco-button.monaco-text-button { - color: var(--vscode-descriptionForeground); + margin-bottom: 0px; + margin-left: 4px; + padding-left: 2px; - :hover { - color: var(--vscode-foreground); - } + .monaco-button.monaco-text-button { + color: var(--vscode-descriptionForeground); + display: flex; + align-items: center; - .monaco-button:hover .chat-collapsible-hover-chevron { - opacity: 1; - } + :hover { + color: var(--vscode-foreground); } - } - .chat-used-context:not(.chat-used-context-collapsed) { - .monaco-button.monaco-text-button { - color: var(--vscode-foreground); + .monaco-button:hover .chat-collapsible-hover-chevron { + opacity: 1; } } + } - .progress-container, - .chat-confirmation-widget-container { - margin: 0 0 2px 6px; + .chat-used-context:not(.chat-used-context-collapsed) { + .monaco-button.monaco-text-button { + color: var(--vscode-foreground); } + } - /* todo: ideally not !important, but the competing css has 14 specificity */ - .codicon.codicon-check, - .codicon.codicon-loading { - display: none !important; - } + .progress-container, + .chat-confirmation-widget-container { + margin: 0 0 2px 6px; + } - .chat-collapsible-hover-chevron.codicon { - display: inline-flex; - } + /* todo: ideally not !important, but the competing css has 14 specificity */ + .codicon.codicon-check, + .codicon.codicon-loading { + display: none !important; + } + + .chat-collapsible-hover-chevron.codicon { + display: inline-flex; + } .show-checkmarks .chat-confirmation-widget-title > .codicon:first-child:not(.chat-collapsible-hover-chevron) { display: inline-block; @@ -298,6 +298,11 @@ flex-direction: column; width: 100%; + .monaco-button.monaco-text-button { + display: flex; + align-items: center; + } + .chat-used-context-list.chat-terminal-thinking-content { border: none; padding: 0; From a6967f74525156183d16edacc539222000389f48 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Sat, 28 Feb 2026 19:38:24 +0100 Subject: [PATCH 6/9] Updating the edit context on content change not on lines inserted, deleted and changed (#298272) * updating the edit context on content change not on lines inserted, deleted and changed * saving the previous edit context text --- .../editContext/native/nativeEditContext.ts | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts index 88d97714e7ce2..528a8e0f590fc 100644 --- a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts +++ b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts @@ -58,6 +58,7 @@ export class NativeEditContext extends AbstractEditContext { private readonly _editContext: EditContext; private readonly _screenReaderSupport: ScreenReaderSupport; private _previousEditContextSelection: OffsetRange = new OffsetRange(0, 0); + private _previousEditContextText: string = ''; private _editContextPrimarySelection: Selection = new Selection(1, 1, 1, 1); // Overflow guard container @@ -247,6 +248,19 @@ export class NativeEditContext extends AbstractEditContext { } })); this._register(NativeEditContextRegistry.register(ownerID, this)); + this._register(context.viewModel.model.onDidChangeContent((e) => { + let doChange = false; + for (const change of e.changes) { + if (change.range.startLineNumber <= this._editContextPrimarySelection.endLineNumber + && change.range.endLineNumber >= this._editContextPrimarySelection.startLineNumber) { + doChange = true; + break; + } + } + if (doChange) { + this._updateEditContext(); + } + })); } // --- Public methods --- @@ -310,27 +324,17 @@ export class NativeEditContext extends AbstractEditContext { } public override onLinesChanged(e: ViewLinesChangedEvent): boolean { - this._updateEditContextOnLineChange(e.fromLineNumber, e.fromLineNumber + e.count - 1); return true; } public override onLinesDeleted(e: ViewLinesDeletedEvent): boolean { - this._updateEditContextOnLineChange(e.fromLineNumber, e.toLineNumber); return true; } public override onLinesInserted(e: ViewLinesInsertedEvent): boolean { - this._updateEditContextOnLineChange(e.fromLineNumber, e.toLineNumber); return true; } - private _updateEditContextOnLineChange(fromLineNumber: number, toLineNumber: number): void { - if (this._editContextPrimarySelection.endLineNumber < fromLineNumber || this._editContextPrimarySelection.startLineNumber > toLineNumber) { - return; - } - this._updateEditContext(); - } - public override onScrollChanged(e: ViewScrollChangedEvent): boolean { this._scrollLeft = e.scrollLeft; this._scrollTop = e.scrollTop; @@ -412,8 +416,15 @@ export class NativeEditContext extends AbstractEditContext { if (!editContextState) { return; } - this._editContext.updateText(0, Number.MAX_SAFE_INTEGER, editContextState.text ?? ' '); - this._editContext.updateSelection(editContextState.selectionStartOffset, editContextState.selectionEndOffset); + const newText = editContextState.text ?? ' '; + if (newText !== this._previousEditContextText) { + this._editContext.updateText(0, this._previousEditContextText.length, newText); + this._previousEditContextText = newText; + } + if (editContextState.selectionStartOffset !== this._previousEditContextSelection.start || + editContextState.selectionEndOffset !== this._previousEditContextSelection.endExclusive) { + this._editContext.updateSelection(editContextState.selectionStartOffset, editContextState.selectionEndOffset); + } this._editContextPrimarySelection = editContextState.editContextPrimarySelection; this._previousEditContextSelection = new OffsetRange(editContextState.selectionStartOffset, editContextState.selectionEndOffset); } From c7d2d62aa8754e3ef153ac797729104c6a7894ff Mon Sep 17 00:00:00 2001 From: Neruthes 0x5200DF38 Date: Sat, 28 Feb 2026 20:09:32 +0000 Subject: [PATCH 7/9] fix editor punctuation width (#297741) Inserting `text-spacing-trim: space-all;` for `.monaco-editor` --- src/vs/editor/browser/widget/codeEditor/editor.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/editor/browser/widget/codeEditor/editor.css b/src/vs/editor/browser/widget/codeEditor/editor.css index d33122122dedf..638055a055d57 100644 --- a/src/vs/editor/browser/widget/codeEditor/editor.css +++ b/src/vs/editor/browser/widget/codeEditor/editor.css @@ -21,6 +21,7 @@ position: relative; overflow: visible; -webkit-text-size-adjust: 100%; + text-spacing-trim: space-all; color: var(--vscode-editor-foreground); background-color: var(--vscode-editor-background); overflow-wrap: initial; From c3e06383df1a12da82c45ba454dc77b999cf5214 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Sat, 28 Feb 2026 21:18:17 +0100 Subject: [PATCH 8/9] Add editor.hover.showLongLineWarning setting to suppress long line hovers (#298484) Add a new setting `editor.hover.showLongLineWarning` (default: true) that controls whether the "Tokenization is skipped..." and "Rendering paused..." hover messages are shown on long lines. When these hovers are shown, they now include a "Don'\''t Show Again" link that sets the setting to false, letting users dismiss them permanently while keeping all other hover functionality intact. Fixes #172713 --- src/vs/editor/common/config/editorOptions.ts | 12 ++++++++ .../contrib/hover/browser/hoverActionIds.ts | 1 + .../hover/browser/hoverContribution.ts | 6 ++++ .../hover/browser/markdownHoverParticipant.ts | 29 ++++++++++++++----- src/vs/monaco.d.ts | 5 ++++ 5 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 34cdd09c350c4..bf7964361673f 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -2322,6 +2322,11 @@ export interface IEditorHoverOptions { * Defaults to false. */ above?: boolean; + /** + * Should long line warning hovers be shown (tokenization skipped, rendering paused)? + * Defaults to true. + */ + showLongLineWarning?: boolean; } /** @@ -2338,6 +2343,7 @@ class EditorHover extends BaseEditorOption { + accessor.get(IConfigurationService).updateValue('editor.hover.showLongLineWarning', false); +}); // theming registerThemingParticipant((theme, collector) => { diff --git a/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts b/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts index ba261eaa4a44a..9cd72e14497e4 100644 --- a/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts +++ b/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts @@ -9,7 +9,7 @@ import { CancellationToken, CancellationTokenSource } from '../../../../base/com import { IMarkdownString, isEmptyMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js'; import { DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js'; import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js'; -import { DECREASE_HOVER_VERBOSITY_ACTION_ID, INCREASE_HOVER_VERBOSITY_ACTION_ID } from './hoverActionIds.js'; +import { DECREASE_HOVER_VERBOSITY_ACTION_ID, INCREASE_HOVER_VERBOSITY_ACTION_ID, HIDE_LONG_LINE_WARNING_HOVER_ACTION_ID } from './hoverActionIds.js'; import { ICodeEditor } from '../../../browser/editorBrowser.js'; import { Position } from '../../../common/core/position.js'; import { Range } from '../../../common/core/range.js'; @@ -115,17 +115,32 @@ export class MarkdownHoverParticipant implements IEditorHoverParticipant('editor.maxTokenizationLineLength', { overrideIdentifier: languageId }); + const showLongLineWarning = this._editor.getOption(EditorOption.hover).showLongLineWarning; let stopRenderingMessage = false; if (stopRenderingLineAfter >= 0 && lineLength > stopRenderingLineAfter && anchor.range.startColumn >= stopRenderingLineAfter) { stopRenderingMessage = true; - result.push(new MarkdownHover(this, anchor.range, [{ - value: nls.localize('stopped rendering', "Rendering paused for long line for performance reasons. This can be configured via `editor.stopRenderingLineAfter`.") - }], false, index++)); + if (showLongLineWarning) { + result.push(new MarkdownHover(this, anchor.range, [{ + value: nls.localize( + { key: 'stopped rendering', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change', '{Locked="](command:{0})"}'] }, + "Rendering paused for long line for performance reasons. This can be configured via `editor.stopRenderingLineAfter`. [Don't Show Again](command:{0})", + HIDE_LONG_LINE_WARNING_HOVER_ACTION_ID + ), + isTrusted: true + }], false, index++)); + } } if (!stopRenderingMessage && typeof maxTokenizationLineLength === 'number' && lineLength >= maxTokenizationLineLength) { - result.push(new MarkdownHover(this, anchor.range, [{ - value: nls.localize('too many characters', "Tokenization is skipped for long lines for performance reasons. This can be configured via `editor.maxTokenizationLineLength`.") - }], false, index++)); + if (showLongLineWarning) { + result.push(new MarkdownHover(this, anchor.range, [{ + value: nls.localize( + { key: 'too many characters', comment: ['Please do not translate the word "command", it is part of our internal syntax which must not change', '{Locked="](command:{0})"}'] }, + "Tokenization is skipped for long lines for performance reasons. This can be configured via `editor.maxTokenizationLineLength`. [Don't Show Again](command:{0})", + HIDE_LONG_LINE_WARNING_HOVER_ACTION_ID + ), + isTrusted: true + }], false, index++)); + } } let isBeforeContent = false; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index ce6731adeb770..fc9c2da70f5d2 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4307,6 +4307,11 @@ declare namespace monaco.editor { * Defaults to false. */ above?: boolean; + /** + * Should long line warning hovers be shown (tokenization skipped, rendering paused)? + * Defaults to true. + */ + showLongLineWarning?: boolean; } /** From ff740b1cefad130c3477aa75a1e0ec744d516ac1 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sat, 28 Feb 2026 13:28:32 -0800 Subject: [PATCH 9/9] Plumb 'isBuiltin' for chat modes into request (#298486) * Plumb 'isBuiltin' for chat modes into request https://github.com/microsoft/vscode-internalbacklog/issues/6884 * Fix test --- .../api/common/extHostTypeConverters.ts | 3 ++- .../chat/browser/widget/input/chatInputPart.ts | 1 + .../contrib/chat/common/model/chatModel.ts | 1 + .../promptSyntax/utils/promptsServiceUtils.ts | 17 +++++++++++++++++ .../tools/builtinTools/runSubagentTool.ts | 4 ++++ .../tools/builtinTools/runSubagentTool.test.ts | 5 +++++ ...scode.proposed.chatParticipantAdditions.d.ts | 4 ++++ 7 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 476ccdf8443db..841a35e48d293 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -3609,7 +3609,8 @@ export namespace ChatRequestModeInstructions { name: mode.name, content: mode.content, toolReferences: ChatLanguageModelToolReferences.to(mode.toolReferences), - metadata: mode.metadata + metadata: mode.metadata, + isBuiltin: mode.isBuiltin, }; } return undefined; diff --git a/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts index dd3d09b6a248f..d2e482f504a0f 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/input/chatInputPart.ts @@ -420,6 +420,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge content: modeInstructions.content, toolReferences: this.toolService.toToolReferences(modeInstructions.toolReferences), metadata: modeInstructions.metadata, + isBuiltin: mode.isBuiltin } : undefined, modeId: modeId, applyCodeBlockSuggestionId: undefined, diff --git a/src/vs/workbench/contrib/chat/common/model/chatModel.ts b/src/vs/workbench/contrib/chat/common/model/chatModel.ts index a85a969c0b2f0..6b322748eed0d 100644 --- a/src/vs/workbench/contrib/chat/common/model/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/model/chatModel.ts @@ -321,6 +321,7 @@ export interface IChatRequestModeInstructions { readonly content: string; readonly toolReferences: readonly ChatRequestToolReferenceEntry[]; readonly metadata?: Record; + readonly isBuiltin?: boolean; } export interface IChatRequestModelParameters { diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptsServiceUtils.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptsServiceUtils.ts index f458870f629cb..1e252f7aa8f87 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptsServiceUtils.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptsServiceUtils.ts @@ -6,6 +6,7 @@ import { URI } from '../../../../../../base/common/uri.js'; import { ExtensionIdentifier } from '../../../../../../platform/extensions/common/extensions.js'; import { IProductService } from '../../../../../../platform/product/common/productService.js'; +import { IAgentSource, PromptsStorage } from '../service/promptsService.js'; /** * Checks if a prompt file is organization-provided. @@ -26,3 +27,19 @@ export function isOrganizationPromptFile(uri: URI, extensionId: ExtensionIdentif const pathContainsGithub = uri.path.includes('/github/'); return isFromBuiltinChatExtension && pathContainsGithub; } + +/** + * Checks if a custom agent is considered "builtin" - i.e. shipped by the + * built-in chat extension and not organization-provided. Used for telemetry + * to decide whether the agent name is safe to send as-is. + */ +export function isBuiltinAgent(source: IAgentSource, uri: URI, productService: IProductService): boolean { + if (source.storage !== PromptsStorage.extension) { + return false; + } + const chatExtensionId = productService.defaultChatAgent?.chatExtensionId; + if (!chatExtensionId || !ExtensionIdentifier.equals(source.extensionId, chatExtensionId)) { + return false; + } + return !isOrganizationPromptFile(uri, source.extensionId, productService); +} diff --git a/src/vs/workbench/contrib/chat/common/tools/builtinTools/runSubagentTool.ts b/src/vs/workbench/contrib/chat/common/tools/builtinTools/runSubagentTool.ts index bf441cf3d5e49..5db3195bbdbcc 100644 --- a/src/vs/workbench/contrib/chat/common/tools/builtinTools/runSubagentTool.ts +++ b/src/vs/workbench/contrib/chat/common/tools/builtinTools/runSubagentTool.ts @@ -15,6 +15,7 @@ import { localize } from '../../../../../../nls.js'; import { IConfigurationChangeEvent, IConfigurationService } from '../../../../../../platform/configuration/common/configuration.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 { ChatRequestVariableSet } from '../../attachments/chatVariableEntries.js'; import { IChatProgress, IChatService } from '../../chatService/chatService.js'; import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../constants.js'; @@ -24,6 +25,7 @@ import { IChatAgentRequest, IChatAgentService } from '../../participants/chatAge import { ComputeAutomaticInstructions } from '../../promptSyntax/computeAutomaticInstructions.js'; import { IChatRequestHooks } from '../../promptSyntax/hookSchema.js'; import { ICustomAgent, IPromptsService } from '../../promptSyntax/service/promptsService.js'; +import { isBuiltinAgent } from '../../promptSyntax/utils/promptsServiceUtils.js'; import { CountTokensCallback, ILanguageModelToolsService, @@ -74,6 +76,7 @@ export class RunSubagentTool extends Disposable implements IToolImpl { @IConfigurationService private readonly configurationService: IConfigurationService, @IPromptsService private readonly promptsService: IPromptsService, @IInstantiationService private readonly instantiationService: IInstantiationService, + @IProductService private readonly productService: IProductService, ) { super(); this.onDidUpdateToolData = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ChatConfiguration.SubagentToolCustomAgents)); @@ -186,6 +189,7 @@ export class RunSubagentTool extends Disposable implements IToolImpl { content: instructions.content, toolReferences: this.toolsService.toToolReferences(instructions.toolReferences), metadata: instructions.metadata, + isBuiltin: isBuiltinAgent(subagent.source, subagent.uri, this.productService), }; } else { throw new Error(`Requested agent '${subAgentName}' not found. Try again with the correct agent name, or omit the agentName to use the current agent.`); diff --git a/src/vs/workbench/contrib/chat/test/common/tools/builtinTools/runSubagentTool.test.ts b/src/vs/workbench/contrib/chat/test/common/tools/builtinTools/runSubagentTool.test.ts index df306a3ef3ad0..2efa4f1af1637 100644 --- a/src/vs/workbench/contrib/chat/test/common/tools/builtinTools/runSubagentTool.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/tools/builtinTools/runSubagentTool.test.ts @@ -15,6 +15,7 @@ import { IChatAgentService } from '../../../../common/participants/chatAgents.js import { IChatService } from '../../../../common/chatService/chatService.js'; import { ILanguageModelChatMetadata, ILanguageModelChatMetadataAndIdentifier, ILanguageModelsService } from '../../../../common/languageModels.js'; import { IInstantiationService } from '../../../../../../../platform/instantiation/common/instantiation.js'; +import { IProductService } from '../../../../../../../platform/product/common/productService.js'; import { ICustomAgent, PromptsStorage, Target } from '../../../../common/promptSyntax/service/promptsService.js'; import { MockPromptsService } from '../../promptSyntax/service/mockPromptsService.js'; import { ExtensionIdentifier } from '../../../../../../../platform/extensions/common/extensions.js'; @@ -70,6 +71,7 @@ suite('RunSubagentTool', () => { configService, promptsService, {} as IInstantiationService, + {} as IProductService, )); const result = await tool.prepareToolInvocation( @@ -113,6 +115,7 @@ suite('RunSubagentTool', () => { configService, promptsService, {} as IInstantiationService, + {} as IProductService, )); const toolData = tool.getToolData(); @@ -141,6 +144,7 @@ suite('RunSubagentTool', () => { configService, promptsService, {} as IInstantiationService, + {} as IProductService, )); const toolData = tool.getToolData(); @@ -261,6 +265,7 @@ suite('RunSubagentTool', () => { configService, promptsService, {} as IInstantiationService, + {} as IProductService, )); return tool; diff --git a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts index f122a55550a83..f78e56bed5a10 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts @@ -1063,5 +1063,9 @@ declare module 'vscode' { readonly content: string; readonly toolReferences?: readonly ChatLanguageModelToolReference[]; readonly metadata?: Record; + /** + * Whether the mode is a builtin mode (e.g. Ask, Edit, Agent) rather than a user or extension-defined custom mode. + */ + readonly isBuiltin?: boolean; } }