@opensumi/ide-comments 3.0.5-next-1717640023.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,3 @@
1
- import debounce from 'lodash/debounce';
2
1
  import flattenDeep from 'lodash/flattenDeep';
3
2
  import groupBy from 'lodash/groupBy';
4
3
 
@@ -36,6 +35,7 @@ import * as textModel from '@opensumi/monaco-editor-core/esm/vs/editor/common/mo
36
35
 
37
36
  import {
38
37
  CommentPanelId,
38
+ CommentThreadCollapsibleState,
39
39
  ICommentRangeProvider,
40
40
  ICommentsFeatureRegistry,
41
41
  ICommentsService,
@@ -88,6 +88,9 @@ export class CommentsService extends Disposable implements ICommentsService {
88
88
 
89
89
  private threadsCommentChangeEmitter = new Emitter<ICommentsThread>();
90
90
 
91
+ private commentRangeProviderChangeEmitter = new Emitter<void>();
92
+ private onDidChangeCurrentCommentThreadEmitter = new Emitter<ICommentsThread | undefined>();
93
+
91
94
  private threadsCreatedEmitter = new Emitter<ICommentsThread>();
92
95
 
93
96
  private rangeProviderMap = new Map<string, ICommentRangeProvider>();
@@ -96,8 +99,10 @@ export class CommentsService extends Disposable implements ICommentsService {
96
99
 
97
100
  private providerDecorationCache = new LRUCache<string, Deferred<IRange[]>>(10000);
98
101
 
102
+ private commentRangeDecorationMap: Map<string, string[]> = new Map();
103
+
99
104
  // 默认在 file 协议和 git 协议中显示评论数据
100
- private shouldShowCommentsSchemes = new Set(['file', 'git']);
105
+ private shouldShowCommentsSchemes = new Set(['file', 'git', 'diff']);
101
106
 
102
107
  private decorationProviderDisposer = Disposable.NULL;
103
108
 
@@ -132,6 +137,18 @@ export class CommentsService extends Disposable implements ICommentsService {
132
137
  return this.threadsCreatedEmitter.event;
133
138
  }
134
139
 
140
+ get onCommentRangeProviderChange() {
141
+ return this.commentRangeProviderChangeEmitter.event;
142
+ }
143
+
144
+ get onDidChangeCurrentCommentThread() {
145
+ return this.onDidChangeCurrentCommentThreadEmitter.event;
146
+ }
147
+
148
+ public setCurrentCommentThread(thread: ICommentsThread) {
149
+ this.onDidChangeCurrentCommentThreadEmitter.fire(thread);
150
+ }
151
+
135
152
  /**
136
153
  * -------------------------------- IMPORTANT --------------------------------
137
154
  * 需要注意区分 model.IModelDecorationOptions 与 monaco.editor.IModelDecorationOptions 两个类型
@@ -146,12 +163,55 @@ export class CommentsService extends Disposable implements ICommentsService {
146
163
  const avatar =
147
164
  thread.comments.length === 0 ? this.currentAuthorAvatar : thread.comments[0].author.iconPath?.toString();
148
165
  const icon = avatar
149
- ? this.iconService.fromIcon('', avatar, IconType.Background)
150
- : this.iconService.fromString('$(comment-unresolved)');
166
+ ? `${this.iconService.fromIcon('', avatar, IconType.Background)} avatar-icon`
167
+ : this.iconService.fromString('$(comment)');
151
168
  const decorationOptions: model.IModelDecorationOptions = {
152
169
  description: 'comments-thread-decoration',
153
- // 创建评论显示在 glyph margin 处
154
- glyphMarginClassName: ['comments-decoration', 'comments-thread', icon].join(' '),
170
+ glyphMarginClassName: avatar
171
+ ? ['comment-thread', icon].join(' ')
172
+ : ['comment-range', 'comment-thread', icon].join(' '),
173
+ };
174
+ return textModel.ModelDecorationOptions.createDynamic(decorationOptions);
175
+ }
176
+
177
+ private currentThreadCollapseStateListener: IDisposable | undefined;
178
+ private activeThreadDecorationIds: string[] = [];
179
+
180
+ private updateActiveThreadDecoration(thread?: ICommentsThread) {
181
+ const editor = this.getCurrentEditor(thread?.uri);
182
+ if (!editor) {
183
+ return;
184
+ }
185
+ this.currentThreadCollapseStateListener?.dispose();
186
+ const newDecoration: {
187
+ range: IRange;
188
+ options: model.IModelDecorationOptions;
189
+ }[] = [];
190
+
191
+ if (thread) {
192
+ const range = thread.range;
193
+ if (!thread.isCollapsed) {
194
+ this.currentThreadCollapseStateListener = thread.onDidChangeCollapsibleState((state) => {
195
+ if (state === CommentThreadCollapsibleState.Collapsed) {
196
+ this.updateActiveThreadDecoration(undefined);
197
+ }
198
+ });
199
+ newDecoration.push({
200
+ range,
201
+ options: this.createThreadRangeActiveDecoration(),
202
+ });
203
+ }
204
+ }
205
+ this.activeThreadDecorationIds = editor.monacoEditor.deltaDecorations(
206
+ this.activeThreadDecorationIds,
207
+ newDecoration,
208
+ );
209
+ }
210
+
211
+ private createDottedRangeDecoration(): model.IModelDecorationOptions {
212
+ const decorationOptions: model.IModelDecorationOptions = {
213
+ description: 'comments-multiline-hover-decoration',
214
+ linesDecorationsClassName: ['comment-range', 'multiline-add'].join(' '),
155
215
  };
156
216
  return textModel.ModelDecorationOptions.createDynamic(decorationOptions);
157
217
  }
@@ -159,7 +219,39 @@ export class CommentsService extends Disposable implements ICommentsService {
159
219
  private createHoverDecoration(): model.IModelDecorationOptions {
160
220
  const decorationOptions: model.IModelDecorationOptions = {
161
221
  description: 'comments-hover-decoration',
162
- linesDecorationsClassName: ['comments-decoration', 'comments-add', getIcon('add-comments')].join(' '),
222
+ linesDecorationsClassName: ['comment-range', 'line-hover', 'comment-add'].join(' '),
223
+ };
224
+ return textModel.ModelDecorationOptions.createDynamic(decorationOptions);
225
+ }
226
+
227
+ private createThreadRangeActiveDecoration(): model.IModelDecorationOptions {
228
+ const activeDecorationOptions: model.IModelDecorationOptions = {
229
+ description: 'comments-thread-range-active-decoration',
230
+ isWholeLine: false,
231
+ zIndex: 20,
232
+ className: 'comment-thread-range-current',
233
+ shouldFillLineOnLineBreak: true,
234
+ };
235
+
236
+ return textModel.ModelDecorationOptions.createDynamic(activeDecorationOptions);
237
+ }
238
+
239
+ private createThreadRangeDecoration(): model.IModelDecorationOptions {
240
+ const activeDecorationOptions: model.IModelDecorationOptions = {
241
+ description: 'comments-thread-range-decoration',
242
+ isWholeLine: false,
243
+ zIndex: 20,
244
+ className: 'comment-thread-range',
245
+ shouldFillLineOnLineBreak: true,
246
+ };
247
+
248
+ return textModel.ModelDecorationOptions.createDynamic(activeDecorationOptions);
249
+ }
250
+
251
+ private createCommentRangeDecoration(): model.IModelDecorationOptions {
252
+ const decorationOptions: model.IModelDecorationOptions = {
253
+ description: 'comments-range-decoration',
254
+ linesDecorationsClassName: ['comment-range', 'comment-diff-added'].join(' '),
163
255
  };
164
256
  return textModel.ModelDecorationOptions.createDynamic(decorationOptions);
165
257
  }
@@ -191,49 +283,56 @@ export class CommentsService extends Disposable implements ICommentsService {
191
283
  this.registerDecorationProvider();
192
284
  }
193
285
 
286
+ private startCommentRange: IRange | null;
287
+ private endCommentRange: IRange | null;
288
+ private editor: IEditor | null;
289
+ private allEditors: IEditor[] = [];
290
+
291
+ getCurrentEditor(uri?: URI) {
292
+ if (uri) {
293
+ for (const editor of this.allEditors) {
294
+ if (editor.currentUri?.isEqual(uri)) {
295
+ this.editor = editor;
296
+ return editor;
297
+ }
298
+ }
299
+ }
300
+ return this.editor;
301
+ }
302
+
194
303
  public handleOnCreateEditor(editor: IEditor) {
304
+ this.allEditors.push(editor);
305
+ this.editor = editor;
195
306
  const disposer = new Disposable();
307
+ let commentRangeDecorationIds: string[] = [];
308
+ let hasHiddenArea = false;
196
309
 
197
310
  disposer.addDispose(
198
311
  editor.monacoEditor.onMouseDown((event) => {
199
312
  if (
200
313
  event.target.type === monacoBrowser.editor.MouseTargetType.GUTTER_LINE_DECORATIONS &&
201
314
  event.target.element &&
202
- event.target.element.className.indexOf('comments-add') > -1
315
+ event.target.element.className.indexOf('comment-add') > -1
203
316
  ) {
204
317
  const { target } = event;
205
318
  if (target && target.range) {
206
319
  const { range } = target;
207
- // 如果已经存在一个待输入的评论组件,则不创建新的
208
- if (
209
- !this.commentsThreads.some(
210
- (thread) =>
211
- thread.comments.length === 0 &&
212
- thread.uri.isEqual(editor.currentUri!) &&
213
- thread.range.startLineNumber === range.startLineNumber,
214
- )
215
- ) {
216
- const thread = this.createThread(editor.currentUri!, range);
217
- thread.show(editor);
218
- }
320
+ this.startCommentRange = range;
219
321
  event.event.stopPropagation();
220
322
  }
221
323
  } else if (
222
324
  event.target.type === monacoBrowser.editor.MouseTargetType.GUTTER_GLYPH_MARGIN &&
223
325
  event.target.element &&
224
- event.target.element.className.indexOf('comments-thread') > -1
326
+ event.target.element.className.indexOf('comment-thread') > -1
225
327
  ) {
226
328
  const { target } = event;
227
329
  if (target && target.range) {
228
330
  const { range } = target;
229
331
  const threads = this.commentsThreads.filter(
230
- (thread) =>
231
- thread.uri.isEqual(editor.currentUri!) && thread.range.startLineNumber === range.startLineNumber,
332
+ (thread) => thread.uri.isEqual(editor.currentUri!) && thread.range.endLineNumber === range.endLineNumber,
232
333
  );
233
334
  if (threads.length) {
234
- // 判断当前 widget 是否是显示的
235
335
  const isShowWidget = threads.some((thread) => thread.isShowWidget(editor));
236
-
237
336
  if (isShowWidget) {
238
337
  threads.forEach((thread) => thread.hide(editor));
239
338
  } else {
@@ -245,55 +344,479 @@ export class CommentsService extends Disposable implements ICommentsService {
245
344
  }
246
345
  }),
247
346
  );
248
- let oldDecorations: string[] = [];
347
+
249
348
  disposer.addDispose(
250
- editor.monacoEditor.onMouseMove(
251
- debounce(async (event) => {
252
- const uri = editor.currentUri;
349
+ editor.monacoEditor.onMouseUp(async (event) => {
350
+ if (this.startCommentRange) {
351
+ if (hasHiddenArea) {
352
+ this.renderCommentRange(editor);
353
+ hasHiddenArea = false;
354
+ this.startCommentRange = null;
355
+ this.endCommentRange = null;
356
+ return;
357
+ }
358
+ let range = this.startCommentRange;
359
+ if (this.endCommentRange) {
360
+ if (this.endCommentRange.startLineNumber < this.startCommentRange.startLineNumber) {
361
+ range.startColumn = this.endCommentRange.startColumn;
362
+ range.startLineNumber = this.endCommentRange.startLineNumber;
363
+ } else {
364
+ range.endColumn = this.endCommentRange.endColumn;
365
+ range.endLineNumber = this.endCommentRange.endLineNumber;
366
+ }
367
+ }
368
+ if (editor.currentUri) {
369
+ range = await this.getValidRange(range, editor.currentUri);
370
+ }
371
+ if (range) {
372
+ if (
373
+ !this.commentsThreads.some(
374
+ (thread) =>
375
+ thread.comments.length === 0 &&
376
+ thread.uri.isEqual(editor.currentUri!) &&
377
+ thread.range.startLineNumber === range.startLineNumber &&
378
+ thread.range.endLineNumber === range.endLineNumber,
379
+ )
380
+ ) {
381
+ const thread = this.createThread(editor.currentUri!, range);
382
+ thread.show(editor);
383
+ }
384
+ event.event.stopPropagation();
385
+ }
386
+ } else if (
387
+ event.target.type === monacoBrowser.editor.MouseTargetType.GUTTER_LINE_DECORATIONS &&
388
+ event.target.element &&
389
+ event.target.element.className.indexOf('comment-range') > -1 &&
390
+ event.target.element.className.indexOf('comment-thread') < 0
391
+ ) {
392
+ const { target } = event;
393
+ const range: IRange | undefined = target.range;
394
+ if (range) {
395
+ if (
396
+ !this.commentsThreads.some(
397
+ (thread) =>
398
+ thread.comments.length === 0 &&
399
+ thread.uri.isEqual(editor.currentUri!) &&
400
+ thread.range.startLineNumber === range.startLineNumber &&
401
+ thread.range.endLineNumber === range.endLineNumber,
402
+ )
403
+ ) {
404
+ const thread = this.createThread(editor.currentUri!, range);
405
+ thread.show(editor);
406
+ }
407
+ event.event.stopPropagation();
408
+ }
409
+ }
410
+ this.startCommentRange = null;
411
+ this.endCommentRange = null;
412
+ }),
413
+ );
253
414
 
254
- const range = event.target.range;
415
+ disposer.addDispose(
416
+ editor.monacoEditor.onMouseMove(async (event) => {
417
+ const uri = editor.currentUri;
418
+ const range = event.target.range;
419
+ // 多行评论
420
+ if (this.startCommentRange) {
421
+ if (!event.target.element?.className) {
422
+ if (event.target.element?.offsetParent?.className.includes('diff-hidden-lines')) {
423
+ // 当多行评论跨过折叠代码时,不创建评论
424
+ hasHiddenArea = true;
425
+ }
426
+ }
427
+ if (uri && range) {
428
+ let selection = {
429
+ startLineNumber: this.startCommentRange.startLineNumber,
430
+ endLineNumber: range.endLineNumber,
431
+ startColumn: this.startCommentRange.startColumn,
432
+ endColumn: range.endColumn,
433
+ };
434
+ if (this.startCommentRange.startLineNumber > range.startLineNumber) {
435
+ selection = {
436
+ startLineNumber: range.startLineNumber,
437
+ endLineNumber: this.startCommentRange.endLineNumber,
438
+ startColumn: range.startColumn,
439
+ endColumn: this.startCommentRange.endColumn,
440
+ };
441
+ }
442
+ this.renderCommentRange(editor, selection);
443
+ this.endCommentRange = range;
444
+ }
445
+ } else {
255
446
  if (uri && range && (await this.shouldShowHoverDecoration(uri, range))) {
256
- oldDecorations = editor.monacoEditor.deltaDecorations(oldDecorations, [
447
+ const newDecorations = [
257
448
  {
258
449
  range: positionToRange(range.startLineNumber),
259
450
  options: this.createHoverDecoration() as unknown as monaco.editor.IModelDecorationOptions,
260
451
  },
261
- ]);
262
- } else {
263
- oldDecorations = editor.monacoEditor.deltaDecorations(oldDecorations, []);
452
+ ];
453
+ commentRangeDecorationIds = editor.monacoEditor.deltaDecorations(commentRangeDecorationIds, newDecorations);
264
454
  }
265
- }, 10),
266
- ),
455
+ }
456
+ }),
267
457
  );
268
458
 
269
459
  disposer.addDispose(
270
- editor.monacoEditor.onMouseLeave(
271
- debounce(() => {
272
- oldDecorations = editor.monacoEditor.deltaDecorations(oldDecorations, []);
273
- }, 10),
274
- ),
460
+ editor.monacoEditor.onMouseLeave(async (event) => {
461
+ const range = event.target?.range;
462
+ const newDecorations: {
463
+ range: IRange;
464
+ options: monaco.editor.IModelDecorationOptions;
465
+ }[] = [];
466
+ if (!this.startCommentRange && range) {
467
+ newDecorations.push({
468
+ range: positionToRange(range.startLineNumber),
469
+ options: this.createCommentRangeDecoration() as unknown as monaco.editor.IModelDecorationOptions,
470
+ });
471
+ }
472
+ commentRangeDecorationIds = editor.monacoEditor.deltaDecorations(commentRangeDecorationIds, newDecorations);
473
+ }),
474
+ );
475
+
476
+ disposer.addDispose(
477
+ this.onCommentRangeProviderChange(() => {
478
+ this.renderCommentRange(editor);
479
+ }),
480
+ );
481
+
482
+ disposer.addDispose(
483
+ Event.any(
484
+ this.onThreadsChanged,
485
+ this.onThreadsCommentChange,
486
+ this.onThreadsCreated,
487
+ )((thread) => {
488
+ const editor = this.getCurrentEditor(thread.uri);
489
+ if (editor) {
490
+ this.renderCommentRange(editor);
491
+ }
492
+ this.updateActiveThreadDecoration(undefined);
493
+ }),
494
+ );
495
+
496
+ disposer.addDispose(
497
+ this.onDidChangeCurrentCommentThread((thread) => {
498
+ this.updateActiveThreadDecoration(thread);
499
+ }),
275
500
  );
276
501
 
502
+ this.tryUpdateReservedSpace(editor);
503
+
504
+ disposer.addDispose(
505
+ editor.monacoEditor.onDidChangeModel(() => {
506
+ this.renderCommentRange(editor);
507
+ this.tryUpdateReservedSpace(editor);
508
+ }),
509
+ );
277
510
  return disposer;
278
511
  }
279
512
 
513
+ private editorCommentingRangeSpaceReservedMap: Map<string, boolean> = new Map();
514
+ private editorLineDecorationsWidthMap: Map<string, number> = new Map();
515
+
516
+ private async getValidRange(range: IRange, uri?: URI) {
517
+ if (!uri) {
518
+ return range;
519
+ }
520
+ const contributionRanges = await this.getContributionRanges(uri);
521
+ if (contributionRanges.length === 0) {
522
+ return range;
523
+ }
524
+ const validRange = contributionRanges.find((contributionRange) => {
525
+ if (
526
+ range.startLineNumber >= contributionRange.startLineNumber &&
527
+ range.startLineNumber <= contributionRange.endLineNumber
528
+ ) {
529
+ return true;
530
+ }
531
+ });
532
+ if (validRange) {
533
+ if (validRange.endLineNumber < range.endLineNumber) {
534
+ return {
535
+ startLineNumber: range.startLineNumber,
536
+ startColumn: range.startColumn,
537
+ endLineNumber: validRange.endLineNumber,
538
+ endColumn: validRange.endColumn,
539
+ };
540
+ } else {
541
+ return range;
542
+ }
543
+ } else {
544
+ return range;
545
+ }
546
+ }
547
+
548
+ private ensureCommentingRangeReservedAmount(editor: IEditor) {
549
+ const existing = this.getExistingCommentEditorOptions(editor);
550
+ const lineDecorationsWidth = this.editorLineDecorationsWidthMap.get(editor.getId());
551
+ if (existing.lineDecorationsWidth !== lineDecorationsWidth) {
552
+ editor.updateOptions({
553
+ lineDecorationsWidth: this.getWithCommentsLineDecorationWidth(editor, existing.lineDecorationsWidth),
554
+ });
555
+ }
556
+ }
557
+
558
+ private async tryUpdateReservedSpace(editor: IEditor) {
559
+ if (!editor) {
560
+ return;
561
+ }
562
+ let commentingRangeSpaceReserved = this.editorCommentingRangeSpaceReservedMap.get(editor.getId()) || false;
563
+ const shouldShowComments = editor.currentUri ? this.shouldShowCommentsSchemes.has(editor.currentUri.scheme) : false;
564
+
565
+ const hasComments = this.commentsThreads.some(
566
+ (thread) => thread.uri.isEqual(editor.currentUri!) && thread.comments.length > 0,
567
+ );
568
+
569
+ const hasCommentsOrRanges = shouldShowComments || hasComments;
570
+ if (hasCommentsOrRanges) {
571
+ if (!commentingRangeSpaceReserved) {
572
+ commentingRangeSpaceReserved = true;
573
+ const { lineDecorationsWidth, extraEditorClassName } = this.getExistingCommentEditorOptions(editor);
574
+ const newOptions = this.getWithCommentsEditorOptions(editor, extraEditorClassName, lineDecorationsWidth);
575
+ this.updateEditorLayoutOptions(editor, newOptions.extraEditorClassName, newOptions.lineDecorationsWidth);
576
+ } else {
577
+ this.ensureCommentingRangeReservedAmount(editor);
578
+ }
579
+ } else if (!hasCommentsOrRanges && commentingRangeSpaceReserved) {
580
+ commentingRangeSpaceReserved = false;
581
+ const { lineDecorationsWidth, extraEditorClassName } = this.getExistingCommentEditorOptions(editor);
582
+ const newOptions = this.getWithoutCommentsEditorOptions(editor, extraEditorClassName, lineDecorationsWidth);
583
+ this.updateEditorLayoutOptions(editor, newOptions.extraEditorClassName, newOptions.lineDecorationsWidth);
584
+ }
585
+ this.editorCommentingRangeSpaceReservedMap.set(editor.getId(), commentingRangeSpaceReserved);
586
+ }
587
+
588
+ private getExistingCommentEditorOptions(editor: IEditor) {
589
+ const lineDecorationsWidth: number = editor.monacoEditor.getOption(monaco.EditorOption.lineDecorationsWidth);
590
+ let extraEditorClassName: string[] = [];
591
+ const configuredExtraClassName = editor.monacoEditor.getRawOptions().extraEditorClassName;
592
+ if (configuredExtraClassName) {
593
+ extraEditorClassName = configuredExtraClassName.split(' ');
594
+ }
595
+ return { lineDecorationsWidth, extraEditorClassName };
596
+ }
597
+
598
+ private getWithoutCommentsEditorOptions(
599
+ editor: IEditor,
600
+ extraEditorClassName: string[],
601
+ startingLineDecorationsWidth: number,
602
+ ) {
603
+ let lineDecorationsWidth = startingLineDecorationsWidth;
604
+ const inlineCommentPos = extraEditorClassName.findIndex((name) => name === 'inline-comment');
605
+ if (inlineCommentPos >= 0) {
606
+ extraEditorClassName.splice(inlineCommentPos, 1);
607
+ }
608
+
609
+ const options = editor.monacoEditor.getOptions();
610
+ if (options.get(monaco.EditorOption.folding) && options.get(monaco.EditorOption.showFoldingControls) !== 'never') {
611
+ lineDecorationsWidth += 11; // 11 comes from https://github.com/microsoft/vscode/blob/94ee5f58619d59170983f453fe78f156c0cc73a3/src/vs/workbench/contrib/comments/browser/media/review.css#L485
612
+ }
613
+ lineDecorationsWidth -= 24;
614
+ return { extraEditorClassName, lineDecorationsWidth };
615
+ }
616
+
617
+ private updateEditorLayoutOptions(editor: IEditor, extraEditorClassName: string[], lineDecorationsWidth: number) {
618
+ editor.updateOptions({
619
+ extraEditorClassName: extraEditorClassName.join(' '),
620
+ lineDecorationsWidth,
621
+ });
622
+ }
623
+
624
+ private getWithCommentsEditorOptions(
625
+ editor: IEditor,
626
+ extraEditorClassName: string[],
627
+ startingLineDecorationsWidth: number,
628
+ ) {
629
+ extraEditorClassName.push('inline-comment');
630
+ return {
631
+ lineDecorationsWidth: this.getWithCommentsLineDecorationWidth(editor, startingLineDecorationsWidth),
632
+ extraEditorClassName,
633
+ };
634
+ }
635
+
636
+ private getWithCommentsLineDecorationWidth(editor: IEditor, startingLineDecorationsWidth: number) {
637
+ let lineDecorationsWidth = startingLineDecorationsWidth;
638
+ const options = editor.monacoEditor.getOptions();
639
+ if (options.get(monaco.EditorOption.folding) && options.get(monaco.EditorOption.showFoldingControls) !== 'never') {
640
+ lineDecorationsWidth -= 11;
641
+ }
642
+ lineDecorationsWidth += 24;
643
+ this.editorLineDecorationsWidthMap.set(editor.getId(), lineDecorationsWidth);
644
+ return lineDecorationsWidth;
645
+ }
646
+
647
+ private async renderCommentRange(
648
+ editor: IEditor,
649
+ selection: IRange = {
650
+ startLineNumber: 0,
651
+ endLineNumber: 0,
652
+ startColumn: 0,
653
+ endColumn: 0,
654
+ },
655
+ ) {
656
+ if (!editor.currentUri) {
657
+ return;
658
+ }
659
+ const contributionRanges = await this.getContributionRanges(editor.currentUri);
660
+ if (contributionRanges.length > 0) {
661
+ const newDecorations: {
662
+ range: IRange;
663
+ options: monaco.editor.IModelDecorationOptions;
664
+ }[] = [];
665
+ contributionRanges.map((contributionRange) => {
666
+ if (selection.startLineNumber === 0 && selection.endLineNumber === 0) {
667
+ newDecorations.push({
668
+ range: contributionRange,
669
+ options: this.createCommentRangeDecoration() as unknown as monaco.editor.IModelDecorationOptions,
670
+ });
671
+ } else if (
672
+ selection.startLineNumber <= contributionRange.startLineNumber &&
673
+ selection.endLineNumber >= contributionRange.endLineNumber
674
+ ) {
675
+ newDecorations.push(
676
+ ...[
677
+ {
678
+ range: contributionRange,
679
+ options: this.createDottedRangeDecoration() as unknown as monaco.editor.IModelDecorationOptions,
680
+ },
681
+ {
682
+ range: contributionRange,
683
+ options: this.createThreadRangeDecoration() as unknown as monaco.editor.IModelDecorationOptions,
684
+ },
685
+ ],
686
+ );
687
+ } else if (selection.endLineNumber >= contributionRange.endLineNumber) {
688
+ if (selection.startLineNumber <= contributionRange.endLineNumber) {
689
+ // 存在交集
690
+ const selectionRange = {
691
+ startLineNumber: selection.startLineNumber,
692
+ endLineNumber: contributionRange.endLineNumber,
693
+ startColumn: selection.startColumn,
694
+ endColumn: contributionRange.endColumn,
695
+ };
696
+ const topCommentRange = {
697
+ startLineNumber: contributionRange.startLineNumber,
698
+ endLineNumber: selectionRange.startLineNumber - 1,
699
+ startColumn: contributionRange.startColumn,
700
+ endColumn: selectionRange.endColumn,
701
+ };
702
+ newDecorations.push(
703
+ ...[
704
+ {
705
+ range: topCommentRange,
706
+ options: this.createCommentRangeDecoration() as unknown as monaco.editor.IModelDecorationOptions,
707
+ },
708
+ {
709
+ range: selectionRange,
710
+ options: this.createDottedRangeDecoration() as unknown as monaco.editor.IModelDecorationOptions,
711
+ },
712
+ {
713
+ range: selectionRange,
714
+ options: this.createThreadRangeDecoration() as unknown as monaco.editor.IModelDecorationOptions,
715
+ },
716
+ ],
717
+ );
718
+ } else {
719
+ newDecorations.push({
720
+ range: contributionRange,
721
+ options: this.createCommentRangeDecoration() as unknown as monaco.editor.IModelDecorationOptions,
722
+ });
723
+ }
724
+ } else if (selection.endLineNumber < contributionRange.endLineNumber) {
725
+ if (selection.endLineNumber >= contributionRange.startLineNumber) {
726
+ // 存在交集
727
+ if (selection.startLineNumber >= contributionRange.startLineNumber) {
728
+ const topCommentRange = {
729
+ startLineNumber: contributionRange.startLineNumber,
730
+ startColumn: contributionRange.startColumn,
731
+ endLineNumber: selection.startLineNumber - 1,
732
+ endColumn: selection.startColumn,
733
+ };
734
+ const bottomCommentRange = {
735
+ startLineNumber: selection.endLineNumber + 1,
736
+ startColumn: selection.endColumn,
737
+ endLineNumber: contributionRange.endLineNumber,
738
+ endColumn: contributionRange.endColumn,
739
+ };
740
+ const decorations =
741
+ selection.startLineNumber !== contributionRange.startLineNumber
742
+ ? [
743
+ {
744
+ range: topCommentRange,
745
+ options:
746
+ this.createCommentRangeDecoration() as unknown as monaco.editor.IModelDecorationOptions,
747
+ },
748
+ ]
749
+ : [];
750
+ decorations.push({
751
+ range: selection,
752
+ options: this.createDottedRangeDecoration() as unknown as monaco.editor.IModelDecorationOptions,
753
+ });
754
+ decorations.push({
755
+ range: selection,
756
+ options: this.createThreadRangeDecoration() as unknown as monaco.editor.IModelDecorationOptions,
757
+ });
758
+ decorations.push({
759
+ range: bottomCommentRange,
760
+ options: this.createCommentRangeDecoration() as unknown as monaco.editor.IModelDecorationOptions,
761
+ });
762
+ newDecorations.push(...decorations);
763
+ } else {
764
+ const selectionRange = {
765
+ startLineNumber: contributionRange.startLineNumber,
766
+ startColumn: contributionRange.startColumn,
767
+ endLineNumber: selection.endLineNumber,
768
+ endColumn: selection.endColumn,
769
+ };
770
+ const bottomCommentRange = {
771
+ startLineNumber: selectionRange.endLineNumber + 1,
772
+ startColumn: selectionRange.endColumn,
773
+ endLineNumber: contributionRange.endLineNumber,
774
+ endColumn: contributionRange.endColumn,
775
+ };
776
+ newDecorations.push(
777
+ ...[
778
+ {
779
+ range: selectionRange,
780
+ options: this.createDottedRangeDecoration() as unknown as monaco.editor.IModelDecorationOptions,
781
+ },
782
+ {
783
+ range: selectionRange,
784
+ options: this.createThreadRangeDecoration() as unknown as monaco.editor.IModelDecorationOptions,
785
+ },
786
+ {
787
+ range: bottomCommentRange,
788
+ options: this.createCommentRangeDecoration() as unknown as monaco.editor.IModelDecorationOptions,
789
+ },
790
+ ],
791
+ );
792
+ }
793
+ } else {
794
+ newDecorations.push({
795
+ range: contributionRange,
796
+ options: this.createCommentRangeDecoration() as unknown as monaco.editor.IModelDecorationOptions,
797
+ });
798
+ }
799
+ }
800
+ });
801
+ const commentRangeDecorationIds = this.commentRangeDecorationMap.get(editor.currentUri.toString()) || [];
802
+ this.commentRangeDecorationMap.set(
803
+ editor.currentUri.toString(),
804
+ editor.monacoEditor.deltaDecorations(commentRangeDecorationIds, newDecorations),
805
+ );
806
+ } else {
807
+ this.commentRangeDecorationMap.set(editor.currentUri.toString(), []);
808
+ }
809
+ }
810
+
280
811
  private async shouldShowHoverDecoration(uri: URI, range: IRange) {
281
812
  if (!this.shouldShowCommentsSchemes.has(uri.scheme)) {
282
813
  return false;
283
814
  }
284
815
  const contributionRanges = await this.getContributionRanges(uri);
285
816
  const isProviderRanges = contributionRanges.some(
286
- (contributionRange) =>
287
- range.startLineNumber >= contributionRange.startLineNumber &&
288
- range.startLineNumber <= contributionRange.endLineNumber,
817
+ (contributionRange) => range.startLineNumber <= contributionRange.endLineNumber,
289
818
  );
290
- // 如果不支持对同一行进行多个评论,那么过滤掉当前有 thread 行号的 decoration
291
- const isShowHoverToSingleLine =
292
- this.isMultiCommentsForSingleLine ||
293
- !this.commentsThreads.some(
294
- (thread) => thread.uri.isEqual(uri) && thread.range.startLineNumber === range.startLineNumber,
295
- );
296
- return isProviderRanges && isShowHoverToSingleLine;
819
+ return isProviderRanges;
297
820
  }
298
821
 
299
822
  public createThread(
@@ -305,7 +828,7 @@ export class CommentsService extends Disposable implements ICommentsService {
305
828
  },
306
829
  ) {
307
830
  // 获取当前 range 的 providerId,用于 commentController contextKey 的生成
308
- const providerId = this.getProviderIdsByLine(range.startLineNumber)[0];
831
+ const providerId = this.getProviderIdsByLine(range.endLineNumber)[0];
309
832
  const thread = this.injector.get(CommentsThread, [uri, range, providerId, options]);
310
833
  thread.onDispose(() => {
311
834
  this.threads.delete(thread.id);
@@ -367,13 +890,17 @@ export class CommentsService extends Disposable implements ICommentsService {
367
890
  for (const thread of (parent as CommentFileNode).threads) {
368
891
  const [first] = thread.comments;
369
892
  const comment = typeof first.body === 'string' ? first.body : first.body.value;
893
+ let description = `[Ln ${thread.range.startLineNumber}]`;
894
+ if (thread.range.startLineNumber !== thread.range.endLineNumber) {
895
+ description = `[Ln ${thread.range.startLineNumber}-${thread.range.endLineNumber}]`;
896
+ }
370
897
  childs.push(
371
898
  new CommentContentNode(
372
899
  this,
373
900
  thread,
374
901
  comment,
375
- `[Ln ${thread.range.startLineNumber}]`,
376
- first.author.iconPath
902
+ description,
903
+ first.author.iconPath && (first.author.iconPath as URI)?.authority
377
904
  ? (this.iconService.fromIcon('', first.author.iconPath.toString(), IconType.Background) as string)
378
905
  : getIcon('message'),
379
906
  first.author,
@@ -444,13 +971,16 @@ export class CommentsService extends Disposable implements ICommentsService {
444
971
  return await cache.promise;
445
972
  }
446
973
 
447
- const model = this.documentService.getModelReference(uri, 'get-contribution-rages');
974
+ const model = this.documentService.getModelReference(uri, 'Get Comment Range');
448
975
  const rangePromise: Promise<IRange[] | undefined>[] = [];
449
976
  for (const rangeProvider of this.rangeProviderMap) {
450
977
  const [id, provider] = rangeProvider;
451
978
  rangePromise.push(
452
979
  (async () => {
453
- const ranges = await provider.getCommentingRanges(model?.instance!);
980
+ if (!model?.instance) {
981
+ return;
982
+ }
983
+ const ranges = await provider.getCommentingRanges(model.instance);
454
984
  if (ranges && ranges.length) {
455
985
  // FIXME: ranges 会被 Diff uri 的两个 range 互相覆盖,导致可能根据行查不到 provider
456
986
  this.rangeOwner.set(id, ranges);
@@ -503,7 +1033,12 @@ export class CommentsService extends Disposable implements ICommentsService {
503
1033
  return isCurrentThread;
504
1034
  })
505
1035
  .map((thread) => ({
506
- range: thread.range,
1036
+ range: {
1037
+ startLineNumber: thread.range.endLineNumber,
1038
+ endLineNumber: thread.range.endLineNumber,
1039
+ startColumn: thread.range.endColumn,
1040
+ endColumn: thread.range.endColumn,
1041
+ },
507
1042
  options: this.createThreadDecoration(thread) as unknown as monaco.editor.IModelDecorationOptions,
508
1043
  })),
509
1044
  });
@@ -543,6 +1078,7 @@ export class CommentsService extends Disposable implements ICommentsService {
543
1078
  this.rangeProviderMap.set(id, provider);
544
1079
  // 注册一个新的 range provider 后清理掉之前的缓存
545
1080
  this.providerDecorationCache.clear();
1081
+ this.commentRangeProviderChangeEmitter.fire();
546
1082
  return Disposable.create(() => {
547
1083
  this.rangeProviderMap.delete(id);
548
1084
  this.rangeOwner.delete(id);
@@ -563,7 +1099,6 @@ export class CommentsService extends Disposable implements ICommentsService {
563
1099
  public getProviderIdsByLine(line: number): string[] {
564
1100
  const result: string[] = [];
565
1101
  if (this.rangeOwner.size === 1) {
566
- // 只有一个provider,直接返回
567
1102
  return [this.rangeOwner.keys().next().value];
568
1103
  }
569
1104
  for (const rangeOwner of this.rangeOwner) {