@opensumi/ide-comments 2.21.7-rc-1670246007.0 → 2.21.7

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