add accessible view provider

This commit is contained in:
meganrogge
2024-03-08 15:57:47 -08:00
parent 016fa658d8
commit dc97163e1c
7 changed files with 117 additions and 116 deletions
@@ -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 {
@@ -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<ICodeBlockContextProviderRegistry>('codeBlockContextProviderRegistry');
export interface ICodeBlockContextProviderRegistry {
}
export class CodeBlockContextProviderRegistry {
serviceBrand: undefined;
static providers = new Map<string, ICodeBlockActionContextProvider>();
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,
@@ -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<IChatWidgetService>('chatWidgetService');
export const IQuickChatService = createDecorator<IQuickChatService>('quickChatService');
export const IChatAccessibilityService = createDecorator<IChatAccessibilityService>('chatAccessibilityService');
export const ICodeBlockContextProviderRegistry = createDecorator<ICodeBlockContextProviderRegistry>('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<string, ICodeBlockActionContextProvider>();
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);
@@ -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<ChatTreeItem> {
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;
}
}
@@ -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<ChatTreeItem> {
}
}
export class ChatAccessibilityProvider implements IListAccessibilityProvider<ChatTreeItem> {
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<T> extends IDisposable {
object: T;
@@ -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';
@@ -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 {