Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts
3296 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 { KeyChord, KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
7
import './media/review.css';
8
import { IActiveCodeEditor, isCodeEditor, isDiffEditor } from '../../../../editor/browser/editorBrowser.js';
9
import { EditorContributionInstantiation, registerEditorContribution } from '../../../../editor/browser/editorExtensions.js';
10
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
11
import * as nls from '../../../../nls.js';
12
import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
13
import { KeybindingsRegistry, KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
14
import { ICommentService } from './commentService.js';
15
import { ctxCommentEditorFocused, SimpleCommentEditor } from './simpleCommentEditor.js';
16
import { IEditorService } from '../../../services/editor/common/editorService.js';
17
import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js';
18
import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js';
19
import { CommentController, ID } from './commentsController.js';
20
import { IRange, Range } from '../../../../editor/common/core/range.js';
21
import { INotificationService } from '../../../../platform/notification/common/notification.js';
22
import { CommentContextKeys } from '../common/commentContextKeys.js';
23
import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from '../../../../platform/accessibility/common/accessibility.js';
24
import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
25
import { accessibilityHelpIsShown, accessibleViewCurrentProviderId } from '../../accessibility/browser/accessibilityConfiguration.js';
26
import { CommentCommandId } from '../common/commentCommandIds.js';
27
import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
28
import { CommentsInputContentProvider } from './commentsInputContentProvider.js';
29
import { AccessibleViewProviderId } from '../../../../platform/accessibility/browser/accessibleView.js';
30
import { CommentWidgetFocus } from './commentThreadZoneWidget.js';
31
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
32
33
registerEditorContribution(ID, CommentController, EditorContributionInstantiation.AfterFirstRender);
34
registerWorkbenchContribution2(CommentsInputContentProvider.ID, CommentsInputContentProvider, WorkbenchPhase.BlockRestore);
35
36
KeybindingsRegistry.registerCommandAndKeybindingRule({
37
id: CommentCommandId.NextThread,
38
handler: async (accessor, args?: { range: IRange; fileComment: boolean }) => {
39
const activeEditor = getActiveEditor(accessor);
40
if (!activeEditor) {
41
return Promise.resolve();
42
}
43
44
const controller = CommentController.get(activeEditor);
45
if (!controller) {
46
return Promise.resolve();
47
}
48
controller.nextCommentThread(true);
49
},
50
weight: KeybindingWeight.EditorContrib,
51
primary: KeyMod.Alt | KeyCode.F9,
52
});
53
54
KeybindingsRegistry.registerCommandAndKeybindingRule({
55
id: CommentCommandId.PreviousThread,
56
handler: async (accessor, args?: { range: IRange; fileComment: boolean }) => {
57
const activeEditor = getActiveEditor(accessor);
58
if (!activeEditor) {
59
return Promise.resolve();
60
}
61
62
const controller = CommentController.get(activeEditor);
63
if (!controller) {
64
return Promise.resolve();
65
}
66
controller.previousCommentThread(true);
67
},
68
weight: KeybindingWeight.EditorContrib,
69
primary: KeyMod.Shift | KeyMod.Alt | KeyCode.F9
70
});
71
72
registerAction2(class extends Action2 {
73
constructor() {
74
super({
75
id: CommentCommandId.NextCommentedRange,
76
title: {
77
value: nls.localize('comments.NextCommentedRange', "Go to Next Commented Range"),
78
original: 'Go to Next Commented Range'
79
},
80
category: {
81
value: nls.localize('commentsCategory', "Comments"),
82
original: 'Comments'
83
},
84
menu: [{
85
id: MenuId.CommandPalette,
86
when: CommentContextKeys.activeEditorHasCommentingRange
87
}],
88
keybinding: {
89
primary: KeyMod.Alt | KeyCode.F10,
90
weight: KeybindingWeight.EditorContrib,
91
when: CommentContextKeys.activeEditorHasCommentingRange
92
}
93
});
94
}
95
override run(accessor: ServicesAccessor, ...args: any[]): void {
96
const activeEditor = getActiveEditor(accessor);
97
if (!activeEditor) {
98
return;
99
}
100
101
const controller = CommentController.get(activeEditor);
102
if (!controller) {
103
return;
104
}
105
controller.nextCommentThread(false);
106
}
107
});
108
109
registerAction2(class extends Action2 {
110
constructor() {
111
super({
112
id: CommentCommandId.PreviousCommentedRange,
113
title: {
114
value: nls.localize('comments.previousCommentedRange', "Go to Previous Commented Range"),
115
original: 'Go to Previous Commented Range'
116
},
117
category: {
118
value: nls.localize('commentsCategory', "Comments"),
119
original: 'Comments'
120
},
121
menu: [{
122
id: MenuId.CommandPalette,
123
when: CommentContextKeys.activeEditorHasCommentingRange
124
}],
125
keybinding: {
126
primary: KeyMod.Shift | KeyMod.Alt | KeyCode.F10,
127
weight: KeybindingWeight.EditorContrib,
128
when: CommentContextKeys.activeEditorHasCommentingRange
129
}
130
});
131
}
132
override run(accessor: ServicesAccessor, ...args: any[]): void {
133
const activeEditor = getActiveEditor(accessor);
134
if (!activeEditor) {
135
return;
136
}
137
138
const controller = CommentController.get(activeEditor);
139
if (!controller) {
140
return;
141
}
142
controller.previousCommentThread(false);
143
}
144
});
145
146
registerAction2(class extends Action2 {
147
constructor() {
148
super({
149
id: CommentCommandId.NextRange,
150
title: {
151
value: nls.localize('comments.nextCommentingRange', "Go to Next Commenting Range"),
152
original: 'Go to Next Commenting Range'
153
},
154
category: {
155
value: nls.localize('commentsCategory', "Comments"),
156
original: 'Comments'
157
},
158
menu: [{
159
id: MenuId.CommandPalette,
160
when: CommentContextKeys.activeEditorHasCommentingRange
161
}],
162
keybinding: {
163
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.DownArrow),
164
weight: KeybindingWeight.EditorContrib,
165
when: ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, ContextKeyExpr.or(EditorContextKeys.focus, CommentContextKeys.commentFocused, ContextKeyExpr.and(accessibilityHelpIsShown, accessibleViewCurrentProviderId.isEqualTo(AccessibleViewProviderId.Comments))))
166
}
167
});
168
}
169
170
override run(accessor: ServicesAccessor, args?: { range: IRange; fileComment: boolean }): void {
171
const activeEditor = getActiveEditor(accessor);
172
if (!activeEditor) {
173
return;
174
}
175
176
const controller = CommentController.get(activeEditor);
177
if (!controller) {
178
return;
179
}
180
controller.nextCommentingRange();
181
}
182
});
183
184
registerAction2(class extends Action2 {
185
constructor() {
186
super({
187
id: CommentCommandId.PreviousRange,
188
title: {
189
value: nls.localize('comments.previousCommentingRange', "Go to Previous Commenting Range"),
190
original: 'Go to Previous Commenting Range'
191
},
192
category: {
193
value: nls.localize('commentsCategory', "Comments"),
194
original: 'Comments'
195
},
196
menu: [{
197
id: MenuId.CommandPalette,
198
when: CommentContextKeys.activeEditorHasCommentingRange
199
}],
200
keybinding: {
201
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.UpArrow),
202
weight: KeybindingWeight.EditorContrib,
203
when: ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, ContextKeyExpr.or(EditorContextKeys.focus, CommentContextKeys.commentFocused, ContextKeyExpr.and(accessibilityHelpIsShown, accessibleViewCurrentProviderId.isEqualTo(AccessibleViewProviderId.Comments))))
204
}
205
});
206
}
207
208
override async run(accessor: ServicesAccessor, ...args: any[]): Promise<void> {
209
const activeEditor = getActiveEditor(accessor);
210
if (!activeEditor) {
211
return;
212
}
213
214
const controller = CommentController.get(activeEditor);
215
if (!controller) {
216
return;
217
}
218
controller.previousCommentingRange();
219
}
220
});
221
222
registerAction2(class extends Action2 {
223
constructor() {
224
super({
225
id: CommentCommandId.ToggleCommenting,
226
title: {
227
value: nls.localize('comments.toggleCommenting', "Toggle Editor Commenting"),
228
original: 'Toggle Editor Commenting'
229
},
230
category: {
231
value: nls.localize('commentsCategory', "Comments"),
232
original: 'Comments'
233
},
234
menu: [{
235
id: MenuId.CommandPalette,
236
when: CommentContextKeys.WorkspaceHasCommenting
237
}]
238
});
239
}
240
override run(accessor: ServicesAccessor, ...args: any[]): void {
241
const commentService = accessor.get(ICommentService);
242
const enable = commentService.isCommentingEnabled;
243
commentService.enableCommenting(!enable);
244
}
245
});
246
247
registerAction2(class extends Action2 {
248
constructor() {
249
super({
250
id: CommentCommandId.Add,
251
title: {
252
value: nls.localize('comments.addCommand', "Add Comment on Current Selection"),
253
original: 'Add Comment on Current Selection'
254
},
255
category: {
256
value: nls.localize('commentsCategory', "Comments"),
257
original: 'Comments'
258
},
259
menu: [{
260
id: MenuId.CommandPalette,
261
when: CommentContextKeys.activeCursorHasCommentingRange
262
}],
263
keybinding: {
264
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyC),
265
weight: KeybindingWeight.EditorContrib,
266
when: CommentContextKeys.activeCursorHasCommentingRange
267
}
268
});
269
}
270
271
override async run(accessor: ServicesAccessor, args?: { range: IRange; fileComment: boolean }): Promise<void> {
272
const activeEditor = getActiveEditor(accessor);
273
if (!activeEditor) {
274
return;
275
}
276
277
const controller = CommentController.get(activeEditor);
278
if (!controller) {
279
return;
280
}
281
282
const position = args?.range ? new Range(args.range.startLineNumber, args.range.startLineNumber, args.range.endLineNumber, args.range.endColumn)
283
: (args?.fileComment ? undefined : activeEditor.getSelection());
284
await controller.addOrToggleCommentAtLine(position, undefined);
285
}
286
});
287
288
registerAction2(class extends Action2 {
289
constructor() {
290
super({
291
id: CommentCommandId.FocusCommentOnCurrentLine,
292
title: {
293
value: nls.localize('comments.focusCommentOnCurrentLine', "Focus Comment on Current Line"),
294
original: 'Focus Comment on Current Line'
295
},
296
category: {
297
value: nls.localize('commentsCategory', "Comments"),
298
original: 'Comments'
299
},
300
f1: true,
301
precondition: CommentContextKeys.activeCursorHasComment,
302
});
303
}
304
override async run(accessor: ServicesAccessor, ...args: any[]): Promise<void> {
305
const activeEditor = getActiveEditor(accessor);
306
if (!activeEditor) {
307
return;
308
}
309
310
const controller = CommentController.get(activeEditor);
311
if (!controller) {
312
return;
313
}
314
const position = activeEditor.getSelection();
315
const notificationService = accessor.get(INotificationService);
316
let error = false;
317
try {
318
const commentAtLine = controller.getCommentsAtLine(position);
319
if (commentAtLine.length === 0) {
320
error = true;
321
} else {
322
await controller.revealCommentThread(commentAtLine[0].commentThread.threadId, undefined, false, CommentWidgetFocus.Widget);
323
}
324
} catch (e) {
325
error = true;
326
}
327
if (error) {
328
notificationService.error(nls.localize('comments.focusCommand.error', "The cursor must be on a line with a comment to focus the comment"));
329
}
330
}
331
});
332
333
registerAction2(class extends Action2 {
334
constructor() {
335
super({
336
id: CommentCommandId.CollapseAll,
337
title: {
338
value: nls.localize('comments.collapseAll', "Collapse All Comments"),
339
original: 'Collapse All Comments'
340
},
341
category: {
342
value: nls.localize('commentsCategory', "Comments"),
343
original: 'Comments'
344
},
345
menu: [{
346
id: MenuId.CommandPalette,
347
when: CommentContextKeys.WorkspaceHasCommenting
348
}]
349
});
350
}
351
override run(accessor: ServicesAccessor, ...args: any[]): void {
352
getActiveController(accessor)?.collapseAll();
353
}
354
});
355
356
registerAction2(class extends Action2 {
357
constructor() {
358
super({
359
id: CommentCommandId.ExpandAll,
360
title: {
361
value: nls.localize('comments.expandAll', "Expand All Comments"),
362
original: 'Expand All Comments'
363
},
364
category: {
365
value: nls.localize('commentsCategory', "Comments"),
366
original: 'Comments'
367
},
368
menu: [{
369
id: MenuId.CommandPalette,
370
when: CommentContextKeys.WorkspaceHasCommenting
371
}]
372
});
373
}
374
override run(accessor: ServicesAccessor, ...args: any[]): void {
375
getActiveController(accessor)?.expandAll();
376
}
377
});
378
379
registerAction2(class extends Action2 {
380
constructor() {
381
super({
382
id: CommentCommandId.ExpandUnresolved,
383
title: {
384
value: nls.localize('comments.expandUnresolved', "Expand Unresolved Comments"),
385
original: 'Expand Unresolved Comments'
386
},
387
category: {
388
value: nls.localize('commentsCategory', "Comments"),
389
original: 'Comments'
390
},
391
menu: [{
392
id: MenuId.CommandPalette,
393
when: CommentContextKeys.WorkspaceHasCommenting
394
}]
395
});
396
}
397
override run(accessor: ServicesAccessor, ...args: any[]): void {
398
getActiveController(accessor)?.expandUnresolved();
399
}
400
});
401
402
KeybindingsRegistry.registerCommandAndKeybindingRule({
403
id: CommentCommandId.Submit,
404
weight: KeybindingWeight.EditorContrib,
405
primary: KeyMod.CtrlCmd | KeyCode.Enter,
406
when: ctxCommentEditorFocused,
407
handler: (accessor, args) => {
408
const activeCodeEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor();
409
if (activeCodeEditor instanceof SimpleCommentEditor) {
410
activeCodeEditor.getParentThread().submitComment();
411
}
412
}
413
});
414
415
KeybindingsRegistry.registerCommandAndKeybindingRule({
416
id: CommentCommandId.Hide,
417
weight: KeybindingWeight.EditorContrib,
418
primary: KeyCode.Escape,
419
secondary: [KeyMod.Shift | KeyCode.Escape],
420
when: ContextKeyExpr.or(ctxCommentEditorFocused, CommentContextKeys.commentFocused),
421
handler: async (accessor, args) => {
422
const activeCodeEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor();
423
const keybindingService = accessor.get(IKeybindingService);
424
// Unfortunate, but collapsing the comment thread might cause a dialog to show
425
// If we don't wait for the key up here, then the dialog will consume it and immediately close
426
await keybindingService.enableKeybindingHoldMode(CommentCommandId.Hide);
427
if (activeCodeEditor instanceof SimpleCommentEditor) {
428
activeCodeEditor.getParentThread().collapse();
429
} else if (activeCodeEditor) {
430
const controller = CommentController.get(activeCodeEditor);
431
if (!controller) {
432
return;
433
}
434
const notificationService = accessor.get(INotificationService);
435
const commentService = accessor.get(ICommentService);
436
let error = false;
437
try {
438
const activeComment = commentService.lastActiveCommentcontroller?.activeComment;
439
if (!activeComment) {
440
error = true;
441
} else {
442
controller.collapseAndFocusRange(activeComment.thread.threadId);
443
}
444
} catch (e) {
445
error = true;
446
}
447
if (error) {
448
notificationService.error(nls.localize('comments.focusCommand.error', "The cursor must be on a line with a comment to focus the comment"));
449
}
450
}
451
}
452
});
453
454
export function getActiveEditor(accessor: ServicesAccessor): IActiveCodeEditor | null {
455
let activeTextEditorControl = accessor.get(IEditorService).activeTextEditorControl;
456
457
if (isDiffEditor(activeTextEditorControl)) {
458
if (activeTextEditorControl.getOriginalEditor().hasTextFocus()) {
459
activeTextEditorControl = activeTextEditorControl.getOriginalEditor();
460
} else {
461
activeTextEditorControl = activeTextEditorControl.getModifiedEditor();
462
}
463
}
464
465
if (!isCodeEditor(activeTextEditorControl) || !activeTextEditorControl.hasModel()) {
466
return null;
467
}
468
469
return activeTextEditorControl;
470
}
471
472
function getActiveController(accessor: ServicesAccessor): CommentController | undefined {
473
const activeEditor = getActiveEditor(accessor);
474
if (!activeEditor) {
475
return undefined;
476
}
477
478
const controller = CommentController.get(activeEditor);
479
if (!controller) {
480
return undefined;
481
}
482
return controller;
483
}
484
485
486