@opensumi/ide-comments 2.21.13 → 2.22.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.
Files changed (64) hide show
  1. package/lib/browser/comments-feature.registry.js.map +1 -1
  2. package/lib/browser/comments-panel.view.d.ts +2 -3
  3. package/lib/browser/comments-panel.view.d.ts.map +1 -1
  4. package/lib/browser/comments-panel.view.js +32 -70
  5. package/lib/browser/comments-panel.view.js.map +1 -1
  6. package/lib/browser/comments-thread.d.ts +4 -2
  7. package/lib/browser/comments-thread.d.ts.map +1 -1
  8. package/lib/browser/comments-thread.js +14 -7
  9. package/lib/browser/comments-thread.js.map +1 -1
  10. package/lib/browser/comments-zone.service.js.map +1 -1
  11. package/lib/browser/comments-zone.view.d.ts +1 -1
  12. package/lib/browser/comments-zone.view.d.ts.map +1 -1
  13. package/lib/browser/comments-zone.view.js +1 -1
  14. package/lib/browser/comments-zone.view.js.map +1 -1
  15. package/lib/browser/comments.contribution.d.ts +1 -0
  16. package/lib/browser/comments.contribution.d.ts.map +1 -1
  17. package/lib/browser/comments.contribution.js +6 -1
  18. package/lib/browser/comments.contribution.js.map +1 -1
  19. package/lib/browser/comments.service.d.ts +7 -4
  20. package/lib/browser/comments.service.d.ts.map +1 -1
  21. package/lib/browser/comments.service.js +60 -87
  22. package/lib/browser/comments.service.js.map +1 -1
  23. package/lib/browser/index.d.ts +0 -1
  24. package/lib/browser/index.d.ts.map +1 -1
  25. package/lib/browser/index.js +5 -1
  26. package/lib/browser/index.js.map +1 -1
  27. package/lib/browser/tree/comment-node.d.ts +14 -0
  28. package/lib/browser/tree/comment-node.d.ts.map +1 -0
  29. package/lib/browser/tree/comment-node.js +64 -0
  30. package/lib/browser/tree/comment-node.js.map +1 -0
  31. package/lib/browser/tree/tree-model.service.d.ts +43 -0
  32. package/lib/browser/tree/tree-model.service.d.ts.map +1 -0
  33. package/lib/browser/tree/tree-model.service.js +150 -0
  34. package/lib/browser/tree/tree-model.service.js.map +1 -0
  35. package/lib/browser/tree/tree-node.defined.d.ts +61 -0
  36. package/lib/browser/tree/tree-node.defined.d.ts.map +1 -0
  37. package/lib/browser/tree/tree-node.defined.js +116 -0
  38. package/lib/browser/tree/tree-node.defined.js.map +1 -0
  39. package/lib/browser/tree/tree-node.module.less +154 -0
  40. package/lib/common/index.d.ts +38 -36
  41. package/lib/common/index.d.ts.map +1 -1
  42. package/lib/common/index.js.map +1 -1
  43. package/package.json +13 -12
  44. package/src/browser/comment-reactions.view.tsx +109 -0
  45. package/src/browser/comments-body.tsx +57 -0
  46. package/src/browser/comments-feature.registry.ts +91 -0
  47. package/src/browser/comments-item.view.tsx +362 -0
  48. package/src/browser/comments-panel.view.tsx +90 -0
  49. package/src/browser/comments-textarea.view.tsx +194 -0
  50. package/src/browser/comments-thread.ts +309 -0
  51. package/src/browser/comments-zone.service.ts +29 -0
  52. package/src/browser/comments-zone.view.tsx +206 -0
  53. package/src/browser/comments.contribution.ts +201 -0
  54. package/src/browser/comments.module.less +210 -0
  55. package/src/browser/comments.service.ts +546 -0
  56. package/src/browser/index.ts +29 -0
  57. package/src/browser/markdown.style.ts +25 -0
  58. package/src/browser/mentions.style.ts +55 -0
  59. package/src/browser/tree/comment-node.tsx +130 -0
  60. package/src/browser/tree/tree-model.service.ts +173 -0
  61. package/src/browser/tree/tree-node.defined.ts +167 -0
  62. package/src/browser/tree/tree-node.module.less +154 -0
  63. package/src/common/index.ts +710 -0
  64. package/src/index.ts +1 -0
@@ -0,0 +1,546 @@
1
+ import debounce from 'lodash/debounce';
2
+ import flattenDeep from 'lodash/flattenDeep';
3
+ import groupBy from 'lodash/groupBy';
4
+
5
+ import { INJECTOR_TOKEN, Injector, Injectable, Autowired } from '@opensumi/di';
6
+ import {
7
+ Disposable,
8
+ IRange,
9
+ URI,
10
+ Emitter,
11
+ AppConfig,
12
+ localize,
13
+ getIcon,
14
+ Event,
15
+ memoize,
16
+ IDisposable,
17
+ positionToRange,
18
+ Deferred,
19
+ path,
20
+ LRUCache,
21
+ MaybePromise,
22
+ LabelService,
23
+ formatLocalize,
24
+ getExternalIcon,
25
+ } from '@opensumi/ide-core-browser';
26
+ import { IEditor } from '@opensumi/ide-editor';
27
+ import {
28
+ IEditorDecorationCollectionService,
29
+ IEditorDocumentModelService,
30
+ ResourceService,
31
+ WorkbenchEditorService,
32
+ } from '@opensumi/ide-editor/lib/browser';
33
+ import { IMainLayoutService } from '@opensumi/ide-main-layout';
34
+ import { IIconService, IconType } from '@opensumi/ide-theme';
35
+ import * as model from '@opensumi/monaco-editor-core/esm/vs/editor/common/model';
36
+ import * as textModel from '@opensumi/monaco-editor-core/esm/vs/editor/common/model/textModel';
37
+ import * as monaco from '@opensumi/monaco-editor-core/esm/vs/editor/editor.api';
38
+
39
+ import {
40
+ ICommentsService,
41
+ ICommentsThread,
42
+ ICommentsFeatureRegistry,
43
+ CommentPanelId,
44
+ ICommentRangeProvider,
45
+ ICommentsThreadOptions,
46
+ IWriteableCommentsTreeNode,
47
+ } from '../common';
48
+
49
+ import { CommentsPanel } from './comments-panel.view';
50
+ import { CommentsThread } from './comments-thread';
51
+ import { CommentContentNode, CommentFileNode, CommentReplyNode, CommentRoot } from './tree/tree-node.defined';
52
+
53
+ @Injectable()
54
+ export class CommentsService extends Disposable implements ICommentsService {
55
+ @Autowired(INJECTOR_TOKEN)
56
+ private readonly injector: Injector;
57
+
58
+ @Autowired(AppConfig)
59
+ private readonly appConfig: AppConfig;
60
+
61
+ @Autowired(IEditorDecorationCollectionService)
62
+ private readonly editorDecorationCollectionService: IEditorDecorationCollectionService;
63
+
64
+ @Autowired(IIconService)
65
+ private readonly iconService: IIconService;
66
+
67
+ @Autowired(LabelService)
68
+ private readonly labelService: LabelService;
69
+
70
+ @Autowired(ICommentsFeatureRegistry)
71
+ private readonly commentsFeatureRegistry: ICommentsFeatureRegistry;
72
+
73
+ @Autowired(IEditorDocumentModelService)
74
+ private readonly documentService: IEditorDocumentModelService;
75
+
76
+ @Autowired(IMainLayoutService)
77
+ private readonly layoutService: IMainLayoutService;
78
+
79
+ @Autowired(WorkbenchEditorService)
80
+ private readonly workbenchEditorService: WorkbenchEditorService;
81
+
82
+ @Autowired(ResourceService)
83
+ private readonly resourceService: ResourceService;
84
+
85
+ private decorationChangeEmitter = new Emitter<URI>();
86
+
87
+ private threads = new Map<string, ICommentsThread>();
88
+
89
+ private threadsChangeEmitter = new Emitter<ICommentsThread>();
90
+
91
+ private threadsCommentChangeEmitter = new Emitter<ICommentsThread>();
92
+
93
+ private threadsCreatedEmitter = new Emitter<ICommentsThread>();
94
+
95
+ private rangeProviderMap = new Map<string, ICommentRangeProvider>();
96
+
97
+ private rangeOwner = new Map<string, IRange[]>();
98
+
99
+ private providerDecorationCache = new LRUCache<string, Deferred<IRange[]>>(10000);
100
+
101
+ // 默认在 file 协议和 git 协议中显示评论数据
102
+ private shouldShowCommentsSchemes = new Set(['file', 'git']);
103
+
104
+ private decorationProviderDisposer = Disposable.NULL;
105
+
106
+ get commentsThreads() {
107
+ return [...this.threads.values()];
108
+ }
109
+
110
+ @memoize
111
+ get isMultiCommentsForSingleLine() {
112
+ return !!this.commentsFeatureRegistry.getConfig()?.isMultiCommentsForSingleLine;
113
+ }
114
+
115
+ @memoize
116
+ get currentAuthorAvatar() {
117
+ return this.commentsFeatureRegistry.getConfig()?.author?.avatar;
118
+ }
119
+
120
+ @memoize
121
+ get filterThreadDecoration() {
122
+ return this.commentsFeatureRegistry.getConfig()?.filterThreadDecoration;
123
+ }
124
+
125
+ get onThreadsChanged(): Event<ICommentsThread> {
126
+ return this.threadsChangeEmitter.event;
127
+ }
128
+
129
+ get onThreadsCommentChange(): Event<ICommentsThread> {
130
+ return this.threadsCommentChangeEmitter.event;
131
+ }
132
+
133
+ get onThreadsCreated(): Event<ICommentsThread> {
134
+ return this.threadsCreatedEmitter.event;
135
+ }
136
+
137
+ /**
138
+ * -------------------------------- IMPORTANT --------------------------------
139
+ * 需要注意区分 model.IModelDecorationOptions 与 monaco.editor.IModelDecorationOptions 两个类型
140
+ * 将 model.IModelDecorationOptions 类型的对象传给签名为 monaco.editor.IModelDecorationOptions 的方法时需要做 Type Assertion
141
+ * 这是因为 monaco.d.ts 与 vs/editor/common/model 分别导出了枚举 TrackedRangeStickiness
142
+ * 这种情况下两个枚举的类型是不兼容的,即使他们是同一段代码的编译产物
143
+ * -------------------------------- IMPORTANT --------------------------------
144
+ * @param thread
145
+ */
146
+ private createThreadDecoration(thread: ICommentsThread): model.IModelDecorationOptions {
147
+ // 对于新增的空的 thread,默认显示当前用户的头像,否则使用第一个用户的头像
148
+ const avatar =
149
+ thread.comments.length === 0 ? this.currentAuthorAvatar : thread.comments[0].author.iconPath?.toString();
150
+ const icon = avatar ? this.iconService.fromIcon('', avatar, IconType.Background) : getIcon('message');
151
+ const decorationOptions: model.IModelDecorationOptions = {
152
+ description: 'comments-thread-decoration',
153
+ // 创建评论显示在 glyph margin 处
154
+ glyphMarginClassName: ['comments-decoration', 'comments-thread', icon].join(' '),
155
+ };
156
+ return textModel.ModelDecorationOptions.createDynamic(decorationOptions);
157
+ }
158
+
159
+ private createHoverDecoration(): model.IModelDecorationOptions {
160
+ const decorationOptions: model.IModelDecorationOptions = {
161
+ description: 'comments-hover-decoration',
162
+ linesDecorationsClassName: ['comments-decoration', 'comments-add', getIcon('add-comments')].join(' '),
163
+ };
164
+ return textModel.ModelDecorationOptions.createDynamic(decorationOptions);
165
+ }
166
+
167
+ public init() {
168
+ // 插件注册 ResourceProvider 时重新注册 CommentDecorationProvider
169
+ // 例如 Github Pull Request 插件的 scheme 为 pr
170
+ this.addDispose(
171
+ this.resourceService.onRegisterResourceProvider((provider) => {
172
+ if (provider.scheme) {
173
+ this.shouldShowCommentsSchemes.add(provider.scheme);
174
+ this.registerDecorationProvider();
175
+ }
176
+ }),
177
+ );
178
+ this.addDispose(
179
+ this.resourceService.onUnregisterResourceProvider((provider) => {
180
+ if (provider.scheme) {
181
+ this.shouldShowCommentsSchemes.delete(provider.scheme);
182
+ this.registerDecorationProvider();
183
+ }
184
+ }),
185
+ );
186
+ this.registerDecorationProvider();
187
+ }
188
+
189
+ public handleOnCreateEditor(editor: IEditor) {
190
+ const disposer = new Disposable();
191
+
192
+ disposer.addDispose(
193
+ editor.monacoEditor.onMouseDown((event) => {
194
+ if (
195
+ event.target.type === monaco.editor.MouseTargetType.GUTTER_LINE_DECORATIONS &&
196
+ event.target.element &&
197
+ event.target.element.className.indexOf('comments-add') > -1
198
+ ) {
199
+ const { target } = event;
200
+ if (target && target.range) {
201
+ const { range } = target;
202
+ // 如果已经存在一个待输入的评论组件,则不创建新的
203
+ if (
204
+ this.commentsThreads.some(
205
+ (thread) =>
206
+ thread.comments.length === 0 &&
207
+ thread.uri.isEqual(editor.currentUri!) &&
208
+ thread.range.startLineNumber === range.startLineNumber,
209
+ )
210
+ ) {
211
+ return;
212
+ }
213
+ const thread = this.createThread(editor.currentUri!, range);
214
+ thread.show(editor);
215
+ }
216
+ } else if (
217
+ event.target.type === monaco.editor.MouseTargetType.GUTTER_GLYPH_MARGIN &&
218
+ event.target.element &&
219
+ event.target.element.className.indexOf('comments-thread') > -1
220
+ ) {
221
+ const { target } = event;
222
+ if (target && target.range) {
223
+ const { range } = target;
224
+ const threads = this.commentsThreads.filter(
225
+ (thread) =>
226
+ thread.uri.isEqual(editor.currentUri!) && thread.range.startLineNumber === range.startLineNumber,
227
+ );
228
+ if (threads.length) {
229
+ // 判断当前 widget 是否是显示的
230
+ const isShowWidget = threads.some((thread) => thread.isShowWidget(editor));
231
+
232
+ if (isShowWidget) {
233
+ threads.forEach((thread) => thread.hide(editor));
234
+ } else {
235
+ threads.forEach((thread) => thread.show(editor));
236
+ }
237
+ }
238
+ }
239
+ }
240
+ }),
241
+ );
242
+ let oldDecorations: string[] = [];
243
+ disposer.addDispose(
244
+ editor.monacoEditor.onMouseMove(
245
+ debounce(async (event) => {
246
+ const uri = editor.currentUri;
247
+
248
+ const range = event.target.range;
249
+ if (uri && range && (await this.shouldShowHoverDecoration(uri, range))) {
250
+ oldDecorations = editor.monacoEditor.deltaDecorations(oldDecorations, [
251
+ {
252
+ range: positionToRange(range.startLineNumber),
253
+ options: this.createHoverDecoration() as unknown as monaco.editor.IModelDecorationOptions,
254
+ },
255
+ ]);
256
+ } else {
257
+ oldDecorations = editor.monacoEditor.deltaDecorations(oldDecorations, []);
258
+ }
259
+ }, 10),
260
+ ),
261
+ );
262
+
263
+ disposer.addDispose(
264
+ editor.monacoEditor.onMouseLeave(
265
+ debounce(() => {
266
+ oldDecorations = editor.monacoEditor.deltaDecorations(oldDecorations, []);
267
+ }, 10),
268
+ ),
269
+ );
270
+
271
+ return disposer;
272
+ }
273
+
274
+ private async shouldShowHoverDecoration(uri: URI, range: IRange) {
275
+ if (!this.shouldShowCommentsSchemes.has(uri.scheme)) {
276
+ return false;
277
+ }
278
+ const contributionRanges = await this.getContributionRanges(uri);
279
+ const isProviderRanges = contributionRanges.some(
280
+ (contributionRange) =>
281
+ range.startLineNumber >= contributionRange.startLineNumber &&
282
+ range.startLineNumber <= contributionRange.endLineNumber,
283
+ );
284
+ // 如果不支持对同一行进行多个评论,那么过滤掉当前有 thread 行号的 decoration
285
+ const isShowHoverToSingleLine =
286
+ this.isMultiCommentsForSingleLine ||
287
+ !this.commentsThreads.some(
288
+ (thread) => thread.uri.isEqual(uri) && thread.range.startLineNumber === range.startLineNumber,
289
+ );
290
+ return isProviderRanges && isShowHoverToSingleLine;
291
+ }
292
+
293
+ public createThread(
294
+ uri: URI,
295
+ range: IRange,
296
+ options: ICommentsThreadOptions = {
297
+ comments: [],
298
+ readOnly: false,
299
+ },
300
+ ) {
301
+ // 获取当前 range 的 providerId,用于 commentController contextKey 的生成
302
+ const providerId = this.getProviderIdsByLine(range.startLineNumber)[0];
303
+ const thread = this.injector.get(CommentsThread, [uri, range, providerId, options]);
304
+ thread.onDispose(() => {
305
+ this.threads.delete(thread.id);
306
+ this.threadsChangeEmitter.fire(thread);
307
+ this.decorationChangeEmitter.fire(uri);
308
+ });
309
+ thread.onDidChange(() => {
310
+ this.threadsChangeEmitter.fire(thread);
311
+ });
312
+ this.threads.set(thread.id, thread);
313
+ this.addDispose(thread);
314
+ this.threadsChangeEmitter.fire(thread);
315
+ this.threadsCreatedEmitter.fire(thread);
316
+ this.decorationChangeEmitter.fire(uri);
317
+ return thread;
318
+ }
319
+
320
+ public getThreadsByUri(uri: URI) {
321
+ return (
322
+ this.commentsThreads
323
+ .filter((thread) => thread.uri.isEqual(uri))
324
+ // 默认按照 rang 顺序 升序排列
325
+ .sort((a, b) => a.range.startLineNumber - b.range.startLineNumber)
326
+ );
327
+ }
328
+
329
+ async resolveChildren(parent?: CommentRoot | CommentFileNode | CommentContentNode) {
330
+ let childs: (CommentRoot | CommentFileNode | CommentContentNode | CommentReplyNode)[] = [];
331
+ if (!parent) {
332
+ childs.push(new CommentRoot(this));
333
+ } else {
334
+ if (CommentRoot.isRoot(parent)) {
335
+ const commentThreads = [...this.threads.values()].filter((thread) => thread.comments.length);
336
+ const threadUris = groupBy(commentThreads, (thread: ICommentsThread) => thread.uri);
337
+ Object.keys(threadUris).map((uri) => {
338
+ const threads: ICommentsThread[] = threadUris[uri];
339
+ if (threads.length === 0) {
340
+ return;
341
+ }
342
+ const workspaceDir = new URI(this.appConfig.workspaceDir);
343
+ const resource = new URI(uri);
344
+ const description = workspaceDir.relative(resource)?.toString();
345
+ childs.push(
346
+ new CommentFileNode(
347
+ this,
348
+ threads,
349
+ description,
350
+ resource.codeUri.fsPath,
351
+ this.labelService.getIcon(resource),
352
+ resource,
353
+ parent,
354
+ ),
355
+ );
356
+ });
357
+ } else if (CommentFileNode.is(parent)) {
358
+ for (const thread of (parent as CommentFileNode).threads) {
359
+ const [first] = thread.comments;
360
+ const comment = typeof first.body === 'string' ? first.body : first.body.value;
361
+ childs.push(
362
+ new CommentContentNode(
363
+ this,
364
+ thread,
365
+ comment,
366
+ `[Ln ${thread.range.startLineNumber}]`,
367
+ first.author.iconPath
368
+ ? (this.iconService.fromIcon('', first.author.iconPath.toString(), IconType.Background) as string)
369
+ : getIcon('message'),
370
+ first.author,
371
+ parent.resource,
372
+ parent as CommentFileNode,
373
+ ),
374
+ );
375
+ }
376
+ } else if (CommentContentNode.is(parent)) {
377
+ const thread = parent.thread;
378
+ const [_, ...others] = thread.comments;
379
+ const lastReply = others[others.length - 1].author.name;
380
+ childs.push(
381
+ new CommentReplyNode(
382
+ this,
383
+ thread,
384
+ formatLocalize('comment.reply.count', others?.length || 0),
385
+ formatLocalize('comment.reply.lastReply', lastReply),
386
+ '',
387
+ parent.resource,
388
+ parent,
389
+ ),
390
+ );
391
+ }
392
+ }
393
+ if (childs.length === 1 && CommentRoot.isRoot(childs[0])) {
394
+ return childs;
395
+ }
396
+ const handlers = this.commentsFeatureRegistry.getCommentsPanelTreeNodeHandlers();
397
+ if (handlers.length > 0) {
398
+ for (const handler of handlers) {
399
+ childs = handler(childs as IWriteableCommentsTreeNode[]) as (
400
+ | CommentFileNode
401
+ | CommentContentNode
402
+ | CommentReplyNode
403
+ )[];
404
+ }
405
+ }
406
+ return childs;
407
+ }
408
+
409
+ public async getContributionRanges(uri: URI): Promise<IRange[]> {
410
+ // 一个diff editor对应两个uri,两个uri的rangeOwner不应该互相覆盖
411
+ const cache = this.providerDecorationCache.get(uri.toString());
412
+ // 优先从缓存中拿
413
+ if (cache) {
414
+ return await cache.promise;
415
+ }
416
+
417
+ const model = this.documentService.getModelReference(uri, 'get-contribution-rages');
418
+ const rangePromise: Promise<IRange[] | undefined>[] = [];
419
+ for (const rangeProvider of this.rangeProviderMap) {
420
+ const [id, provider] = rangeProvider;
421
+ rangePromise.push(
422
+ (async () => {
423
+ const ranges = await provider.getCommentingRanges(model?.instance!);
424
+ if (ranges && ranges.length) {
425
+ // FIXME: ranges 会被 Diff uri 的两个 range 互相覆盖,导致可能根据行查不到 provider
426
+ this.rangeOwner.set(id, ranges);
427
+ }
428
+ return ranges;
429
+ })(),
430
+ );
431
+ }
432
+ const deferredRes = new Deferred<IRange[]>();
433
+ this.providerDecorationCache.set(uri.toString(), deferredRes);
434
+ const res = await Promise.all(rangePromise);
435
+ // 消除 document 引用
436
+ model?.dispose();
437
+ // 拍平,去掉 undefined
438
+ const flattenRange: IRange[] = flattenDeep(res).filter(Boolean) as IRange[];
439
+ deferredRes.resolve(flattenRange);
440
+ return flattenRange;
441
+ }
442
+
443
+ public fireThreadCommentChange(thread: ICommentsThread) {
444
+ this.threadsCommentChangeEmitter.fire(thread);
445
+ }
446
+
447
+ private registerDecorationProvider() {
448
+ // dispose 掉上一个 decorationProvider
449
+ this.decorationProviderDisposer.dispose();
450
+ this.decorationProviderDisposer = this.editorDecorationCollectionService.registerDecorationProvider({
451
+ schemes: [...this.shouldShowCommentsSchemes.values()],
452
+ key: 'comments',
453
+ onDidDecorationChange: this.decorationChangeEmitter.event,
454
+ provideEditorDecoration: (uri: URI) =>
455
+ this.commentsThreads
456
+ .map((thread) => {
457
+ if (thread.uri.isEqual(uri)) {
458
+ // 恢复之前的现场
459
+ thread.showWidgetsIfShowed();
460
+ } else {
461
+ // 临时隐藏,当切回来时会恢复
462
+ thread.hideWidgetsByDispose();
463
+ }
464
+ return thread;
465
+ })
466
+ .filter((thread) => {
467
+ const isCurrentThread = thread.uri.isEqual(uri);
468
+ if (this.filterThreadDecoration) {
469
+ return isCurrentThread && this.filterThreadDecoration(thread);
470
+ }
471
+ return isCurrentThread;
472
+ })
473
+ .map((thread) => ({
474
+ range: thread.range,
475
+ options: this.createThreadDecoration(thread) as unknown as monaco.editor.IModelDecorationOptions,
476
+ })),
477
+ });
478
+ this.addDispose(this.decorationProviderDisposer);
479
+ }
480
+
481
+ public registerCommentPanel() {
482
+ // 面板只注册一次
483
+ if (this.layoutService.getTabbarHandler(CommentPanelId)) {
484
+ return;
485
+ }
486
+ this.layoutService.collectTabbarComponent(
487
+ [
488
+ {
489
+ id: CommentPanelId,
490
+ component: CommentsPanel,
491
+ },
492
+ ],
493
+ {
494
+ badge: this.panelBadge,
495
+ containerId: CommentPanelId,
496
+ title: localize('comments').toUpperCase(),
497
+ hidden: false,
498
+ activateKeyBinding: 'ctrlcmd+shift+c',
499
+ ...this.commentsFeatureRegistry.getCommentsPanelOptions(),
500
+ },
501
+ 'bottom',
502
+ );
503
+ }
504
+
505
+ get panelBadge() {
506
+ const length = this.commentsThreads.length;
507
+ return length ? length + '' : '';
508
+ }
509
+
510
+ registerCommentRangeProvider(id: string, provider: ICommentRangeProvider): IDisposable {
511
+ this.rangeProviderMap.set(id, provider);
512
+ // 注册一个新的 range provider 后清理掉之前的缓存
513
+ this.providerDecorationCache.clear();
514
+ return Disposable.create(() => {
515
+ this.rangeProviderMap.delete(id);
516
+ this.rangeOwner.delete(id);
517
+ this.providerDecorationCache.clear();
518
+ });
519
+ }
520
+
521
+ forceUpdateDecoration(): void {
522
+ // 默认适应当前 uri 去强刷 decoration
523
+ // 这个值为 core editor 或者 modified editor
524
+ const uri = this.workbenchEditorService.currentEditor?.currentUri;
525
+ uri && this.decorationChangeEmitter.fire(uri);
526
+ // diffeditor 的 originalUri 也需要更新 Decoration
527
+ const originalUri = this.workbenchEditorService.currentEditorGroup?.diffEditor.originalEditor.currentUri;
528
+ originalUri && this.decorationChangeEmitter.fire(originalUri);
529
+ }
530
+
531
+ public getProviderIdsByLine(line: number): string[] {
532
+ const result: string[] = [];
533
+ if (this.rangeOwner.size === 1) {
534
+ // 只有一个provider,直接返回
535
+ return [this.rangeOwner.keys().next().value];
536
+ }
537
+ for (const rangeOwner of this.rangeOwner) {
538
+ const [id, ranges] = rangeOwner;
539
+ if (ranges && ranges.some((range) => range.startLineNumber <= line && line <= range.endLineNumber)) {
540
+ result.push(id);
541
+ }
542
+ }
543
+
544
+ return result;
545
+ }
546
+ }
@@ -0,0 +1,29 @@
1
+ import { Provider, Injectable } from '@opensumi/di';
2
+ import { BrowserModule } from '@opensumi/ide-core-browser';
3
+
4
+ import { CommentsContribution, ICommentsService, ICommentsFeatureRegistry } from '../common';
5
+
6
+ import { CommentsFeatureRegistry } from './comments-feature.registry';
7
+ import { CommentsBrowserContribution } from './comments.contribution';
8
+ import { CommentsService } from './comments.service';
9
+ import { CommentModelService } from './tree/tree-model.service';
10
+
11
+ @Injectable()
12
+ export class CommentsModule extends BrowserModule {
13
+ contributionProvider = CommentsContribution;
14
+ providers: Provider[] = [
15
+ {
16
+ token: ICommentsService,
17
+ useClass: CommentsService,
18
+ },
19
+ {
20
+ token: CommentModelService,
21
+ useClass: CommentModelService,
22
+ },
23
+ {
24
+ token: ICommentsFeatureRegistry,
25
+ useClass: CommentsFeatureRegistry,
26
+ },
27
+ CommentsBrowserContribution,
28
+ ];
29
+ }
@@ -0,0 +1,25 @@
1
+ export const markdownCss = `
2
+ h1, h2, h3, h4, h5, h6, p {
3
+ color: var(--foreground);
4
+ word-break: break-all;
5
+ }
6
+ pre:first-child,
7
+ p:first-child {
8
+ margin-top: 0;
9
+ }
10
+ pre:last-child,
11
+ p:last-child {
12
+ margin-bottom: 0;
13
+ }
14
+ pre {
15
+ background-color: var(--textBlockQuote-background);
16
+ border-color: var(--textBlockQuote-border);
17
+ }
18
+ img {
19
+ max-width: 100%;
20
+ max-height: 100%;
21
+ }
22
+ a {
23
+ color: var(--textLink-foreground);
24
+ }
25
+ `;
@@ -0,0 +1,55 @@
1
+ const lineHeight = 20;
2
+
3
+ export const getMentionBoxStyle = ({ maxRows = 10, minRows = 2 }) => ({
4
+ control: {
5
+ fontSize: 12,
6
+ },
7
+
8
+ highlighter: {
9
+ overflow: 'hidden',
10
+ },
11
+
12
+ input: {
13
+ margin: 0,
14
+ },
15
+
16
+ '&multiLine': {
17
+ control: {
18
+ border: 'none',
19
+ },
20
+
21
+ highlighter: {
22
+ padding: 9,
23
+ },
24
+
25
+ input: {
26
+ boxSizing: 'content-box',
27
+ padding: '8px 0',
28
+ lineHeight: `${lineHeight}px`,
29
+ minHeight: `${lineHeight * minRows}px`,
30
+ maxHeight: `${lineHeight * maxRows}px`,
31
+ outline: 0,
32
+ border: 0,
33
+ overflowY: 'auto',
34
+ },
35
+ },
36
+
37
+ suggestions: {
38
+ dataA: 'aaa',
39
+ list: {
40
+ backgroundColor: 'var(--kt-selectDropdown-background)',
41
+ fontSize: 12,
42
+ maxHeight: 200,
43
+ overflowY: 'auto',
44
+ },
45
+
46
+ item: {
47
+ backgroundColor: 'var(--kt-selectDropdown-background)',
48
+ color: 'var(--kt-selectDropdown-foreground)',
49
+ padding: '4px 16px',
50
+ '&focused': {
51
+ backgroundColor: 'var(--kt-selectDropdown-selectionBackground)',
52
+ },
53
+ },
54
+ },
55
+ });