Path: blob/main/src/vs/workbench/contrib/comments/browser/commentsAccessibleView.ts
5241 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 { CommentNode } from '../common/commentModel.js';18import { CommentContextKeys } from '../common/commentContextKeys.js';19import { moveToNextCommentInThread as findNextCommentInThread, revealCommentThread } from './commentsController.js';20import { IEditorService } from '../../../services/editor/common/editorService.js';21import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';22import { isCodeEditor } from '../../../../editor/browser/editorBrowser.js';23import { URI } from '../../../../base/common/uri.js';24import { CommentThread, Comment } from '../../../../editor/common/languages.js';25import { IRange } from '../../../../editor/common/core/range.js';26import { IAction } from '../../../../base/common/actions.js';2728export class CommentsAccessibleView extends Disposable implements IAccessibleViewImplementation {29readonly priority = 90;30readonly name = 'comment';31readonly when = CONTEXT_KEY_COMMENT_FOCUSED;32readonly type = AccessibleViewType.View;33getProvider(accessor: ServicesAccessor) {34const contextKeyService = accessor.get(IContextKeyService);35const viewsService = accessor.get(IViewsService);36const menuService = accessor.get(IMenuService);37const commentsView = viewsService.getActiveViewWithId<CommentsPanel>(COMMENTS_VIEW_ID);38const focusedCommentNode = commentsView?.focusedCommentNode;3940if (!commentsView || !focusedCommentNode) {41return;42}43const menus = this._register(new CommentsMenus(menuService));44menus.setContextKeyService(contextKeyService);4546return new CommentsAccessibleContentProvider(commentsView, focusedCommentNode, menus);47}48constructor() {49super();50}51}525354export class CommentThreadAccessibleView extends Disposable implements IAccessibleViewImplementation {55readonly priority = 85;56readonly name = 'commentThread';57readonly when = CommentContextKeys.commentFocused;58readonly type = AccessibleViewType.View;59getProvider(accessor: ServicesAccessor) {60const commentService = accessor.get(ICommentService);61const editorService = accessor.get(IEditorService);62const uriIdentityService = accessor.get(IUriIdentityService);63const threads = commentService.commentsModel.hasCommentThreads();64if (!threads) {65return;66}67return new CommentsThreadWidgetAccessibleContentProvider(commentService, editorService, uriIdentityService);68}69constructor() {70super();71}72}737475class CommentsAccessibleContentProvider extends Disposable implements IAccessibleViewContentProvider {76public readonly actions: IAction[];77constructor(78private readonly _commentsView: CommentsPanel,79private readonly _focusedCommentNode: CommentNode,80private readonly _menus: CommentsMenus,81) {82super();8384this.actions = [...this._menus.getResourceContextActions(this._focusedCommentNode)].filter(i => i.enabled).map(action => {85return {86...action,87run: () => {88this._commentsView.focus();89action.run({90thread: this._focusedCommentNode.thread,91$mid: MarshalledId.CommentThread,92commentControlHandle: this._focusedCommentNode.controllerHandle,93commentThreadHandle: this._focusedCommentNode.threadHandle,94});95}96};97});98}99readonly id = AccessibleViewProviderId.Comments;100readonly verbositySettingKey = AccessibilityVerbositySettingId.Comments;101readonly options = { type: AccessibleViewType.View };102103provideContent(): string {104const commentNode = this._commentsView.focusedCommentNode;105const content = this._commentsView.focusedCommentInfo?.toString();106if (!commentNode || !content) {107throw new Error('Comment tree is focused but no comment is selected');108}109return content;110}111onClose(): void {112this._commentsView.focus();113}114provideNextContent(): string | undefined {115this._commentsView.focusNextNode();116return this.provideContent();117}118providePreviousContent(): string | undefined {119this._commentsView.focusPreviousNode();120return this.provideContent();121}122}123124class CommentsThreadWidgetAccessibleContentProvider extends Disposable implements IAccessibleViewContentProvider {125readonly id = AccessibleViewProviderId.CommentThread;126readonly verbositySettingKey = AccessibilityVerbositySettingId.Comments;127readonly options = { type: AccessibleViewType.View };128private _activeCommentInfo: { thread: CommentThread<IRange>; comment?: Comment } | undefined;129constructor(@ICommentService private readonly _commentService: ICommentService,130@IEditorService private readonly _editorService: IEditorService,131@IUriIdentityService private readonly _uriIdentityService: IUriIdentityService,132) {133super();134}135136private get activeCommentInfo(): { thread: CommentThread<IRange>; comment?: Comment } | undefined {137if (!this._activeCommentInfo && this._commentService.lastActiveCommentcontroller) {138this._activeCommentInfo = this._commentService.lastActiveCommentcontroller.activeComment;139}140return this._activeCommentInfo;141}142143provideContent(): string {144if (!this.activeCommentInfo) {145throw new Error('No current comment thread');146}147const comment = this.activeCommentInfo.comment?.body;148const commentLabel = typeof comment === 'string' ? comment : comment?.value ?? '';149const resource = this.activeCommentInfo.thread.resource;150const range = this.activeCommentInfo.thread.range;151let contentLabel = '';152if (resource && range) {153const editor = this._editorService.findEditors(URI.parse(resource)) || [];154const codeEditor = this._editorService.activeEditorPane?.getControl();155if (editor?.length && isCodeEditor(codeEditor)) {156const content = codeEditor.getModel()?.getValueInRange(range);157if (content) {158contentLabel = '\nCorresponding code: \n' + content;159}160}161}162return commentLabel + contentLabel;163}164onClose(): void {165const lastComment = this._activeCommentInfo;166this._activeCommentInfo = undefined;167if (lastComment) {168revealCommentThread(this._commentService, this._editorService, this._uriIdentityService, lastComment.thread, lastComment.comment);169}170}171provideNextContent(): string | undefined {172const newCommentInfo = findNextCommentInThread(this._activeCommentInfo, 'next');173if (newCommentInfo) {174this._activeCommentInfo = newCommentInfo;175return this.provideContent();176}177return undefined;178}179providePreviousContent(): string | undefined {180const newCommentInfo = findNextCommentInThread(this._activeCommentInfo, 'previous');181if (newCommentInfo) {182this._activeCommentInfo = newCommentInfo;183return this.provideContent();184}185return undefined;186}187}188189190