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