add accessible view for comment thread widget (#228018)

pull/228997/head
Megan Rogge 2024-09-17 12:09:42 -07:00 committed by GitHub
parent 2c9654d473
commit 15183fce60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 142 additions and 2 deletions

View File

@ -7,7 +7,7 @@
{
"kind": 2,
"language": "github-issues",
"value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"August 2024\"\n\n$MINE=assignee:@me"
"value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"September 2024\"\n\n$MINE=assignee:@me"
},
{
"kind": 1,

View File

@ -28,6 +28,7 @@ export const enum AccessibleViewProviderId {
Notification = 'notification',
EmptyEditorHint = 'emptyEditorHint',
Comments = 'comments',
CommentThread = 'commentThread',
Repl = 'repl',
ReplHelp = 'replHelp',
RunAndDebug = 'runAndDebug',

View File

@ -286,7 +286,13 @@ export class MainThreadCommentController implements ICommentController {
private _features: CommentProviderFeatures
) { }
get activeComment() {
return this._activeComment;
}
private _activeComment: { thread: languages.CommentThread; comment?: languages.Comment } | undefined;
async setActiveCommentAndThread(commentInfo: { thread: languages.CommentThread; comment?: languages.Comment } | undefined) {
this._activeComment = commentInfo;
return this._proxy.$setActiveComment(this._handle, commentInfo ? { commentThreadHandle: commentInfo.thread.commentThreadHandle, uniqueIdInThread: commentInfo.comment?.uniqueIdInThread } : undefined);
}

View File

@ -63,6 +63,7 @@ export interface ICommentController {
options?: CommentOptions;
contextValue?: string;
owner: string;
activeComment: { thread: CommentThread; comment?: Comment } | undefined;
createCommentThreadTemplate(resource: UriComponents, range: IRange | undefined, editorId?: string): Promise<void>;
updateCommentThreadTemplate(threadHandle: number, range: IRange): Promise<void>;
deleteCommentThreadMain(commentThreadId: string): void;
@ -91,6 +92,7 @@ export interface ICommentService {
readonly onDidChangeCommentingEnabled: Event<boolean>;
readonly isCommentingEnabled: boolean;
readonly commentsModel: ICommentsModel;
readonly lastActiveCommentcontroller: ICommentController | undefined;
setDocumentComments(resource: URI, commentInfos: ICommentInfo[]): void;
setWorkspaceComments(uniqueOwner: string, commentsByResource: CommentThread<IRange | ICellRange>[]): void;
removeWorkspaceComments(uniqueOwner: string): void;
@ -295,6 +297,10 @@ export class CommentService extends Disposable implements ICommentService {
this._onDidChangeActiveEditingCommentThread.fire(commentThread);
}
get lastActiveCommentcontroller() {
return this._lastActiveCommentController;
}
private _lastActiveCommentController: ICommentController | undefined;
async setActiveCommentAndThread(uniqueOwner: string, commentInfo: { thread: CommentThread<IRange>; comment?: Comment } | undefined) {
const commentController = this._commentControls.get(uniqueOwner);

View File

@ -28,7 +28,7 @@ import { MarshalledCommentThreadInternal } from '../../../common/comments.js';
import { accessibleViewCurrentProviderId, accessibleViewIsShown } from '../../accessibility/browser/accessibilityConfiguration.js';
import { AccessibleViewProviderId } from '../../../../platform/accessibility/browser/accessibleView.js';
import { AccessibleViewRegistry } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js';
import { CommentsAccessibleView } from './commentsAccessibleView.js';
import { CommentsAccessibleView, CommentThreadAccessibleView } from './commentsAccessibleView.js';
import { CommentsAccessibilityHelp } from './commentsAccessibility.js';
registerAction2(class Collapse extends ViewAction<CommentsPanel> {
@ -193,4 +193,5 @@ export class UnresolvedCommentsBadge extends Disposable implements IWorkbenchCon
Registry.as<IWorkbenchContributionsRegistry>(Extensions.Workbench).registerWorkbenchContribution(UnresolvedCommentsBadge, LifecyclePhase.Eventually);
AccessibleViewRegistry.register(new CommentsAccessibleView());
AccessibleViewRegistry.register(new CommentThreadAccessibleView());
AccessibleViewRegistry.register(new CommentsAccessibilityHelp());

View File

@ -14,6 +14,15 @@ import { AccessibilityVerbositySettingId } from '../../accessibility/browser/acc
import { COMMENTS_VIEW_ID, CommentsMenus } from './commentsTreeViewer.js';
import { CommentsPanel, CONTEXT_KEY_COMMENT_FOCUSED } from './commentsView.js';
import { IViewsService } from '../../../services/views/common/viewsService.js';
import { ICommentService } from './commentService.js';
import { CommentContextKeys } from '../common/commentContextKeys.js';
import { moveToNextCommentInThread as findNextCommentInThread, revealCommentThread } from './commentsController.js';
import { IEditorService } from '../../../services/editor/common/editorService.js';
import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';
import { isCodeEditor } from '../../../../editor/browser/editorBrowser.js';
import { URI } from '../../../../base/common/uri.js';
import { CommentThread, Comment } from '../../../../editor/common/languages.js';
import { IRange } from '../../../../editor/common/core/range.js';
export class CommentsAccessibleView extends Disposable implements IAccessibleViewImplentation {
readonly priority = 90;
@ -26,6 +35,7 @@ export class CommentsAccessibleView extends Disposable implements IAccessibleVie
const menuService = accessor.get(IMenuService);
const commentsView = viewsService.getActiveViewWithId<CommentsPanel>(COMMENTS_VIEW_ID);
const focusedCommentNode = commentsView?.focusedCommentNode;
if (!commentsView || !focusedCommentNode) {
return;
}
@ -39,6 +49,28 @@ export class CommentsAccessibleView extends Disposable implements IAccessibleVie
}
}
export class CommentThreadAccessibleView extends Disposable implements IAccessibleViewImplentation {
readonly priority = 85;
readonly name = 'commentThread';
readonly when = CommentContextKeys.commentFocused;
readonly type = AccessibleViewType.View;
getProvider(accessor: ServicesAccessor) {
const commentService = accessor.get(ICommentService);
const editorService = accessor.get(IEditorService);
const uriIdentityService = accessor.get(IUriIdentityService);
const threads = commentService.commentsModel.hasCommentThreads();
if (!threads) {
return;
}
return new CommentsThreadWidgetAccessibleContentProvider(commentService, editorService, uriIdentityService);
}
constructor() {
super();
}
}
class CommentsAccessibleContentProvider extends Disposable implements IAccessibleViewContentProvider {
constructor(
private readonly _commentsView: CommentsPanel,
@ -84,3 +116,68 @@ class CommentsAccessibleContentProvider extends Disposable implements IAccessibl
return this.provideContent();
}
}
class CommentsThreadWidgetAccessibleContentProvider extends Disposable implements IAccessibleViewContentProvider {
readonly id = AccessibleViewProviderId.CommentThread;
readonly verbositySettingKey = AccessibilityVerbositySettingId.Comments;
readonly options = { type: AccessibleViewType.View };
private _activeCommentInfo: { thread: CommentThread<IRange>; comment?: Comment } | undefined;
constructor(@ICommentService private readonly _commentService: ICommentService,
@IEditorService private readonly _editorService: IEditorService,
@IUriIdentityService private readonly _uriIdentityService: IUriIdentityService,
) {
super();
}
private get activeCommentInfo(): { thread: CommentThread<IRange>; comment?: Comment } | undefined {
if (!this._activeCommentInfo && this._commentService.lastActiveCommentcontroller) {
this._activeCommentInfo = this._commentService.lastActiveCommentcontroller.activeComment;
}
return this._activeCommentInfo;
}
provideContent(): string {
if (!this.activeCommentInfo) {
throw new Error('No current comment thread');
}
const comment = this.activeCommentInfo.comment?.body;
const commentLabel = typeof comment === 'string' ? comment : comment?.value ?? '';
const resource = this.activeCommentInfo.thread.resource;
const range = this.activeCommentInfo.thread.range;
let contentLabel = '';
if (resource && range) {
const editor = this._editorService.findEditors(URI.parse(resource)) || [];
const codeEditor = this._editorService.activeEditorPane?.getControl();
if (editor?.length && isCodeEditor(codeEditor)) {
const content = codeEditor.getModel()?.getValueInRange(range);
if (content) {
contentLabel = '\nCorresponding code: \n' + content;
}
}
}
return commentLabel + contentLabel;
}
onClose(): void {
const lastComment = this._activeCommentInfo;
this._activeCommentInfo = undefined;
if (lastComment) {
revealCommentThread(this._commentService, this._editorService, this._uriIdentityService, lastComment.thread, lastComment.comment);
}
}
provideNextContent(): string | undefined {
const newCommentInfo = findNextCommentInThread(this._activeCommentInfo, 'next');
if (newCommentInfo) {
this._activeCommentInfo = newCommentInfo;
return this.provideContent();
}
return undefined;
}
providePreviousContent(): string | undefined {
const newCommentInfo = findNextCommentInThread(this._activeCommentInfo, 'previous');
if (newCommentInfo) {
this._activeCommentInfo = newCommentInfo;
return this.provideContent();
}
return undefined;
}
}

View File

@ -368,6 +368,34 @@ class CommentingRangeDecorator {
}
}
/**
* Navigate to the next or previous comment in the current thread.
* @param type
*/
export function moveToNextCommentInThread(commentInfo: { thread: languages.CommentThread<IRange>; comment?: languages.Comment } | undefined, type: 'next' | 'previous') {
if (!commentInfo?.comment || !commentInfo?.thread?.comments) {
return;
}
const currentIndex = commentInfo.thread.comments?.indexOf(commentInfo.comment);
if (currentIndex === undefined || currentIndex < 0) {
return;
}
if (type === 'previous' && currentIndex === 0) {
return;
}
if (type === 'next' && currentIndex === commentInfo.thread.comments.length - 1) {
return;
}
const comment = commentInfo.thread.comments?.[type === 'previous' ? currentIndex - 1 : currentIndex + 1];
if (!comment) {
return;
}
return {
...commentInfo,
comment,
};
}
export function revealCommentThread(commentService: ICommentService, editorService: IEditorService, uriIdentityService: IUriIdentityService,
commentThread: languages.CommentThread<IRange>, comment: languages.Comment | undefined, focusReply?: boolean, pinned?: boolean, preserveFocus?: boolean, sideBySide?: boolean): void {
if (!commentThread.resource) {

View File

@ -49,6 +49,7 @@ class TestCommentThread implements CommentThread<IRange> {
}
class TestCommentController implements ICommentController {
activeComment: { thread: CommentThread; comment?: Comment } | undefined;
id: string = 'test';
label: string = 'Test Comments';
owner: string = 'test';