diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 86f6c05bbd1..e51a8bebeb1 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -42,6 +42,7 @@ import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQui import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { AccessibilityVerbositySettingId, AccessibilityWorkbenchSettingId, AccessibleViewProviderId, accessibilityHelpIsShown, accessibleViewContainsCodeBlocks, accessibleViewCurrentProviderId, accessibleViewGoToSymbolSupported, accessibleViewInCodeBlock, accessibleViewIsShown, accessibleViewOnLastLine, accessibleViewSupportsNavigation, accessibleViewVerbosityEnabled } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; +import { CodeBlockContextProviderRegistry } from 'vs/workbench/contrib/chat/browser/chat'; import { ICodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; @@ -338,6 +339,9 @@ export class AccessibleView extends Disposable { // only cache a provider with an ID so that it will eventually be cleared. this._lastProvider = provider; } + if (provider.id === AccessibleViewProviderId.Chat) { + this._register(CodeBlockContextProviderRegistry.registerProvider({ getCodeBlockContext: () => this.getCodeBlockContext() }, 'accessibleView')); + } } previous(): void { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 060b9b0cdb3..2793bf70f13 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -6,7 +6,6 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { IBulkEditService, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; @@ -22,15 +21,12 @@ import { localize2 } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { TerminalLocation } from 'vs/platform/terminal/common/terminal'; import { IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor'; import { accessibleViewInCodeBlock } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; -import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; +import { CodeBlockContextProviderRegistry, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ICodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatCopyKind, IChatService, IDocumentContext } from 'vs/workbench/contrib/chat/common/chatService'; @@ -565,44 +561,17 @@ export function registerChatCodeBlockActions() { }); } -export interface ICodeBlockActionContextProvider { - getCodeBlockContext(editor: ICodeEditor): ICodeBlockActionContext | undefined; -} -export const ICodeBlockContextProviderRegistry = createDecorator('codeBlockContextProviderRegistry'); -export interface ICodeBlockContextProviderRegistry { -} -export class CodeBlockContextProviderRegistry { - serviceBrand: undefined; - static providers = new Map(); - static getProviders(): ICodeBlockActionContextProvider[] { - return [...CodeBlockContextProviderRegistry.providers.values()]; - } - static registerProvider(provider: ICodeBlockActionContextProvider, id: string): IDisposable { - CodeBlockContextProviderRegistry.providers.set(id, provider); - return toDisposable(() => CodeBlockContextProviderRegistry.providers.delete(id)); - } - - getProvider(id: string): ICodeBlockActionContextProvider | undefined { - return CodeBlockContextProviderRegistry.providers.get(id); - } -} -registerSingleton(ICodeBlockContextProviderRegistry, CodeBlockContextProviderRegistry, InstantiationType.Delayed); - function getContextFromEditor(editor: ICodeEditor, accessor: ServicesAccessor): ICodeBlockActionContext | undefined { const chatWidgetService = accessor.get(IChatWidgetService); - const accessibleViewService = accessor.get(IAccessibleViewService); - const accessibleViewCodeBlock = accessibleViewService.getCodeBlockContext(); - if (accessibleViewCodeBlock) { - return accessibleViewCodeBlock; - } const model = editor.getModel(); if (!model) { return; } const widget = chatWidgetService.lastFocusedWidget; - if (!widget) { - for (const provider of CodeBlockContextProviderRegistry.getProviders()) { + const codeBlockInfo = widget?.getCodeBlockInfoForEditor(model.uri); + if (!codeBlockInfo) { + for (const provider of CodeBlockContextProviderRegistry.providers) { const context = provider.getCodeBlockContext(editor); if (context) { return context; @@ -611,11 +580,6 @@ function getContextFromEditor(editor: ICodeEditor, accessor: ServicesAccessor): return; } - const codeBlockInfo = widget.getCodeBlockInfoForEditor(model.uri); - if (!codeBlockInfo) { - return; - } - return { element: codeBlockInfo.element, codeBlockIndex: codeBlockInfo.codeBlockIndex, diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index daa5ac41eb1..ecc65df2abf 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -4,11 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Selection } from 'vs/editor/common/core/selection'; +import { registerSingleton, InstantiationType } from 'vs/platform/instantiation/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatWidgetContrib } from 'vs/workbench/contrib/chat/browser/chatWidget'; +import { ICodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatRequestViewModel, IChatResponseViewModel, IChatViewModel, IChatWelcomeMessageViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; @@ -16,6 +19,7 @@ import { IChatRequestViewModel, IChatResponseViewModel, IChatViewModel, IChatWel export const IChatWidgetService = createDecorator('chatWidgetService'); export const IQuickChatService = createDecorator('quickChatService'); export const IChatAccessibilityService = createDecorator('chatAccessibilityService'); +export const ICodeBlockContextProviderRegistry = createDecorator('codeBlockContextProviderRegistry'); export interface IChatWidgetService { @@ -134,3 +138,23 @@ export interface IChatWidget { export interface IChatViewPane { clear(): void; } + + +export interface ICodeBlockActionContextProvider { + getCodeBlockContext(editor?: ICodeEditor): ICodeBlockActionContext | undefined; +} +export interface ICodeBlockContextProviderRegistry { + serviceBrand: undefined; +} +export class CodeBlockContextProviderRegistry implements ICodeBlockContextProviderRegistry { + serviceBrand: undefined; + static readonly _providers = new Map(); + static get providers(): ICodeBlockActionContextProvider[] { + return [...this._providers.values()]; + } + static registerProvider(provider: ICodeBlockActionContextProvider, id: string): IDisposable { + this._providers.set(id, provider); + return toDisposable(() => this._providers.delete(id)); + } +} +registerSingleton(ICodeBlockContextProviderRegistry, CodeBlockContextProviderRegistry, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/chat/browser/chatAccessibilityProvider.ts b/src/vs/workbench/contrib/chat/browser/chatAccessibilityProvider.ts new file mode 100644 index 00000000000..933a8940869 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatAccessibilityProvider.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 { AriaRole } from 'vs/base/browser/ui/aria/aria'; +import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { marked } from 'vs/base/common/marked/marked'; +import { localize } from 'vs/nls'; +import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { ChatTreeItem } from 'vs/workbench/contrib/chat/browser/chat'; +import { isRequestVM, isResponseVM, isWelcomeVM, IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; + +export class ChatAccessibilityProvider implements IListAccessibilityProvider { + + constructor( + @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService + ) { + + } + getWidgetRole(): AriaRole { + return 'list'; + } + + getRole(element: ChatTreeItem): AriaRole | undefined { + return 'listitem'; + } + + getWidgetAriaLabel(): string { + return localize('chat', "Chat"); + } + + getAriaLabel(element: ChatTreeItem): string { + if (isRequestVM(element)) { + return element.messageText; + } + + if (isResponseVM(element)) { + return this._getLabelWithCodeBlockCount(element); + } + + if (isWelcomeVM(element)) { + return element.content.map(c => 'value' in c ? c.value : c.map(followup => followup.message).join('\n')).join('\n'); + } + + return ''; + } + + private _getLabelWithCodeBlockCount(element: IChatResponseViewModel): string { + const accessibleViewHint = this._accessibleViewService.getOpenAriaHint(AccessibilityVerbositySettingId.Chat); + let label: string = ''; + const fileTreeCount = element.response.value.filter((v) => !('value' in v))?.length ?? 0; + let fileTreeCountHint = ''; + switch (fileTreeCount) { + case 0: + break; + case 1: + fileTreeCountHint = localize('singleFileTreeHint', "1 file tree"); + break; + default: + fileTreeCountHint = localize('multiFileTreeHint', "{0} file trees", fileTreeCount); + break; + } + const codeBlockCount = marked.lexer(element.response.asString()).filter(token => token.type === 'code')?.length ?? 0; + switch (codeBlockCount) { + case 0: + label = accessibleViewHint ? localize('noCodeBlocksHint', "{0} {1} {2}", fileTreeCountHint, element.response.asString(), accessibleViewHint) : localize('noCodeBlocks', "{0} {1}", fileTreeCountHint, element.response.asString()); + break; + case 1: + label = accessibleViewHint ? localize('singleCodeBlockHint', "{0} 1 code block: {1} {2}", fileTreeCountHint, element.response.asString(), accessibleViewHint) : localize('singleCodeBlock', "{0} 1 code block: {1}", fileTreeCountHint, element.response.asString()); + break; + default: + label = accessibleViewHint ? localize('multiCodeBlockHint', "{0} {1} code blocks: {2}", fileTreeCountHint, codeBlockCount, element.response.asString(), accessibleViewHint) : localize('multiCodeBlock', "{0} {1} code blocks", fileTreeCountHint, codeBlockCount, element.response.asString()); + break; + } + return label; + } +} diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 0b719131603..f60b2549be8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -5,11 +5,10 @@ import * as dom from 'vs/base/browser/dom'; import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { AriaRole, alert } from 'vs/base/browser/ui/aria/aria'; +import { alert } from 'vs/base/browser/ui/aria/aria'; import { Button } from 'vs/base/browser/ui/button/button'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree'; import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; @@ -23,7 +22,6 @@ import { FuzzyScore } from 'vs/base/common/filters'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, DisposableStore, IDisposable, IReference, toDisposable } from 'vs/base/common/lifecycle'; import { ResourceMap } from 'vs/base/common/map'; -import { marked } from 'vs/base/common/marked/marked'; import { FileAccess, Schemas, matchesSomeScheme } from 'vs/base/common/network'; import { clamp } from 'vs/base/common/numbers'; import { basename } from 'vs/base/common/path'; @@ -50,8 +48,6 @@ import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { ColorScheme } from 'vs/platform/theme/common/theme'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; -import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { ChatTreeItem, IChatCodeBlockInfo, IChatFileTreeInfo } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; import { ChatMarkdownDecorationsRenderer, IMarkdownVulnerability, annotateSpecialMarkdownContent, extractVulnerabilitiesFromText } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer'; @@ -999,72 +995,6 @@ export class ChatListDelegate implements IListVirtualDelegate { } } -export class ChatAccessibilityProvider implements IListAccessibilityProvider { - - constructor( - @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService - ) { - - } - getWidgetRole(): AriaRole { - return 'list'; - } - - getRole(element: ChatTreeItem): AriaRole | undefined { - return 'listitem'; - } - - getWidgetAriaLabel(): string { - return localize('chat', "Chat"); - } - - getAriaLabel(element: ChatTreeItem): string { - if (isRequestVM(element)) { - return element.messageText; - } - - if (isResponseVM(element)) { - return this._getLabelWithCodeBlockCount(element); - } - - if (isWelcomeVM(element)) { - return element.content.map(c => 'value' in c ? c.value : c.map(followup => followup.message).join('\n')).join('\n'); - } - - return ''; - } - - private _getLabelWithCodeBlockCount(element: IChatResponseViewModel): string { - const accessibleViewHint = this._accessibleViewService.getOpenAriaHint(AccessibilityVerbositySettingId.Chat); - let label: string = ''; - const fileTreeCount = element.response.value.filter((v) => !('value' in v))?.length ?? 0; - let fileTreeCountHint = ''; - switch (fileTreeCount) { - case 0: - break; - case 1: - fileTreeCountHint = localize('singleFileTreeHint', "1 file tree"); - break; - default: - fileTreeCountHint = localize('multiFileTreeHint', "{0} file trees", fileTreeCount); - break; - } - const codeBlockCount = marked.lexer(element.response.asString()).filter(token => token.type === 'code')?.length ?? 0; - switch (codeBlockCount) { - case 0: - label = accessibleViewHint ? localize('noCodeBlocksHint', "{0} {1} {2}", fileTreeCountHint, element.response.asString(), accessibleViewHint) : localize('noCodeBlocks', "{0} {1}", fileTreeCountHint, element.response.asString()); - break; - case 1: - label = accessibleViewHint ? localize('singleCodeBlockHint', "{0} 1 code block: {1} {2}", fileTreeCountHint, element.response.asString(), accessibleViewHint) : localize('singleCodeBlock', "{0} 1 code block: {1}", fileTreeCountHint, element.response.asString()); - break; - default: - label = accessibleViewHint ? localize('multiCodeBlockHint', "{0} {1} code blocks: {2}", fileTreeCountHint, codeBlockCount, element.response.asString(), accessibleViewHint) : localize('multiCodeBlock', "{0} {1} code blocks", fileTreeCountHint, codeBlockCount, element.response.asString()); - break; - } - return label; - } -} - interface IDisposableReference extends IDisposable { object: T; diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index b2e1680a28c..bcc50a4370c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -26,8 +26,9 @@ import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { ILogService } from 'vs/platform/log/common/log'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ChatTreeItem, IChatAccessibilityService, IChatCodeBlockInfo, IChatFileTreeInfo, IChatWidget, IChatWidgetService, IChatWidgetViewContext, IChatWidgetViewOptions } from 'vs/workbench/contrib/chat/browser/chat'; +import { ChatAccessibilityProvider } from 'vs/workbench/contrib/chat/browser/chatAccessibilityProvider'; import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; -import { ChatAccessibilityProvider, ChatListDelegate, ChatListItemRenderer, IChatListItemRendererOptions, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; +import { ChatListDelegate, ChatListItemRenderer, IChatListItemRendererOptions, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts index 44bbdcce1ed..88d02f60056 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController.ts @@ -13,7 +13,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; +import { CodeBlockContextProviderRegistry, IChatAccessibilityService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatAgentService, IChatAgentRequest } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatService, IChatProgress, InteractiveSessionVoteDirection, ChatUserAction } from 'vs/workbench/contrib/chat/common/chatService'; @@ -25,7 +25,6 @@ import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/br import { ChatModel, ChatRequestModel, IChatRequestVariableData, getHistoryEntriesFromModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat'; import { MarkdownString } from 'vs/base/common/htmlContent'; -import { CodeBlockContextProviderRegistry } from 'vs/workbench/contrib/chat/browser/actions/chatCodeblockActions'; const enum Message { NONE = 0, @@ -119,7 +118,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr } else { this._terminalAgentRegisteredContextKey.set(true); } - CodeBlockContextProviderRegistry.registerProvider({ + this._register(CodeBlockContextProviderRegistry.registerProvider({ getCodeBlockContext: (editor) => { const chatWidget = this.chatWidget; if (!chatWidget) { @@ -135,7 +134,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr languageId: editor.getModel()!.getLanguageId() }; } - }, 'terminal'); + }, 'terminal')); } xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void {