Path: blob/main/src/vs/workbench/contrib/comments/browser/commentsAccessibleView.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Disposable } from '../../../../base/common/lifecycle.js';6import { MarshalledId } from '../../../../base/common/marshallingIds.js';7import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';8import { AccessibleViewProviderId, AccessibleViewType, IAccessibleViewContentProvider } from '../../../../platform/accessibility/browser/accessibleView.js';9import { IAccessibleViewImplementation } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js';10import { IMenuService } from '../../../../platform/actions/common/actions.js';11import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';12import { AccessibilityVerbositySettingId } from '../../accessibility/browser/accessibilityConfiguration.js';13import { COMMENTS_VIEW_ID, CommentsMenus } from './commentsTreeViewer.js';14import { CommentsPanel, CONTEXT_KEY_COMMENT_FOCUSED } from './commentsView.js';15import { IViewsService } from '../../../services/views/common/viewsService.js';16import { ICommentService } from './commentService.js';17import { CommentContextKeys } from '../common/commentContextKeys.js';18import { moveToNextCommentInThread as findNextCommentInThread, revealCommentThread } from './commentsController.js';19import { IEditorService } from '../../../services/editor/common/editorService.js';20import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';21import { isCodeEditor } from '../../../../editor/browser/editorBrowser.js';22import { URI } from '../../../../base/common/uri.js';23import { CommentThread, Comment } from '../../../../editor/common/languages.js';24import { IRange } from '../../../../editor/common/core/range.js';25import { IAction } from '../../../../base/common/actions.js';2627export class CommentsAccessibleView extends Disposable implements IAccessibleViewImplementation {28readonly priority = 90;29readonly name = 'comment';30readonly when = CONTEXT_KEY_COMMENT_FOCUSED;31readonly type = AccessibleViewType.View;32getProvider(accessor: ServicesAccessor) {33const contextKeyService = accessor.get(IContextKeyService);34const viewsService = accessor.get(IViewsService);35const menuService = accessor.get(IMenuService);36const commentsView = viewsService.getActiveViewWithId<CommentsPanel>(COMMENTS_VIEW_ID);37const focusedCommentNode = commentsView?.focusedCommentNode;3839if (!commentsView || !focusedCommentNode) {40return;41}42const menus = this._register(new CommentsMenus(menuService));43menus.setContextKeyService(contextKeyService);4445return new CommentsAccessibleContentProvider(commentsView, focusedCommentNode, menus);46}47constructor() {48super();49}50}515253export class CommentThreadAccessibleView extends Disposable implements IAccessibleViewImplementation {54readonly priority = 85;55readonly name = 'commentThread';56readonly when = CommentContextKeys.commentFocused;57readonly type = AccessibleViewType.View;58getProvider(accessor: ServicesAccessor) {59const commentService = accessor.get(ICommentService);60const editorService = accessor.get(IEditorService);61const uriIdentityService = accessor.get(IUriIdentityService);62const threads = commentService.commentsModel.hasCommentThreads();63if (!threads) {64return;65}66return new CommentsThreadWidgetAccessibleContentProvider(commentService, editorService, uriIdentityService);67}68constructor() {69super();70}71}727374class CommentsAccessibleContentProvider extends Disposable implements IAccessibleViewContentProvider {75public readonly actions: IAction[];76constructor(77private readonly _commentsView: CommentsPanel,78private readonly _focusedCommentNode: any,79private readonly _menus: CommentsMenus,80) {81super();8283this.actions = [...this._menus.getResourceContextActions(this._focusedCommentNode)].filter(i => i.enabled).map(action => {84return {85...action,86run: () => {87this._commentsView.focus();88action.run({89thread: this._focusedCommentNode.thread,90$mid: MarshalledId.CommentThread,91commentControlHandle: this._focusedCommentNode.controllerHandle,92commentThreadHandle: this._focusedCommentNode.threadHandle,93});94}95};96});97}98readonly id = AccessibleViewProviderId.Comments;99readonly verbositySettingKey = AccessibilityVerbositySettingId.Comments;100readonly options = { type: AccessibleViewType.View };101102provideContent(): string {103const commentNode = this._commentsView.focusedCommentNode;104const content = this._commentsView.focusedCommentInfo?.toString();105if (!commentNode || !content) {106throw new Error('Comment tree is focused but no comment is selected');107}108return content;109}110onClose(): void {111this._commentsView.focus();112}113provideNextContent(): string | undefined {114this._commentsView.focusNextNode();115return this.provideContent();116}117providePreviousContent(): string | undefined {118this._commentsView.focusPreviousNode();119return this.provideContent();120}121}122123class CommentsThreadWidgetAccessibleContentProvider extends Disposable implements IAccessibleViewContentProvider {124readonly id = AccessibleViewProviderId.CommentThread;125readonly verbositySettingKey = AccessibilityVerbositySettingId.Comments;126readonly options = { type: AccessibleViewType.View };127private _activeCommentInfo: { thread: CommentThread<IRange>; comment?: Comment } | undefined;128constructor(@ICommentService private readonly _commentService: ICommentService,129@IEditorService private readonly _editorService: IEditorService,130@IUriIdentityService private readonly _uriIdentityService: IUriIdentityService,131) {132super();133}134135private get activeCommentInfo(): { thread: CommentThread<IRange>; comment?: Comment } | undefined {136if (!this._activeCommentInfo && this._commentService.lastActiveCommentcontroller) {137this._activeCommentInfo = this._commentService.lastActiveCommentcontroller.activeComment;138}139return this._activeCommentInfo;140}141142provideContent(): string {143if (!this.activeCommentInfo) {144throw new Error('No current comment thread');145}146const comment = this.activeCommentInfo.comment?.body;147const commentLabel = typeof comment === 'string' ? comment : comment?.value ?? '';148const resource = this.activeCommentInfo.thread.resource;149const range = this.activeCommentInfo.thread.range;150let contentLabel = '';151if (resource && range) {152const editor = this._editorService.findEditors(URI.parse(resource)) || [];153const codeEditor = this._editorService.activeEditorPane?.getControl();154if (editor?.length && isCodeEditor(codeEditor)) {155const content = codeEditor.getModel()?.getValueInRange(range);156if (content) {157contentLabel = '\nCorresponding code: \n' + content;158}159}160}161return commentLabel + contentLabel;162}163onClose(): void {164const lastComment = this._activeCommentInfo;165this._activeCommentInfo = undefined;166if (lastComment) {167revealCommentThread(this._commentService, this._editorService, this._uriIdentityService, lastComment.thread, lastComment.comment);168}169}170provideNextContent(): string | undefined {171const newCommentInfo = findNextCommentInThread(this._activeCommentInfo, 'next');172if (newCommentInfo) {173this._activeCommentInfo = newCommentInfo;174return this.provideContent();175}176return undefined;177}178providePreviousContent(): string | undefined {179const newCommentInfo = findNextCommentInThread(this._activeCommentInfo, 'previous');180if (newCommentInfo) {181this._activeCommentInfo = newCommentInfo;182return this.provideContent();183}184return undefined;185}186}187188189