Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/comments/browser/commentService.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 { CommentThreadChangedEvent, CommentInfo, Comment, CommentReaction, CommentingRanges, CommentThread, CommentOptions, PendingCommentThread, CommentingRangeResourceHint } from '../../../../editor/common/languages.js';
7
import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
8
import { Event, Emitter } from '../../../../base/common/event.js';
9
import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js';
10
import { URI, UriComponents } from '../../../../base/common/uri.js';
11
import { Range, IRange } from '../../../../editor/common/core/range.js';
12
import { CancellationToken } from '../../../../base/common/cancellation.js';
13
import { ICommentThreadChangedEvent } from '../common/commentModel.js';
14
import { CommentMenus } from './commentMenus.js';
15
import { ICellRange } from '../../notebook/common/notebookRange.js';
16
import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js';
17
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
18
import { COMMENTS_SECTION, ICommentsConfiguration } from '../common/commentsConfiguration.js';
19
import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
20
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
21
import { CommentContextKeys } from '../common/commentContextKeys.js';
22
import { ILogService } from '../../../../platform/log/common/log.js';
23
import { CommentsModel, ICommentsModel } from './commentsModel.js';
24
import { IModelService } from '../../../../editor/common/services/model.js';
25
import { Schemas } from '../../../../base/common/network.js';
26
27
export const ICommentService = createDecorator<ICommentService>('commentService');
28
29
interface IResourceCommentThreadEvent {
30
resource: URI;
31
commentInfos: ICommentInfo[];
32
}
33
34
export interface ICommentInfo<T = IRange> extends CommentInfo<T> {
35
uniqueOwner: string;
36
label?: string;
37
}
38
39
export interface INotebookCommentInfo {
40
extensionId?: string;
41
threads: CommentThread<ICellRange>[];
42
uniqueOwner: string;
43
label?: string;
44
}
45
46
export interface IWorkspaceCommentThreadsEvent {
47
ownerId: string;
48
ownerLabel: string;
49
commentThreads: CommentThread[];
50
}
51
52
export interface INotebookCommentThreadChangedEvent extends CommentThreadChangedEvent<ICellRange> {
53
uniqueOwner: string;
54
}
55
56
export interface ICommentController {
57
id: string;
58
label: string;
59
features: {
60
reactionGroup?: CommentReaction[];
61
reactionHandler?: boolean;
62
options?: CommentOptions;
63
};
64
options?: CommentOptions;
65
contextValue?: string;
66
owner: string;
67
activeComment: { thread: CommentThread; comment?: Comment } | undefined;
68
createCommentThreadTemplate(resource: UriComponents, range: IRange | undefined, editorId?: string): Promise<void>;
69
updateCommentThreadTemplate(threadHandle: number, range: IRange): Promise<void>;
70
deleteCommentThreadMain(commentThreadId: string): void;
71
toggleReaction(uri: URI, thread: CommentThread, comment: Comment, reaction: CommentReaction, token: CancellationToken): Promise<void>;
72
getDocumentComments(resource: URI, token: CancellationToken): Promise<ICommentInfo<IRange>>;
73
getNotebookComments(resource: URI, token: CancellationToken): Promise<INotebookCommentInfo>;
74
setActiveCommentAndThread(commentInfo: { thread: CommentThread; comment?: Comment } | undefined): Promise<void>;
75
}
76
77
export interface IContinueOnCommentProvider {
78
provideContinueOnComments(): PendingCommentThread[];
79
}
80
81
export interface ICommentService {
82
readonly _serviceBrand: undefined;
83
readonly onDidSetResourceCommentInfos: Event<IResourceCommentThreadEvent>;
84
readonly onDidSetAllCommentThreads: Event<IWorkspaceCommentThreadsEvent>;
85
readonly onDidUpdateCommentThreads: Event<ICommentThreadChangedEvent>;
86
readonly onDidUpdateNotebookCommentThreads: Event<INotebookCommentThreadChangedEvent>;
87
readonly onDidChangeActiveEditingCommentThread: Event<CommentThread | null>;
88
readonly onDidChangeCurrentCommentThread: Event<CommentThread | undefined>;
89
readonly onDidUpdateCommentingRanges: Event<{ uniqueOwner: string }>;
90
readonly onDidChangeActiveCommentingRange: Event<{ range: Range; commentingRangesInfo: CommentingRanges }>;
91
readonly onDidSetDataProvider: Event<void>;
92
readonly onDidDeleteDataProvider: Event<string | undefined>;
93
readonly onDidChangeCommentingEnabled: Event<boolean>;
94
readonly onResourceHasCommentingRanges: Event<void>;
95
readonly isCommentingEnabled: boolean;
96
readonly commentsModel: ICommentsModel;
97
readonly lastActiveCommentcontroller: ICommentController | undefined;
98
setDocumentComments(resource: URI, commentInfos: ICommentInfo[]): void;
99
setWorkspaceComments(uniqueOwner: string, commentsByResource: CommentThread<IRange | ICellRange>[]): void;
100
removeWorkspaceComments(uniqueOwner: string): void;
101
registerCommentController(uniqueOwner: string, commentControl: ICommentController): void;
102
unregisterCommentController(uniqueOwner?: string): void;
103
getCommentController(uniqueOwner: string): ICommentController | undefined;
104
createCommentThreadTemplate(uniqueOwner: string, resource: URI, range: Range | undefined, editorId?: string): Promise<void>;
105
updateCommentThreadTemplate(uniqueOwner: string, threadHandle: number, range: Range): Promise<void>;
106
getCommentMenus(uniqueOwner: string): CommentMenus;
107
updateComments(ownerId: string, event: CommentThreadChangedEvent<IRange>): void;
108
updateNotebookComments(ownerId: string, event: CommentThreadChangedEvent<ICellRange>): void;
109
disposeCommentThread(ownerId: string, threadId: string): void;
110
getDocumentComments(resource: URI): Promise<(ICommentInfo | null)[]>;
111
getNotebookComments(resource: URI): Promise<(INotebookCommentInfo | null)[]>;
112
updateCommentingRanges(ownerId: string, resourceHints?: CommentingRangeResourceHint): void;
113
hasReactionHandler(uniqueOwner: string): boolean;
114
toggleReaction(uniqueOwner: string, resource: URI, thread: CommentThread<IRange | ICellRange>, comment: Comment, reaction: CommentReaction): Promise<void>;
115
setActiveEditingCommentThread(commentThread: CommentThread<IRange | ICellRange> | null): void;
116
setCurrentCommentThread(commentThread: CommentThread<IRange | ICellRange> | undefined): void;
117
setActiveCommentAndThread(uniqueOwner: string, commentInfo: { thread: CommentThread<IRange | ICellRange>; comment?: Comment } | undefined): Promise<void>;
118
enableCommenting(enable: boolean): void;
119
registerContinueOnCommentProvider(provider: IContinueOnCommentProvider): IDisposable;
120
removeContinueOnComment(pendingComment: { range: IRange | undefined; uri: URI; uniqueOwner: string; isReply?: boolean }): PendingCommentThread | undefined;
121
resourceHasCommentingRanges(resource: URI): boolean;
122
}
123
124
const CONTINUE_ON_COMMENTS = 'comments.continueOnComments';
125
126
export class CommentService extends Disposable implements ICommentService {
127
declare readonly _serviceBrand: undefined;
128
129
private readonly _onDidSetDataProvider: Emitter<void> = this._register(new Emitter<void>());
130
readonly onDidSetDataProvider: Event<void> = this._onDidSetDataProvider.event;
131
132
private readonly _onDidDeleteDataProvider: Emitter<string | undefined> = this._register(new Emitter<string | undefined>());
133
readonly onDidDeleteDataProvider: Event<string | undefined> = this._onDidDeleteDataProvider.event;
134
135
private readonly _onDidSetResourceCommentInfos: Emitter<IResourceCommentThreadEvent> = this._register(new Emitter<IResourceCommentThreadEvent>());
136
readonly onDidSetResourceCommentInfos: Event<IResourceCommentThreadEvent> = this._onDidSetResourceCommentInfos.event;
137
138
private readonly _onDidSetAllCommentThreads: Emitter<IWorkspaceCommentThreadsEvent> = this._register(new Emitter<IWorkspaceCommentThreadsEvent>());
139
readonly onDidSetAllCommentThreads: Event<IWorkspaceCommentThreadsEvent> = this._onDidSetAllCommentThreads.event;
140
141
private readonly _onDidUpdateCommentThreads: Emitter<ICommentThreadChangedEvent> = this._register(new Emitter<ICommentThreadChangedEvent>());
142
readonly onDidUpdateCommentThreads: Event<ICommentThreadChangedEvent> = this._onDidUpdateCommentThreads.event;
143
144
private readonly _onDidUpdateNotebookCommentThreads: Emitter<INotebookCommentThreadChangedEvent> = this._register(new Emitter<INotebookCommentThreadChangedEvent>());
145
readonly onDidUpdateNotebookCommentThreads: Event<INotebookCommentThreadChangedEvent> = this._onDidUpdateNotebookCommentThreads.event;
146
147
private readonly _onDidUpdateCommentingRanges: Emitter<{ uniqueOwner: string }> = this._register(new Emitter<{ uniqueOwner: string }>());
148
readonly onDidUpdateCommentingRanges: Event<{ uniqueOwner: string }> = this._onDidUpdateCommentingRanges.event;
149
150
private readonly _onDidChangeActiveEditingCommentThread = this._register(new Emitter<CommentThread | null>());
151
readonly onDidChangeActiveEditingCommentThread = this._onDidChangeActiveEditingCommentThread.event;
152
153
private readonly _onDidChangeCurrentCommentThread = this._register(new Emitter<CommentThread | undefined>());
154
readonly onDidChangeCurrentCommentThread = this._onDidChangeCurrentCommentThread.event;
155
156
private readonly _onDidChangeCommentingEnabled = this._register(new Emitter<boolean>());
157
readonly onDidChangeCommentingEnabled = this._onDidChangeCommentingEnabled.event;
158
159
private readonly _onResourceHasCommentingRanges = this._register(new Emitter<void>());
160
readonly onResourceHasCommentingRanges = this._onResourceHasCommentingRanges.event;
161
162
private readonly _onDidChangeActiveCommentingRange: Emitter<{
163
range: Range; commentingRangesInfo:
164
CommentingRanges;
165
}> = this._register(new Emitter<{
166
range: Range; commentingRangesInfo:
167
CommentingRanges;
168
}>());
169
readonly onDidChangeActiveCommentingRange: Event<{ range: Range; commentingRangesInfo: CommentingRanges }> = this._onDidChangeActiveCommentingRange.event;
170
171
private _commentControls = new Map<string, ICommentController>();
172
private _commentMenus = new Map<string, CommentMenus>();
173
private _isCommentingEnabled: boolean = true;
174
private _workspaceHasCommenting: IContextKey<boolean>;
175
private _commentingEnabled: IContextKey<boolean>;
176
177
private _continueOnComments = new Map<string, PendingCommentThread[]>(); // uniqueOwner -> PendingCommentThread[]
178
private _continueOnCommentProviders = new Set<IContinueOnCommentProvider>();
179
180
private readonly _commentsModel: CommentsModel = this._register(new CommentsModel());
181
public readonly commentsModel: ICommentsModel = this._commentsModel;
182
183
private _commentingRangeResources = new Set<string>(); // URIs
184
private _commentingRangeResourceHintSchemes = new Set<string>(); // schemes
185
186
constructor(
187
@IInstantiationService protected readonly instantiationService: IInstantiationService,
188
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
189
@IConfigurationService private readonly configurationService: IConfigurationService,
190
@IContextKeyService contextKeyService: IContextKeyService,
191
@IStorageService private readonly storageService: IStorageService,
192
@ILogService private readonly logService: ILogService,
193
@IModelService private readonly modelService: IModelService
194
) {
195
super();
196
this._handleConfiguration();
197
this._handleZenMode();
198
this._workspaceHasCommenting = CommentContextKeys.WorkspaceHasCommenting.bindTo(contextKeyService);
199
this._commentingEnabled = CommentContextKeys.commentingEnabled.bindTo(contextKeyService);
200
const storageListener = this._register(new DisposableStore());
201
202
const storageEvent = Event.debounce(this.storageService.onDidChangeValue(StorageScope.WORKSPACE, CONTINUE_ON_COMMENTS, storageListener), (last, event) => last?.external ? last : event, 500);
203
storageListener.add(storageEvent(v => {
204
if (!v.external) {
205
return;
206
}
207
const commentsToRestore: PendingCommentThread[] | undefined = this.storageService.getObject(CONTINUE_ON_COMMENTS, StorageScope.WORKSPACE);
208
if (!commentsToRestore) {
209
return;
210
}
211
this.logService.debug(`Comments: URIs of continue on comments from storage ${commentsToRestore.map(thread => thread.uri.toString()).join(', ')}.`);
212
const changedOwners = this._addContinueOnComments(commentsToRestore, this._continueOnComments);
213
for (const uniqueOwner of changedOwners) {
214
const control = this._commentControls.get(uniqueOwner);
215
if (!control) {
216
continue;
217
}
218
const evt: ICommentThreadChangedEvent = {
219
uniqueOwner: uniqueOwner,
220
owner: control.owner,
221
ownerLabel: control.label,
222
pending: this._continueOnComments.get(uniqueOwner) || [],
223
added: [],
224
removed: [],
225
changed: []
226
};
227
this.updateModelThreads(evt);
228
}
229
}));
230
this._register(storageService.onWillSaveState(() => {
231
const map: Map<string, PendingCommentThread[]> = new Map();
232
for (const provider of this._continueOnCommentProviders) {
233
const pendingComments = provider.provideContinueOnComments();
234
this._addContinueOnComments(pendingComments, map);
235
}
236
this._saveContinueOnComments(map);
237
}));
238
239
this._register(this.modelService.onModelAdded(model => {
240
// Excluded schemes
241
if ((model.uri.scheme === Schemas.vscodeSourceControl)) {
242
return;
243
}
244
// Allows comment providers to cause their commenting ranges to be prefetched by opening text documents in the background.
245
if (!this._commentingRangeResources.has(model.uri.toString())) {
246
this.getDocumentComments(model.uri);
247
}
248
}));
249
}
250
251
private _updateResourcesWithCommentingRanges(resource: URI, commentInfos: (ICommentInfo | null)[]) {
252
let addedResources = false;
253
for (const comments of commentInfos) {
254
if (comments && (comments.commentingRanges.ranges.length > 0 || comments.threads.length > 0)) {
255
this._commentingRangeResources.add(resource.toString());
256
addedResources = true;
257
}
258
}
259
if (addedResources) {
260
this._onResourceHasCommentingRanges.fire();
261
}
262
}
263
264
private _handleConfiguration() {
265
this._isCommentingEnabled = this._defaultCommentingEnablement;
266
this._register(this.configurationService.onDidChangeConfiguration(e => {
267
if (e.affectsConfiguration('comments.visible')) {
268
this.enableCommenting(this._defaultCommentingEnablement);
269
}
270
}));
271
}
272
273
private _handleZenMode() {
274
let preZenModeValue: boolean = this._isCommentingEnabled;
275
this._register(this.layoutService.onDidChangeZenMode(e => {
276
if (e) {
277
preZenModeValue = this._isCommentingEnabled;
278
this.enableCommenting(false);
279
} else {
280
this.enableCommenting(preZenModeValue);
281
}
282
}));
283
}
284
285
private get _defaultCommentingEnablement(): boolean {
286
return !!this.configurationService.getValue<ICommentsConfiguration | undefined>(COMMENTS_SECTION)?.visible;
287
}
288
289
get isCommentingEnabled(): boolean {
290
return this._isCommentingEnabled;
291
}
292
293
enableCommenting(enable: boolean): void {
294
if (enable !== this._isCommentingEnabled) {
295
this._isCommentingEnabled = enable;
296
this._commentingEnabled.set(enable);
297
this._onDidChangeCommentingEnabled.fire(enable);
298
}
299
}
300
301
/**
302
* The current comment thread is the thread that has focus or is being hovered.
303
* @param commentThread
304
*/
305
setCurrentCommentThread(commentThread: CommentThread | undefined) {
306
this._onDidChangeCurrentCommentThread.fire(commentThread);
307
}
308
309
/**
310
* The active comment thread is the thread that is currently being edited.
311
* @param commentThread
312
*/
313
setActiveEditingCommentThread(commentThread: CommentThread | null) {
314
this._onDidChangeActiveEditingCommentThread.fire(commentThread);
315
}
316
317
get lastActiveCommentcontroller() {
318
return this._lastActiveCommentController;
319
}
320
321
private _lastActiveCommentController: ICommentController | undefined;
322
async setActiveCommentAndThread(uniqueOwner: string, commentInfo: { thread: CommentThread<IRange>; comment?: Comment } | undefined) {
323
const commentController = this._commentControls.get(uniqueOwner);
324
325
if (!commentController) {
326
return;
327
}
328
329
if (commentController !== this._lastActiveCommentController) {
330
await this._lastActiveCommentController?.setActiveCommentAndThread(undefined);
331
}
332
this._lastActiveCommentController = commentController;
333
return commentController.setActiveCommentAndThread(commentInfo);
334
}
335
336
setDocumentComments(resource: URI, commentInfos: ICommentInfo[]): void {
337
this._onDidSetResourceCommentInfos.fire({ resource, commentInfos });
338
}
339
340
private setModelThreads(ownerId: string, owner: string, ownerLabel: string, commentThreads: CommentThread<IRange>[]) {
341
this._commentsModel.setCommentThreads(ownerId, owner, ownerLabel, commentThreads);
342
this._onDidSetAllCommentThreads.fire({ ownerId, ownerLabel, commentThreads });
343
}
344
345
private updateModelThreads(event: ICommentThreadChangedEvent) {
346
this._commentsModel.updateCommentThreads(event);
347
this._onDidUpdateCommentThreads.fire(event);
348
}
349
350
setWorkspaceComments(uniqueOwner: string, commentsByResource: CommentThread[]): void {
351
352
if (commentsByResource.length) {
353
this._workspaceHasCommenting.set(true);
354
}
355
const control = this._commentControls.get(uniqueOwner);
356
if (control) {
357
this.setModelThreads(uniqueOwner, control.owner, control.label, commentsByResource);
358
}
359
}
360
361
removeWorkspaceComments(uniqueOwner: string): void {
362
const control = this._commentControls.get(uniqueOwner);
363
if (control) {
364
this.setModelThreads(uniqueOwner, control.owner, control.label, []);
365
}
366
}
367
368
registerCommentController(uniqueOwner: string, commentControl: ICommentController): void {
369
this._commentControls.set(uniqueOwner, commentControl);
370
this._onDidSetDataProvider.fire();
371
}
372
373
unregisterCommentController(uniqueOwner?: string): void {
374
if (uniqueOwner) {
375
this._commentControls.delete(uniqueOwner);
376
} else {
377
this._commentControls.clear();
378
}
379
this._commentsModel.deleteCommentsByOwner(uniqueOwner);
380
this._onDidDeleteDataProvider.fire(uniqueOwner);
381
}
382
383
getCommentController(uniqueOwner: string): ICommentController | undefined {
384
return this._commentControls.get(uniqueOwner);
385
}
386
387
async createCommentThreadTemplate(uniqueOwner: string, resource: URI, range: Range | undefined, editorId?: string): Promise<void> {
388
const commentController = this._commentControls.get(uniqueOwner);
389
390
if (!commentController) {
391
return;
392
}
393
394
return commentController.createCommentThreadTemplate(resource, range, editorId);
395
}
396
397
async updateCommentThreadTemplate(uniqueOwner: string, threadHandle: number, range: Range) {
398
const commentController = this._commentControls.get(uniqueOwner);
399
400
if (!commentController) {
401
return;
402
}
403
404
await commentController.updateCommentThreadTemplate(threadHandle, range);
405
}
406
407
disposeCommentThread(uniqueOwner: string, threadId: string) {
408
const controller = this.getCommentController(uniqueOwner);
409
controller?.deleteCommentThreadMain(threadId);
410
}
411
412
getCommentMenus(uniqueOwner: string): CommentMenus {
413
if (this._commentMenus.get(uniqueOwner)) {
414
return this._commentMenus.get(uniqueOwner)!;
415
}
416
417
const menu = this.instantiationService.createInstance(CommentMenus);
418
this._commentMenus.set(uniqueOwner, menu);
419
return menu;
420
}
421
422
updateComments(ownerId: string, event: CommentThreadChangedEvent<IRange>): void {
423
const control = this._commentControls.get(ownerId);
424
if (control) {
425
const evt: ICommentThreadChangedEvent = Object.assign({}, event, { uniqueOwner: ownerId, ownerLabel: control.label, owner: control.owner });
426
this.updateModelThreads(evt);
427
}
428
}
429
430
updateNotebookComments(ownerId: string, event: CommentThreadChangedEvent<ICellRange>): void {
431
const evt: INotebookCommentThreadChangedEvent = Object.assign({}, event, { uniqueOwner: ownerId });
432
this._onDidUpdateNotebookCommentThreads.fire(evt);
433
}
434
435
updateCommentingRanges(ownerId: string, resourceHints?: CommentingRangeResourceHint) {
436
if (resourceHints?.schemes && resourceHints.schemes.length > 0) {
437
for (const scheme of resourceHints.schemes) {
438
this._commentingRangeResourceHintSchemes.add(scheme);
439
}
440
}
441
this._workspaceHasCommenting.set(true);
442
this._onDidUpdateCommentingRanges.fire({ uniqueOwner: ownerId });
443
}
444
445
async toggleReaction(uniqueOwner: string, resource: URI, thread: CommentThread, comment: Comment, reaction: CommentReaction): Promise<void> {
446
const commentController = this._commentControls.get(uniqueOwner);
447
448
if (commentController) {
449
return commentController.toggleReaction(resource, thread, comment, reaction, CancellationToken.None);
450
} else {
451
throw new Error('Not supported');
452
}
453
}
454
455
hasReactionHandler(uniqueOwner: string): boolean {
456
const commentProvider = this._commentControls.get(uniqueOwner);
457
458
if (commentProvider) {
459
return !!commentProvider.features.reactionHandler;
460
}
461
462
return false;
463
}
464
465
async getDocumentComments(resource: URI): Promise<(ICommentInfo | null)[]> {
466
const commentControlResult: Promise<ICommentInfo | null>[] = [];
467
468
for (const control of this._commentControls.values()) {
469
commentControlResult.push(control.getDocumentComments(resource, CancellationToken.None)
470
.then(documentComments => {
471
// Check that there aren't any continue on comments in the provided comments
472
// This can happen because continue on comments are stored separately from local un-submitted comments.
473
for (const documentCommentThread of documentComments.threads) {
474
if (documentCommentThread.comments?.length === 0 && documentCommentThread.range) {
475
this.removeContinueOnComment({ range: documentCommentThread.range, uri: resource, uniqueOwner: documentComments.uniqueOwner });
476
}
477
}
478
const pendingComments = this._continueOnComments.get(documentComments.uniqueOwner);
479
documentComments.pendingCommentThreads = pendingComments?.filter(pendingComment => pendingComment.uri.toString() === resource.toString());
480
return documentComments;
481
})
482
.catch(_ => {
483
return null;
484
}));
485
}
486
487
const commentInfos = await Promise.all(commentControlResult);
488
this._updateResourcesWithCommentingRanges(resource, commentInfos);
489
return commentInfos;
490
}
491
492
async getNotebookComments(resource: URI): Promise<(INotebookCommentInfo | null)[]> {
493
const commentControlResult: Promise<INotebookCommentInfo | null>[] = [];
494
495
this._commentControls.forEach(control => {
496
commentControlResult.push(control.getNotebookComments(resource, CancellationToken.None)
497
.catch(_ => {
498
return null;
499
}));
500
});
501
502
return Promise.all(commentControlResult);
503
}
504
505
registerContinueOnCommentProvider(provider: IContinueOnCommentProvider): IDisposable {
506
this._continueOnCommentProviders.add(provider);
507
return {
508
dispose: () => {
509
this._continueOnCommentProviders.delete(provider);
510
}
511
};
512
}
513
514
private _saveContinueOnComments(map: Map<string, PendingCommentThread[]>) {
515
const commentsToSave: PendingCommentThread[] = [];
516
for (const pendingComments of map.values()) {
517
commentsToSave.push(...pendingComments);
518
}
519
this.logService.debug(`Comments: URIs of continue on comments to add to storage ${commentsToSave.map(thread => thread.uri.toString()).join(', ')}.`);
520
this.storageService.store(CONTINUE_ON_COMMENTS, commentsToSave, StorageScope.WORKSPACE, StorageTarget.USER);
521
}
522
523
removeContinueOnComment(pendingComment: { range: IRange; uri: URI; uniqueOwner: string; isReply?: boolean }): PendingCommentThread | undefined {
524
const pendingComments = this._continueOnComments.get(pendingComment.uniqueOwner);
525
if (pendingComments) {
526
const commentIndex = pendingComments.findIndex(comment => comment.uri.toString() === pendingComment.uri.toString() && Range.equalsRange(comment.range, pendingComment.range) && (pendingComment.isReply === undefined || comment.isReply === pendingComment.isReply));
527
if (commentIndex > -1) {
528
return pendingComments.splice(commentIndex, 1)[0];
529
}
530
}
531
return undefined;
532
}
533
534
private _addContinueOnComments(pendingComments: PendingCommentThread[], map: Map<string, PendingCommentThread[]>): Set<string> {
535
const changedOwners = new Set<string>();
536
for (const pendingComment of pendingComments) {
537
if (!map.has(pendingComment.uniqueOwner)) {
538
map.set(pendingComment.uniqueOwner, [pendingComment]);
539
changedOwners.add(pendingComment.uniqueOwner);
540
} else {
541
const commentsForOwner = map.get(pendingComment.uniqueOwner)!;
542
if (commentsForOwner.every(comment => (comment.uri.toString() !== pendingComment.uri.toString()) || !Range.equalsRange(comment.range, pendingComment.range))) {
543
commentsForOwner.push(pendingComment);
544
changedOwners.add(pendingComment.uniqueOwner);
545
}
546
}
547
}
548
return changedOwners;
549
}
550
551
resourceHasCommentingRanges(resource: URI): boolean {
552
return this._commentingRangeResourceHintSchemes.has(resource.scheme) || this._commentingRangeResources.has(resource.toString());
553
}
554
}
555
556