Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/api/common/extHostComments.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 { asPromise } from '../../../base/common/async.js';
7
import { CancellationToken } from '../../../base/common/cancellation.js';
8
import { debounce } from '../../../base/common/decorators.js';
9
import { Emitter } from '../../../base/common/event.js';
10
import { DisposableStore, MutableDisposable } from '../../../base/common/lifecycle.js';
11
import { MarshalledId } from '../../../base/common/marshallingIds.js';
12
import { URI, UriComponents } from '../../../base/common/uri.js';
13
import { IRange } from '../../../editor/common/core/range.js';
14
import * as languages from '../../../editor/common/languages.js';
15
import { ExtensionIdentifierMap, IExtensionDescription } from '../../../platform/extensions/common/extensions.js';
16
import { ExtHostDocuments } from './extHostDocuments.js';
17
import * as extHostTypeConverter from './extHostTypeConverters.js';
18
import * as types from './extHostTypes.js';
19
import type * as vscode from 'vscode';
20
import { ExtHostCommentsShape, IMainContext, MainContext, CommentThreadChanges, CommentChanges } from './extHost.protocol.js';
21
import { ExtHostCommands } from './extHostCommands.js';
22
import { checkProposedApiEnabled } from '../../services/extensions/common/extensions.js';
23
import { MarshalledCommentThread } from '../../common/comments.js';
24
25
type ProviderHandle = number;
26
27
interface ExtHostComments {
28
createCommentController(extension: IExtensionDescription, id: string, label: string): vscode.CommentController;
29
}
30
31
export function createExtHostComments(mainContext: IMainContext, commands: ExtHostCommands, documents: ExtHostDocuments): ExtHostCommentsShape & ExtHostComments {
32
const proxy = mainContext.getProxy(MainContext.MainThreadComments);
33
34
class ExtHostCommentsImpl implements ExtHostCommentsShape, ExtHostComments {
35
36
private static handlePool = 0;
37
38
39
private _commentControllers: Map<ProviderHandle, ExtHostCommentController> = new Map<ProviderHandle, ExtHostCommentController>();
40
41
private _commentControllersByExtension: ExtensionIdentifierMap<ExtHostCommentController[]> = new ExtensionIdentifierMap<ExtHostCommentController[]>();
42
43
44
constructor(
45
) {
46
commands.registerArgumentProcessor({
47
processArgument: arg => {
48
if (arg && arg.$mid === MarshalledId.CommentController) {
49
const commentController = this._commentControllers.get(arg.handle);
50
51
if (!commentController) {
52
return arg;
53
}
54
55
return commentController.value;
56
} else if (arg && arg.$mid === MarshalledId.CommentThread) {
57
const marshalledCommentThread: MarshalledCommentThread = arg;
58
const commentController = this._commentControllers.get(marshalledCommentThread.commentControlHandle);
59
60
if (!commentController) {
61
return marshalledCommentThread;
62
}
63
64
const commentThread = commentController.getCommentThread(marshalledCommentThread.commentThreadHandle);
65
66
if (!commentThread) {
67
return marshalledCommentThread;
68
}
69
70
return commentThread.value;
71
} else if (arg && (arg.$mid === MarshalledId.CommentThreadReply || arg.$mid === MarshalledId.CommentThreadInstance)) {
72
const commentController = this._commentControllers.get(arg.thread.commentControlHandle);
73
74
if (!commentController) {
75
return arg;
76
}
77
78
const commentThread = commentController.getCommentThread(arg.thread.commentThreadHandle);
79
80
if (!commentThread) {
81
return arg;
82
}
83
84
if (arg.$mid === MarshalledId.CommentThreadInstance) {
85
return commentThread.value;
86
}
87
88
return {
89
thread: commentThread.value,
90
text: arg.text
91
};
92
} else if (arg && arg.$mid === MarshalledId.CommentNode) {
93
const commentController = this._commentControllers.get(arg.thread.commentControlHandle);
94
95
if (!commentController) {
96
return arg;
97
}
98
99
const commentThread = commentController.getCommentThread(arg.thread.commentThreadHandle);
100
101
if (!commentThread) {
102
return arg;
103
}
104
105
const commentUniqueId = arg.commentUniqueId;
106
107
const comment = commentThread.getCommentByUniqueId(commentUniqueId);
108
109
if (!comment) {
110
return arg;
111
}
112
113
return comment;
114
115
} else if (arg && arg.$mid === MarshalledId.CommentThreadNode) {
116
const commentController = this._commentControllers.get(arg.thread.commentControlHandle);
117
118
if (!commentController) {
119
return arg;
120
}
121
122
const commentThread = commentController.getCommentThread(arg.thread.commentThreadHandle);
123
124
if (!commentThread) {
125
return arg;
126
}
127
128
const body: string = arg.text;
129
const commentUniqueId = arg.commentUniqueId;
130
131
const comment = commentThread.getCommentByUniqueId(commentUniqueId);
132
133
if (!comment) {
134
return arg;
135
}
136
137
// If the old comment body was a markdown string, use a markdown string here too.
138
if (typeof comment.body === 'string') {
139
comment.body = body;
140
} else {
141
comment.body = new types.MarkdownString(body);
142
}
143
return comment;
144
}
145
146
return arg;
147
}
148
});
149
}
150
151
createCommentController(extension: IExtensionDescription, id: string, label: string): vscode.CommentController {
152
const handle = ExtHostCommentsImpl.handlePool++;
153
const commentController = new ExtHostCommentController(extension, handle, id, label);
154
this._commentControllers.set(commentController.handle, commentController);
155
156
const commentControllers = this._commentControllersByExtension.get(extension.identifier) || [];
157
commentControllers.push(commentController);
158
this._commentControllersByExtension.set(extension.identifier, commentControllers);
159
160
return commentController.value;
161
}
162
163
async $createCommentThreadTemplate(commentControllerHandle: number, uriComponents: UriComponents, range: IRange | undefined, editorId?: string): Promise<void> {
164
const commentController = this._commentControllers.get(commentControllerHandle);
165
166
if (!commentController) {
167
return;
168
}
169
170
commentController.$createCommentThreadTemplate(uriComponents, range, editorId);
171
}
172
173
async $setActiveComment(controllerHandle: number, commentInfo: { commentThreadHandle: number; uniqueIdInThread?: number }): Promise<void> {
174
const commentController = this._commentControllers.get(controllerHandle);
175
176
if (!commentController) {
177
return;
178
}
179
180
commentController.$setActiveComment(commentInfo ?? undefined);
181
}
182
183
async $updateCommentThreadTemplate(commentControllerHandle: number, threadHandle: number, range: IRange) {
184
const commentController = this._commentControllers.get(commentControllerHandle);
185
186
if (!commentController) {
187
return;
188
}
189
190
commentController.$updateCommentThreadTemplate(threadHandle, range);
191
}
192
193
$deleteCommentThread(commentControllerHandle: number, commentThreadHandle: number) {
194
const commentController = this._commentControllers.get(commentControllerHandle);
195
196
commentController?.$deleteCommentThread(commentThreadHandle);
197
}
198
199
async $updateCommentThread(commentControllerHandle: number, commentThreadHandle: number, changes: CommentThreadChanges) {
200
const commentController = this._commentControllers.get(commentControllerHandle);
201
202
commentController?.$updateCommentThread(commentThreadHandle, changes);
203
}
204
205
async $provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise<{ ranges: IRange[]; fileComments: boolean } | undefined> {
206
const commentController = this._commentControllers.get(commentControllerHandle);
207
208
if (!commentController || !commentController.commentingRangeProvider) {
209
return Promise.resolve(undefined);
210
}
211
212
const document = await documents.ensureDocumentData(URI.revive(uriComponents));
213
return asPromise(async () => {
214
const rangesResult = await commentController.commentingRangeProvider?.provideCommentingRanges(document.document, token);
215
let ranges: { ranges: vscode.Range[]; fileComments: boolean } | undefined;
216
if (Array.isArray(rangesResult)) {
217
ranges = {
218
ranges: rangesResult,
219
fileComments: false
220
};
221
} else if (rangesResult) {
222
ranges = {
223
ranges: rangesResult.ranges || [],
224
fileComments: rangesResult.enableFileComments || false
225
};
226
} else {
227
ranges = rangesResult ?? undefined;
228
}
229
return ranges;
230
}).then(ranges => {
231
let convertedResult: { ranges: IRange[]; fileComments: boolean } | undefined = undefined;
232
if (ranges) {
233
convertedResult = {
234
ranges: ranges.ranges.map(x => extHostTypeConverter.Range.from(x)),
235
fileComments: ranges.fileComments
236
};
237
}
238
return convertedResult;
239
});
240
}
241
242
$toggleReaction(commentControllerHandle: number, threadHandle: number, uri: UriComponents, comment: languages.Comment, reaction: languages.CommentReaction): Promise<void> {
243
const commentController = this._commentControllers.get(commentControllerHandle);
244
245
if (!commentController || !commentController.reactionHandler) {
246
return Promise.resolve(undefined);
247
}
248
249
return asPromise(() => {
250
const commentThread = commentController.getCommentThread(threadHandle);
251
if (commentThread) {
252
const vscodeComment = commentThread.getCommentByUniqueId(comment.uniqueIdInThread);
253
254
if (commentController !== undefined && vscodeComment) {
255
if (commentController.reactionHandler) {
256
return commentController.reactionHandler(vscodeComment, convertFromReaction(reaction));
257
}
258
}
259
}
260
261
return Promise.resolve(undefined);
262
});
263
}
264
}
265
type CommentThreadModification = Partial<{
266
range: vscode.Range;
267
label: string | undefined;
268
contextValue: string | undefined;
269
comments: vscode.Comment[];
270
collapsibleState: vscode.CommentThreadCollapsibleState;
271
canReply: boolean | vscode.CommentAuthorInformation;
272
state: vscode.CommentThreadState;
273
isTemplate: boolean;
274
applicability: vscode.CommentThreadApplicability;
275
}>;
276
277
class ExtHostCommentThread implements vscode.CommentThread2 {
278
private static _handlePool: number = 0;
279
readonly handle = ExtHostCommentThread._handlePool++;
280
public commentHandle: number = 0;
281
282
private modifications: CommentThreadModification = Object.create(null);
283
284
set threadId(id: string) {
285
this._id = id;
286
}
287
288
get threadId(): string {
289
return this._id!;
290
}
291
292
get id(): string {
293
return this._id!;
294
}
295
296
get resource(): vscode.Uri {
297
return this._uri;
298
}
299
300
get uri(): vscode.Uri {
301
return this._uri;
302
}
303
304
private readonly _onDidUpdateCommentThread = new Emitter<void>();
305
readonly onDidUpdateCommentThread = this._onDidUpdateCommentThread.event;
306
307
set range(range: vscode.Range | undefined) {
308
if (((range === undefined) !== (this._range === undefined)) || (!range || !this._range || !range.isEqual(this._range))) {
309
this._range = range;
310
this.modifications.range = range;
311
this._onDidUpdateCommentThread.fire();
312
}
313
}
314
315
get range(): vscode.Range | undefined {
316
return this._range;
317
}
318
319
private _canReply: boolean | vscode.CommentAuthorInformation = true;
320
321
set canReply(state: boolean | vscode.CommentAuthorInformation) {
322
if (this._canReply !== state) {
323
this._canReply = state;
324
this.modifications.canReply = state;
325
this._onDidUpdateCommentThread.fire();
326
}
327
}
328
get canReply() {
329
return this._canReply;
330
}
331
332
private _label: string | undefined;
333
334
get label(): string | undefined {
335
return this._label;
336
}
337
338
set label(label: string | undefined) {
339
this._label = label;
340
this.modifications.label = label;
341
this._onDidUpdateCommentThread.fire();
342
}
343
344
private _contextValue: string | undefined;
345
346
get contextValue(): string | undefined {
347
return this._contextValue;
348
}
349
350
set contextValue(context: string | undefined) {
351
this._contextValue = context;
352
this.modifications.contextValue = context;
353
this._onDidUpdateCommentThread.fire();
354
}
355
356
get comments(): vscode.Comment[] {
357
return this._comments;
358
}
359
360
set comments(newComments: vscode.Comment[]) {
361
this._comments = newComments;
362
this.modifications.comments = newComments;
363
this._onDidUpdateCommentThread.fire();
364
}
365
366
private _collapseState?: vscode.CommentThreadCollapsibleState;
367
368
get collapsibleState(): vscode.CommentThreadCollapsibleState {
369
return this._collapseState!;
370
}
371
372
set collapsibleState(newState: vscode.CommentThreadCollapsibleState) {
373
if (this._collapseState === newState) {
374
return;
375
}
376
this._collapseState = newState;
377
this.modifications.collapsibleState = newState;
378
this._onDidUpdateCommentThread.fire();
379
}
380
381
private _state?: vscode.CommentThreadState | { resolved?: vscode.CommentThreadState; applicability?: vscode.CommentThreadApplicability };
382
383
get state(): vscode.CommentThreadState | { resolved?: vscode.CommentThreadState; applicability?: vscode.CommentThreadApplicability } | undefined {
384
return this._state!;
385
}
386
387
set state(newState: vscode.CommentThreadState | { resolved?: vscode.CommentThreadState; applicability?: vscode.CommentThreadApplicability }) {
388
this._state = newState;
389
if (typeof newState === 'object') {
390
checkProposedApiEnabled(this.extensionDescription, 'commentThreadApplicability');
391
this.modifications.state = newState.resolved;
392
this.modifications.applicability = newState.applicability;
393
} else {
394
this.modifications.state = newState;
395
}
396
this._onDidUpdateCommentThread.fire();
397
}
398
399
private _localDisposables: types.Disposable[];
400
401
private _isDiposed: boolean;
402
403
public get isDisposed(): boolean {
404
return this._isDiposed;
405
}
406
407
private _commentsMap: Map<vscode.Comment, number> = new Map<vscode.Comment, number>();
408
409
private readonly _acceptInputDisposables = new MutableDisposable<DisposableStore>();
410
411
readonly value: vscode.CommentThread2;
412
413
constructor(
414
commentControllerId: string,
415
private _commentControllerHandle: number,
416
private _id: string | undefined,
417
private _uri: vscode.Uri,
418
private _range: vscode.Range | undefined,
419
private _comments: vscode.Comment[],
420
public readonly extensionDescription: IExtensionDescription,
421
private _isTemplate: boolean,
422
editorId?: string
423
) {
424
this._acceptInputDisposables.value = new DisposableStore();
425
426
if (this._id === undefined) {
427
this._id = `${commentControllerId}.${this.handle}`;
428
}
429
430
proxy.$createCommentThread(
431
_commentControllerHandle,
432
this.handle,
433
this._id,
434
this._uri,
435
extHostTypeConverter.Range.from(this._range),
436
this._comments.map(cmt => convertToDTOComment(this, cmt, this._commentsMap, this.extensionDescription)),
437
extensionDescription.identifier,
438
this._isTemplate,
439
editorId
440
);
441
442
this._localDisposables = [];
443
this._isDiposed = false;
444
445
this._localDisposables.push(this.onDidUpdateCommentThread(() => {
446
this.eventuallyUpdateCommentThread();
447
}));
448
449
this._localDisposables.push({
450
dispose: () => {
451
proxy.$deleteCommentThread(
452
_commentControllerHandle,
453
this.handle
454
);
455
}
456
});
457
458
const that = this;
459
this.value = {
460
get uri() { return that.uri; },
461
get range() { return that.range; },
462
set range(value: vscode.Range | undefined) { that.range = value; },
463
get comments() { return that.comments; },
464
set comments(value: vscode.Comment[]) { that.comments = value; },
465
get collapsibleState() { return that.collapsibleState; },
466
set collapsibleState(value: vscode.CommentThreadCollapsibleState) { that.collapsibleState = value; },
467
get canReply() { return that.canReply; },
468
set canReply(state: boolean | vscode.CommentAuthorInformation) { that.canReply = state; },
469
get contextValue() { return that.contextValue; },
470
set contextValue(value: string | undefined) { that.contextValue = value; },
471
get label() { return that.label; },
472
set label(value: string | undefined) { that.label = value; },
473
get state(): vscode.CommentThreadState | { resolved?: vscode.CommentThreadState; applicability?: vscode.CommentThreadApplicability } | undefined { return that.state; },
474
set state(value: vscode.CommentThreadState | { resolved?: vscode.CommentThreadState; applicability?: vscode.CommentThreadApplicability }) { that.state = value; },
475
reveal: (comment?: vscode.Comment | vscode.CommentThreadRevealOptions, options?: vscode.CommentThreadRevealOptions) => that.reveal(comment, options),
476
hide: () => that.hide(),
477
dispose: () => {
478
that.dispose();
479
}
480
};
481
}
482
483
private updateIsTemplate() {
484
if (this._isTemplate) {
485
this._isTemplate = false;
486
this.modifications.isTemplate = false;
487
}
488
}
489
490
@debounce(100)
491
eventuallyUpdateCommentThread(): void {
492
if (this._isDiposed) {
493
return;
494
}
495
this.updateIsTemplate();
496
497
if (!this._acceptInputDisposables.value) {
498
this._acceptInputDisposables.value = new DisposableStore();
499
}
500
501
const modified = (value: keyof CommentThreadModification): boolean =>
502
Object.prototype.hasOwnProperty.call(this.modifications, value);
503
504
const formattedModifications: CommentThreadChanges = {};
505
if (modified('range')) {
506
formattedModifications.range = extHostTypeConverter.Range.from(this._range);
507
}
508
if (modified('label')) {
509
formattedModifications.label = this.label;
510
}
511
if (modified('contextValue')) {
512
/*
513
* null -> cleared contextValue
514
* undefined -> no change
515
*/
516
formattedModifications.contextValue = this.contextValue ?? null;
517
}
518
if (modified('comments')) {
519
formattedModifications.comments =
520
this._comments.map(cmt => convertToDTOComment(this, cmt, this._commentsMap, this.extensionDescription));
521
}
522
if (modified('collapsibleState')) {
523
formattedModifications.collapseState = convertToCollapsibleState(this._collapseState);
524
}
525
if (modified('canReply')) {
526
formattedModifications.canReply = this.canReply;
527
}
528
if (modified('state')) {
529
formattedModifications.state = convertToState(this._state);
530
}
531
if (modified('applicability')) {
532
formattedModifications.applicability = convertToRelevance(this._state);
533
}
534
if (modified('isTemplate')) {
535
formattedModifications.isTemplate = this._isTemplate;
536
}
537
this.modifications = {};
538
539
proxy.$updateCommentThread(
540
this._commentControllerHandle,
541
this.handle,
542
this._id!,
543
this._uri,
544
formattedModifications
545
);
546
}
547
548
getCommentByUniqueId(uniqueId: number): vscode.Comment | undefined {
549
for (const key of this._commentsMap) {
550
const comment = key[0];
551
const id = key[1];
552
if (uniqueId === id) {
553
return comment;
554
}
555
}
556
557
return;
558
}
559
560
async reveal(commentOrOptions?: vscode.Comment | vscode.CommentThreadRevealOptions, options?: vscode.CommentThreadRevealOptions): Promise<void> {
561
checkProposedApiEnabled(this.extensionDescription, 'commentReveal');
562
let comment: vscode.Comment | undefined;
563
if (commentOrOptions && (commentOrOptions as vscode.Comment).body !== undefined) {
564
comment = commentOrOptions as vscode.Comment;
565
} else {
566
options = options ?? commentOrOptions as vscode.CommentThreadRevealOptions;
567
}
568
let commentToReveal = comment ? this._commentsMap.get(comment) : undefined;
569
commentToReveal ??= this._commentsMap.get(this._comments[0])!;
570
let preserveFocus: boolean = true;
571
let focusReply: boolean = false;
572
if (options?.focus === types.CommentThreadFocus.Reply) {
573
focusReply = true;
574
preserveFocus = false;
575
} else if (options?.focus === types.CommentThreadFocus.Comment) {
576
preserveFocus = false;
577
}
578
return proxy.$revealCommentThread(this._commentControllerHandle, this.handle, commentToReveal, { preserveFocus, focusReply });
579
}
580
581
async hide(): Promise<void> {
582
return proxy.$hideCommentThread(this._commentControllerHandle, this.handle);
583
}
584
585
dispose() {
586
this._isDiposed = true;
587
this._acceptInputDisposables.dispose();
588
this._localDisposables.forEach(disposable => disposable.dispose());
589
}
590
}
591
592
type ReactionHandler = (comment: vscode.Comment, reaction: vscode.CommentReaction) => Promise<void>;
593
594
class ExtHostCommentController {
595
get id(): string {
596
return this._id;
597
}
598
599
get label(): string {
600
return this._label;
601
}
602
603
public get handle(): number {
604
return this._handle;
605
}
606
607
private _threads: Map<number, ExtHostCommentThread> = new Map<number, ExtHostCommentThread>();
608
609
private _commentingRangeProvider?: vscode.CommentingRangeProvider;
610
get commentingRangeProvider(): vscode.CommentingRangeProvider | undefined {
611
return this._commentingRangeProvider;
612
}
613
614
set commentingRangeProvider(provider: vscode.CommentingRangeProvider | undefined) {
615
this._commentingRangeProvider = provider;
616
if (provider?.resourceHints) {
617
checkProposedApiEnabled(this._extension, 'commentingRangeHint');
618
}
619
proxy.$updateCommentingRanges(this.handle, provider?.resourceHints);
620
}
621
622
private _reactionHandler?: ReactionHandler;
623
624
get reactionHandler(): ReactionHandler | undefined {
625
return this._reactionHandler;
626
}
627
628
set reactionHandler(handler: ReactionHandler | undefined) {
629
this._reactionHandler = handler;
630
631
proxy.$updateCommentControllerFeatures(this.handle, { reactionHandler: !!handler });
632
}
633
634
private _options: languages.CommentOptions | undefined;
635
636
get options() {
637
return this._options;
638
}
639
640
set options(options: languages.CommentOptions | undefined) {
641
this._options = options;
642
643
proxy.$updateCommentControllerFeatures(this.handle, { options: this._options });
644
}
645
646
private _activeComment: vscode.Comment | undefined;
647
648
get activeComment(): vscode.Comment | undefined {
649
checkProposedApiEnabled(this._extension, 'activeComment');
650
return this._activeComment;
651
}
652
653
private _activeThread: ExtHostCommentThread | undefined;
654
655
get activeCommentThread(): vscode.CommentThread2 | undefined {
656
checkProposedApiEnabled(this._extension, 'activeComment');
657
return this._activeThread?.value;
658
}
659
660
private _localDisposables: types.Disposable[];
661
readonly value: vscode.CommentController;
662
663
constructor(
664
private _extension: IExtensionDescription,
665
private _handle: number,
666
private _id: string,
667
private _label: string
668
) {
669
proxy.$registerCommentController(this.handle, _id, _label, this._extension.identifier.value);
670
671
const that = this;
672
this.value = Object.freeze({
673
id: that.id,
674
label: that.label,
675
get options() { return that.options; },
676
set options(options: vscode.CommentOptions | undefined) { that.options = options; },
677
get commentingRangeProvider(): vscode.CommentingRangeProvider | undefined { return that.commentingRangeProvider; },
678
set commentingRangeProvider(commentingRangeProvider: vscode.CommentingRangeProvider | undefined) { that.commentingRangeProvider = commentingRangeProvider; },
679
get reactionHandler(): ReactionHandler | undefined { return that.reactionHandler; },
680
set reactionHandler(handler: ReactionHandler | undefined) { that.reactionHandler = handler; },
681
// get activeComment(): vscode.Comment | undefined { return that.activeComment; },
682
get activeCommentThread(): vscode.CommentThread2 | undefined { return that.activeCommentThread; },
683
createCommentThread(uri: vscode.Uri, range: vscode.Range | undefined, comments: vscode.Comment[]): vscode.CommentThread | vscode.CommentThread2 {
684
return that.createCommentThread(uri, range, comments).value;
685
},
686
dispose: () => { that.dispose(); },
687
}) as any; // TODO @alexr00 remove this cast when the proposed API is stable
688
689
this._localDisposables = [];
690
this._localDisposables.push({
691
dispose: () => {
692
proxy.$unregisterCommentController(this.handle);
693
}
694
});
695
}
696
697
createCommentThread(resource: vscode.Uri, range: vscode.Range | undefined, comments: vscode.Comment[]): ExtHostCommentThread {
698
const commentThread = new ExtHostCommentThread(this.id, this.handle, undefined, resource, range, comments, this._extension, false);
699
this._threads.set(commentThread.handle, commentThread);
700
return commentThread;
701
}
702
703
$setActiveComment(commentInfo: { commentThreadHandle: number; uniqueIdInThread?: number } | undefined) {
704
if (!commentInfo) {
705
this._activeComment = undefined;
706
this._activeThread = undefined;
707
return;
708
}
709
const thread = this._threads.get(commentInfo.commentThreadHandle);
710
if (thread) {
711
this._activeComment = commentInfo.uniqueIdInThread ? thread.getCommentByUniqueId(commentInfo.uniqueIdInThread) : undefined;
712
this._activeThread = thread;
713
}
714
}
715
716
$createCommentThreadTemplate(uriComponents: UriComponents, range: IRange | undefined, editorId?: string): ExtHostCommentThread {
717
const commentThread = new ExtHostCommentThread(this.id, this.handle, undefined, URI.revive(uriComponents), extHostTypeConverter.Range.to(range), [], this._extension, true, editorId);
718
commentThread.collapsibleState = languages.CommentThreadCollapsibleState.Expanded;
719
this._threads.set(commentThread.handle, commentThread);
720
return commentThread;
721
}
722
723
$updateCommentThreadTemplate(threadHandle: number, range: IRange): void {
724
const thread = this._threads.get(threadHandle);
725
if (thread) {
726
thread.range = extHostTypeConverter.Range.to(range);
727
}
728
}
729
730
$updateCommentThread(threadHandle: number, changes: CommentThreadChanges): void {
731
const thread = this._threads.get(threadHandle);
732
if (!thread) {
733
return;
734
}
735
736
const modified = (value: keyof CommentThreadChanges): boolean =>
737
Object.prototype.hasOwnProperty.call(changes, value);
738
739
if (modified('collapseState')) {
740
thread.collapsibleState = convertToCollapsibleState(changes.collapseState);
741
}
742
}
743
744
$deleteCommentThread(threadHandle: number): void {
745
const thread = this._threads.get(threadHandle);
746
747
thread?.dispose();
748
749
this._threads.delete(threadHandle);
750
}
751
752
getCommentThread(handle: number): ExtHostCommentThread | undefined {
753
return this._threads.get(handle);
754
}
755
756
dispose(): void {
757
this._threads.forEach(value => {
758
value.dispose();
759
});
760
761
this._localDisposables.forEach(disposable => disposable.dispose());
762
}
763
}
764
765
function convertToDTOComment(thread: ExtHostCommentThread, vscodeComment: vscode.Comment, commentsMap: Map<vscode.Comment, number>, extension: IExtensionDescription): CommentChanges {
766
let commentUniqueId = commentsMap.get(vscodeComment)!;
767
if (!commentUniqueId) {
768
commentUniqueId = ++thread.commentHandle;
769
commentsMap.set(vscodeComment, commentUniqueId);
770
}
771
772
if (vscodeComment.state !== undefined) {
773
checkProposedApiEnabled(extension, 'commentsDraftState');
774
}
775
776
if (vscodeComment.reactions?.some(reaction => reaction.reactors !== undefined)) {
777
checkProposedApiEnabled(extension, 'commentReactor');
778
}
779
780
return {
781
mode: vscodeComment.mode,
782
contextValue: vscodeComment.contextValue,
783
uniqueIdInThread: commentUniqueId,
784
body: (typeof vscodeComment.body === 'string') ? vscodeComment.body : extHostTypeConverter.MarkdownString.from(vscodeComment.body),
785
userName: vscodeComment.author.name,
786
userIconPath: vscodeComment.author.iconPath,
787
label: vscodeComment.label,
788
commentReactions: vscodeComment.reactions ? vscodeComment.reactions.map(reaction => convertToReaction(reaction)) : undefined,
789
state: vscodeComment.state,
790
timestamp: vscodeComment.timestamp?.toJSON()
791
};
792
}
793
794
function convertToReaction(reaction: vscode.CommentReaction): languages.CommentReaction {
795
return {
796
label: reaction.label,
797
iconPath: reaction.iconPath ? extHostTypeConverter.pathOrURIToURI(reaction.iconPath) : undefined,
798
count: reaction.count,
799
hasReacted: reaction.authorHasReacted,
800
reactors: ((reaction.reactors && (reaction.reactors.length > 0) && (typeof reaction.reactors[0] !== 'string')) ? (reaction.reactors as languages.CommentAuthorInformation[]).map(reactor => reactor.name) : reaction.reactors) as string[]
801
};
802
}
803
804
function convertFromReaction(reaction: languages.CommentReaction): vscode.CommentReaction {
805
return {
806
label: reaction.label || '',
807
count: reaction.count || 0,
808
iconPath: reaction.iconPath ? URI.revive(reaction.iconPath) : '',
809
authorHasReacted: reaction.hasReacted || false,
810
reactors: reaction.reactors?.map(reactor => ({ name: reactor }))
811
};
812
}
813
814
function convertToCollapsibleState(kind: vscode.CommentThreadCollapsibleState | undefined): languages.CommentThreadCollapsibleState {
815
if (kind !== undefined) {
816
switch (kind) {
817
case types.CommentThreadCollapsibleState.Expanded:
818
return languages.CommentThreadCollapsibleState.Expanded;
819
case types.CommentThreadCollapsibleState.Collapsed:
820
return languages.CommentThreadCollapsibleState.Collapsed;
821
}
822
}
823
return languages.CommentThreadCollapsibleState.Collapsed;
824
}
825
826
function convertToState(kind: vscode.CommentThreadState | { resolved?: vscode.CommentThreadState; applicability?: vscode.CommentThreadApplicability } | undefined): languages.CommentThreadState {
827
let resolvedKind: vscode.CommentThreadState | undefined;
828
if (typeof kind === 'object') {
829
resolvedKind = kind.resolved;
830
} else {
831
resolvedKind = kind;
832
}
833
834
if (resolvedKind !== undefined) {
835
switch (resolvedKind) {
836
case types.CommentThreadState.Unresolved:
837
return languages.CommentThreadState.Unresolved;
838
case types.CommentThreadState.Resolved:
839
return languages.CommentThreadState.Resolved;
840
}
841
}
842
return languages.CommentThreadState.Unresolved;
843
}
844
845
function convertToRelevance(kind: vscode.CommentThreadState | { resolved?: vscode.CommentThreadState; applicability?: vscode.CommentThreadApplicability } | undefined): languages.CommentThreadApplicability {
846
let applicabilityKind: vscode.CommentThreadApplicability | undefined = undefined;
847
if (typeof kind === 'object') {
848
applicabilityKind = kind.applicability;
849
}
850
851
if (applicabilityKind !== undefined) {
852
switch (applicabilityKind) {
853
case types.CommentThreadApplicability.Current:
854
return languages.CommentThreadApplicability.Current;
855
case types.CommentThreadApplicability.Outdated:
856
return languages.CommentThreadApplicability.Outdated;
857
}
858
}
859
return languages.CommentThreadApplicability.Current;
860
}
861
862
return new ExtHostCommentsImpl();
863
}
864
865