@theia/plugin-ext 1.61.0 → 1.62.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 (85) hide show
  1. package/lib/common/plugin-api-rpc-model.d.ts +2 -2
  2. package/lib/common/plugin-api-rpc-model.d.ts.map +1 -1
  3. package/lib/common/plugin-api-rpc.d.ts +3 -2
  4. package/lib/common/plugin-api-rpc.d.ts.map +1 -1
  5. package/lib/common/plugin-api-rpc.js.map +1 -1
  6. package/lib/main/browser/comments/comment-thread-widget.d.ts +64 -15
  7. package/lib/main/browser/comments/comment-thread-widget.d.ts.map +1 -1
  8. package/lib/main/browser/comments/comment-thread-widget.js +93 -49
  9. package/lib/main/browser/comments/comment-thread-widget.js.map +1 -1
  10. package/lib/main/browser/comments/{comments-context-key-service.d.ts → comments-context.d.ts} +2 -7
  11. package/lib/main/browser/comments/comments-context.d.ts.map +1 -0
  12. package/lib/main/browser/comments/{comments-context-key-service.js → comments-context.js} +8 -25
  13. package/lib/main/browser/comments/comments-context.js.map +1 -0
  14. package/lib/main/browser/comments/comments-contribution.d.ts +2 -2
  15. package/lib/main/browser/comments/comments-contribution.d.ts.map +1 -1
  16. package/lib/main/browser/comments/comments-contribution.js +8 -8
  17. package/lib/main/browser/comments/comments-contribution.js.map +1 -1
  18. package/lib/main/browser/comments/comments-main.d.ts +28 -3
  19. package/lib/main/browser/comments/comments-main.d.ts.map +1 -1
  20. package/lib/main/browser/comments/comments-main.js.map +1 -1
  21. package/lib/main/browser/custom-editors/custom-editor-opener.d.ts.map +1 -1
  22. package/lib/main/browser/custom-editors/custom-editor-opener.js +6 -1
  23. package/lib/main/browser/custom-editors/custom-editor-opener.js.map +1 -1
  24. package/lib/main/browser/documents-main.d.ts +2 -1
  25. package/lib/main/browser/documents-main.d.ts.map +1 -1
  26. package/lib/main/browser/documents-main.js +13 -9
  27. package/lib/main/browser/documents-main.js.map +1 -1
  28. package/lib/main/browser/main-context.d.ts.map +1 -1
  29. package/lib/main/browser/main-context.js +3 -1
  30. package/lib/main/browser/main-context.js.map +1 -1
  31. package/lib/main/browser/menus/menus-contribution-handler.d.ts +7 -13
  32. package/lib/main/browser/menus/menus-contribution-handler.d.ts.map +1 -1
  33. package/lib/main/browser/menus/menus-contribution-handler.js +64 -51
  34. package/lib/main/browser/menus/menus-contribution-handler.js.map +1 -1
  35. package/lib/main/browser/menus/plugin-menu-command-adapter.d.ts +5 -30
  36. package/lib/main/browser/menus/plugin-menu-command-adapter.d.ts.map +1 -1
  37. package/lib/main/browser/menus/plugin-menu-command-adapter.js +7 -110
  38. package/lib/main/browser/menus/plugin-menu-command-adapter.js.map +1 -1
  39. package/lib/main/browser/menus/vscode-theia-menu-mappings.d.ts +4 -4
  40. package/lib/main/browser/menus/vscode-theia-menu-mappings.d.ts.map +1 -1
  41. package/lib/main/browser/menus/vscode-theia-menu-mappings.js +7 -10
  42. package/lib/main/browser/menus/vscode-theia-menu-mappings.js.map +1 -1
  43. package/lib/main/browser/plugin-ext-frontend-module.d.ts.map +1 -1
  44. package/lib/main/browser/plugin-ext-frontend-module.js +2 -4
  45. package/lib/main/browser/plugin-ext-frontend-module.js.map +1 -1
  46. package/lib/main/browser/terminal-main.d.ts.map +1 -1
  47. package/lib/main/browser/terminal-main.js +4 -1
  48. package/lib/main/browser/terminal-main.js.map +1 -1
  49. package/lib/main/browser/text-editor-model-service.d.ts +1 -3
  50. package/lib/main/browser/text-editor-model-service.d.ts.map +1 -1
  51. package/lib/main/browser/text-editor-model-service.js +0 -5
  52. package/lib/main/browser/text-editor-model-service.js.map +1 -1
  53. package/lib/main/browser/view/tree-view-widget.d.ts +2 -2
  54. package/lib/main/browser/view/tree-view-widget.d.ts.map +1 -1
  55. package/lib/main/browser/view/tree-view-widget.js +5 -4
  56. package/lib/main/browser/view/tree-view-widget.js.map +1 -1
  57. package/lib/plugin/comments.d.ts +2 -2
  58. package/lib/plugin/comments.d.ts.map +1 -1
  59. package/lib/plugin/comments.js.map +1 -1
  60. package/lib/plugin/terminal-ext.d.ts +2 -1
  61. package/lib/plugin/terminal-ext.d.ts.map +1 -1
  62. package/lib/plugin/terminal-ext.js +13 -3
  63. package/lib/plugin/terminal-ext.js.map +1 -1
  64. package/package.json +29 -29
  65. package/src/common/plugin-api-rpc-model.ts +2 -2
  66. package/src/common/plugin-api-rpc.ts +3 -2
  67. package/src/main/browser/comments/comment-thread-widget.tsx +177 -85
  68. package/src/main/browser/comments/{comments-context-key-service.ts → comments-context.ts} +1 -20
  69. package/src/main/browser/comments/comments-contribution.ts +5 -5
  70. package/src/main/browser/comments/comments-main.ts +5 -4
  71. package/src/main/browser/custom-editors/custom-editor-opener.tsx +5 -1
  72. package/src/main/browser/documents-main.ts +19 -10
  73. package/src/main/browser/main-context.ts +3 -1
  74. package/src/main/browser/menus/menus-contribution-handler.ts +68 -49
  75. package/src/main/browser/menus/plugin-menu-command-adapter.ts +13 -120
  76. package/src/main/browser/menus/vscode-theia-menu-mappings.ts +4 -6
  77. package/src/main/browser/plugin-ext-frontend-module.ts +2 -4
  78. package/src/main/browser/style/comments.css +9 -1
  79. package/src/main/browser/terminal-main.ts +4 -1
  80. package/src/main/browser/text-editor-model-service.ts +1 -6
  81. package/src/main/browser/view/tree-view-widget.tsx +7 -6
  82. package/src/plugin/comments.ts +4 -4
  83. package/src/plugin/terminal-ext.ts +14 -3
  84. package/lib/main/browser/comments/comments-context-key-service.d.ts.map +0 -1
  85. package/lib/main/browser/comments/comments-context-key-service.js.map +0 -1
@@ -27,16 +27,20 @@ import * as React from '@theia/core/shared/react';
27
27
  import { MouseTargetType } from '@theia/editor/lib/browser';
28
28
  import { CommentsService } from './comments-service';
29
29
  import {
30
- ActionMenuNode,
30
+ CommandMenu,
31
31
  CommandRegistry,
32
32
  CompoundMenuNode,
33
+ isObject,
34
+ DisposableCollection,
33
35
  MenuModelRegistry,
34
36
  MenuPath
35
37
  } from '@theia/core/lib/common';
36
- import { CommentsContextKeyService } from './comments-context-key-service';
38
+ import { CommentsContext } from './comments-context';
37
39
  import { RefObject } from '@theia/core/shared/react';
38
40
  import * as monaco from '@theia/monaco-editor-core';
39
41
  import { createRoot, Root } from '@theia/core/shared/react-dom/client';
42
+ import { CommentAuthorInformation } from '@theia/plugin';
43
+ import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
40
44
 
41
45
  /*---------------------------------------------------------------------------------------------
42
46
  * Copyright (c) Microsoft Corporation. All rights reserved.
@@ -64,7 +68,8 @@ export class CommentThreadWidget extends BaseWidget {
64
68
  private _commentThread: CommentThread,
65
69
  private commentService: CommentsService,
66
70
  protected readonly menus: MenuModelRegistry,
67
- protected readonly contextKeyService: CommentsContextKeyService,
71
+ protected readonly commentsContext: CommentsContext,
72
+ protected readonly contextKeyService: ContextKeyService,
68
73
  protected readonly commands: CommandRegistry
69
74
  ) {
70
75
  super();
@@ -84,14 +89,9 @@ export class CommentThreadWidget extends BaseWidget {
84
89
  return;
85
90
  }
86
91
  }));
87
- this.contextKeyService.commentIsEmpty.set(true);
92
+ this.commentsContext.commentIsEmpty.set(true);
88
93
  this.toDispose.push(this.zoneWidget.editor.onMouseDown(e => this.onEditorMouseDown(e)));
89
- this.toDispose.push(this.contextKeyService.onDidChange(() => {
90
- const commentForm = this.commentFormRef.current;
91
- if (commentForm) {
92
- commentForm.update();
93
- }
94
- }));
94
+
95
95
  this.toDispose.push(this._commentThread.onDidChangeCanReply(_canReply => {
96
96
  const commentForm = this.commentFormRef.current;
97
97
  if (commentForm) {
@@ -102,9 +102,14 @@ export class CommentThreadWidget extends BaseWidget {
102
102
  this.update();
103
103
  }));
104
104
  this.contextMenu = this.menus.getMenu(COMMENT_THREAD_CONTEXT);
105
- this.contextMenu.children.map(node => node instanceof ActionMenuNode && node.when).forEach(exp => {
106
- if (typeof exp === 'string') {
107
- this.contextKeyService.setExpression(exp);
105
+ this.contextMenu.children.forEach(node => {
106
+ if (node.onDidChange) {
107
+ this.toDispose.push(node.onDidChange(() => {
108
+ const commentForm = this.commentFormRef.current;
109
+ if (commentForm) {
110
+ commentForm.update();
111
+ }
112
+ }));
108
113
  }
109
114
  });
110
115
  }
@@ -288,6 +293,7 @@ export class CommentThreadWidget extends BaseWidget {
288
293
  {this._commentThread.comments?.map((comment, index) => <ReviewComment
289
294
  key={index}
290
295
  contextKeyService={this.contextKeyService}
296
+ commentsContext={this.commentsContext}
291
297
  menus={this.menus}
292
298
  comment={comment}
293
299
  commentForm={this.commentFormRef}
@@ -296,6 +302,7 @@ export class CommentThreadWidget extends BaseWidget {
296
302
  />)}
297
303
  </div>
298
304
  <CommentForm contextKeyService={this.contextKeyService}
305
+ commentsContext={this.commentsContext}
299
306
  commands={this.commands}
300
307
  commentThread={this._commentThread}
301
308
  menus={this.menus}
@@ -312,7 +319,8 @@ namespace CommentForm {
312
319
  menus: MenuModelRegistry,
313
320
  commentThread: CommentThread;
314
321
  commands: CommandRegistry;
315
- contextKeyService: CommentsContextKeyService;
322
+ contextKeyService: ContextKeyService;
323
+ commentsContext: CommentsContext;
316
324
  widget: CommentThreadWidget;
317
325
  }
318
326
 
@@ -322,16 +330,16 @@ namespace CommentForm {
322
330
  }
323
331
 
324
332
  export class CommentForm<P extends CommentForm.Props = CommentForm.Props> extends React.Component<P, CommentForm.State> {
325
- private readonly menu: CompoundMenuNode;
326
333
  private inputRef: RefObject<HTMLTextAreaElement> = React.createRef<HTMLTextAreaElement>();
327
334
  private inputValue: string = '';
328
335
  private readonly getInput = () => this.inputValue;
336
+ private toDisposeOnUnmount = new DisposableCollection();
329
337
  private readonly clearInput: () => void = () => {
330
338
  const input = this.inputRef.current;
331
339
  if (input) {
332
340
  this.inputValue = '';
333
341
  input.value = this.inputValue;
334
- this.props.contextKeyService.commentIsEmpty.set(true);
342
+ this.props.commentsContext.commentIsEmpty.set(true);
335
343
  }
336
344
  };
337
345
 
@@ -364,11 +372,15 @@ export class CommentForm<P extends CommentForm.Props = CommentForm.Props> extend
364
372
  }, 100);
365
373
  }
366
374
 
375
+ override componentWillUnmount(): void {
376
+ this.toDisposeOnUnmount.dispose();
377
+ }
378
+
367
379
  private readonly onInput: (event: React.FormEvent) => void = (event: React.FormEvent) => {
368
380
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
369
381
  const value = (event.target as any).value;
370
382
  if (this.inputValue.length === 0 || value.length === 0) {
371
- this.props.contextKeyService.commentIsEmpty.set(value.length === 0);
383
+ this.props.commentsContext.commentIsEmpty.set(value.length === 0);
372
384
  }
373
385
  this.inputValue = value;
374
386
  };
@@ -383,48 +395,106 @@ export class CommentForm<P extends CommentForm.Props = CommentForm.Props> extend
383
395
  this.setState = newState => {
384
396
  setState(newState);
385
397
  };
386
-
387
- this.menu = this.props.menus.getMenu(COMMENT_THREAD_CONTEXT);
388
- this.menu.children.map(node => node instanceof ActionMenuNode && node.when).forEach(exp => {
389
- if (typeof exp === 'string') {
390
- this.props.contextKeyService.setExpression(exp);
391
- }
392
- });
393
398
  }
394
399
 
395
- override render(): React.ReactNode {
396
- const { commands, commentThread, contextKeyService } = this.props;
400
+ /**
401
+ * Renders the comment form with textarea, actions, and reply button.
402
+ *
403
+ * @returns The rendered comment form
404
+ */
405
+ protected renderCommentForm(): React.ReactNode {
406
+ const { commentThread, commentsContext, contextKeyService, menus } = this.props;
397
407
  const hasExistingComments = commentThread.comments && commentThread.comments.length > 0;
398
- return commentThread.canReply ? <div className={'comment-form' + (this.state.expanded || commentThread.comments && commentThread.comments.length === 0 ? ' expand' : '')}>
399
- <div className={'theia-comments-input-message-container'}>
400
- <textarea className={'theia-comments-input-message theia-input'}
401
- spellCheck={false}
402
- placeholder={hasExistingComments ? 'Reply...' : 'Type a new comment'}
403
- onInput={this.onInput}
404
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
405
- onBlur={(event: any) => {
406
- if (event.target.value.length > 0) {
407
- return;
408
- }
409
- if (event.relatedTarget && event.relatedTarget.className === 'comments-button comments-text-button theia-button') {
410
- this.state = { expanded: false };
411
- return;
412
- }
413
- this.collapse();
414
- }}
415
- ref={this.inputRef}>
416
- </textarea>
408
+
409
+ // Determine when to show the expanded form:
410
+ // - When state.expanded is true (user clicked the reply button)
411
+ // - When there are no existing comments (new thread)
412
+ const shouldShowExpanded = this.state.expanded || (commentThread.comments && commentThread.comments.length === 0);
413
+
414
+ return commentThread.canReply ? (
415
+ <div className={`comment-form${shouldShowExpanded ? ' expand' : ''}`}>
416
+ <div className={'theia-comments-input-message-container'}>
417
+ <textarea className={'theia-comments-input-message theia-input'}
418
+ spellCheck={false}
419
+ placeholder={hasExistingComments ? 'Reply...' : 'Type a new comment'}
420
+ onInput={this.onInput}
421
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
422
+ onBlur={(event: any) => {
423
+ if (event.target.value.length > 0) {
424
+ return;
425
+ }
426
+ if (event.relatedTarget && event.relatedTarget.className === 'comments-button comments-text-button theia-button') {
427
+ this.state = { expanded: false };
428
+ return;
429
+ }
430
+ this.collapse();
431
+ }}
432
+ ref={this.inputRef}>
433
+ </textarea>
434
+ </div>
435
+ <CommentActions menu={menus.getMenu(COMMENT_THREAD_CONTEXT)}
436
+ menuPath={[]}
437
+ contextKeyService={contextKeyService}
438
+ commentsContext={commentsContext}
439
+ commentThread={commentThread}
440
+ getInput={this.getInput}
441
+ clearInput={this.clearInput}
442
+ />
443
+ <button className={'review-thread-reply-button'} title={'Reply...'} onClick={this.expand}>Reply...</button>
417
444
  </div>
418
- <CommentActions menu={this.menu}
419
- contextKeyService={contextKeyService}
420
- commands={commands}
421
- commentThread={commentThread}
422
- getInput={this.getInput}
423
- clearInput={this.clearInput}
424
- />
425
- <button className={'review-thread-reply-button'} title={'Reply...'} onClick={this.expand}>Reply...</button>
426
- </div> : null;
445
+ ) : null;
427
446
  }
447
+
448
+ /**
449
+ * Renders the author information section.
450
+ *
451
+ * @param authorInfo The author information to display
452
+ * @returns The rendered author information section
453
+ */
454
+ protected renderAuthorInfo(authorInfo: CommentAuthorInformation): React.ReactNode {
455
+ return (
456
+ <div className={'avatar-container'}>
457
+ {authorInfo.iconPath && (
458
+ <img className={'avatar'} src={authorInfo.iconPath.toString()} />
459
+ )}
460
+ </div>
461
+ );
462
+ }
463
+
464
+ override render(): React.ReactNode {
465
+ const { commentThread } = this.props;
466
+
467
+ if (!commentThread.canReply) {
468
+ return null;
469
+ }
470
+
471
+ // If there's author info, wrap in a container with author info on the left
472
+ if (isCommentAuthorInformation(commentThread.canReply)) {
473
+ return (
474
+ <div className={'review-comment'}>
475
+ {this.renderAuthorInfo(commentThread.canReply)}
476
+ <div className={'review-comment-contents'}>
477
+ <div className={'comment-title monaco-mouse-cursor-text'}>
478
+ <strong className={'author'}>{commentThread.canReply.name}</strong>
479
+ </div>
480
+ {this.renderCommentForm()}
481
+ </div>
482
+ </div>
483
+ );
484
+ }
485
+
486
+ // Otherwise, just return the comment form
487
+ return (
488
+ <div className={'review-comment'}>
489
+ <div className={'review-comment-contents'}>
490
+ {this.renderCommentForm()}
491
+ </div>
492
+ </div>);
493
+ }
494
+ }
495
+
496
+ function isCommentAuthorInformation(item: unknown): item is CommentAuthorInformation {
497
+ return isObject(item) && 'name' in item;
428
498
  }
429
499
 
430
500
  namespace ReviewComment {
@@ -432,7 +502,8 @@ namespace ReviewComment {
432
502
  menus: MenuModelRegistry,
433
503
  comment: Comment;
434
504
  commentThread: CommentThread;
435
- contextKeyService: CommentsContextKeyService;
505
+ contextKeyService: ContextKeyService;
506
+ commentsContext: CommentsContext;
436
507
  commands: CommandRegistry;
437
508
  commentForm: RefObject<CommentForm>;
438
509
  }
@@ -469,10 +540,10 @@ export class ReviewComment<P extends ReviewComment.Props = ReviewComment.Props>
469
540
  protected hideHover = () => this.setState({ hover: false });
470
541
 
471
542
  override render(): React.ReactNode {
472
- const { comment, commentForm, contextKeyService, menus, commands, commentThread } = this.props;
543
+ const { comment, commentForm, contextKeyService, commentsContext, menus, commands, commentThread } = this.props;
473
544
  const commentUniqueId = comment.uniqueIdInThread;
474
545
  const { hover } = this.state;
475
- contextKeyService.comment.set(comment.contextValue);
546
+ commentsContext.comment.set(comment.contextValue);
476
547
  return <div className={'review-comment'}
477
548
  tabIndex={-1}
478
549
  aria-label={`${comment.userName}, ${comment.body.value}`}
@@ -489,14 +560,18 @@ export class ReviewComment<P extends ReviewComment.Props = ReviewComment.Props>
489
560
  <span className={'isPending'}>{comment.label}</span>
490
561
  <div className={'theia-comments-inline-actions-container'}>
491
562
  <div className={'theia-comments-inline-actions'} role={'toolbar'}>
492
- {hover && menus.getMenu(COMMENT_TITLE).children.map((node, index) => node instanceof ActionMenuNode &&
493
- <CommentsInlineAction key={index} {...{ node, commands, commentThread, commentUniqueId, contextKeyService }} />)}
563
+ {hover && menus.getMenuNode(COMMENT_TITLE) && menus.getMenu(COMMENT_TITLE).children.map((node, index): React.ReactNode => CommandMenu.is(node) &&
564
+ <CommentsInlineAction key={index} {...{
565
+ node, nodePath: [...COMMENT_TITLE, node.id], commands, commentThread, commentUniqueId,
566
+ contextKeyService, commentsContext
567
+ }} />)}
494
568
  </div>
495
569
  </div>
496
570
  </div>
497
571
  <CommentBody value={comment.body.value}
498
572
  isVisible={comment.mode === undefined || comment.mode === CommentMode.Preview} />
499
573
  <CommentEditContainer contextKeyService={contextKeyService}
574
+ commentsContext={commentsContext}
500
575
  menus={menus}
501
576
  comment={comment}
502
577
  commentThread={commentThread}
@@ -540,7 +615,8 @@ export class CommentBody extends React.Component<CommentBody.Props> {
540
615
 
541
616
  namespace CommentEditContainer {
542
617
  export interface Props {
543
- contextKeyService: CommentsContextKeyService
618
+ contextKeyService: ContextKeyService;
619
+ commentsContext: CommentsContext;
544
620
  menus: MenuModelRegistry,
545
621
  comment: Comment;
546
622
  commentThread: CommentThread;
@@ -572,7 +648,7 @@ export class CommentEditContainer extends React.Component<CommentEditContainer.P
572
648
  }
573
649
 
574
650
  override render(): React.ReactNode {
575
- const { menus, comment, commands, commentThread, contextKeyService } = this.props;
651
+ const { menus, comment, commands, commentThread, contextKeyService, commentsContext } = this.props;
576
652
  if (!(comment.mode === CommentMode.Editing)) {
577
653
  return false;
578
654
  }
@@ -586,7 +662,7 @@ export class CommentEditContainer extends React.Component<CommentEditContainer.P
586
662
  </div>
587
663
  </div>
588
664
  <div className={'form-actions'}>
589
- {menus.getMenu(COMMENT_CONTEXT).children.map((node, index) => {
665
+ {menus.getMenu(COMMENT_CONTEXT).children.map((node, index): React.ReactNode => {
590
666
  const onClick = () => {
591
667
  commands.executeCommand(node.id, {
592
668
  commentControlHandle: commentThread.controllerHandle,
@@ -595,8 +671,11 @@ export class CommentEditContainer extends React.Component<CommentEditContainer.P
595
671
  text: this.inputRef.current ? this.inputRef.current.value : ''
596
672
  });
597
673
  };
598
- return node instanceof ActionMenuNode &&
599
- <CommentAction key={index} {...{ node, commands, onClick, contextKeyService }} />;
674
+ return CommandMenu.is(node) &&
675
+ <CommentAction key={index} {...{
676
+ node, nodePath: [...COMMENT_CONTEXT, node.id], comment,
677
+ commands, onClick, contextKeyService, commentsContext, commentThread
678
+ }} />;
600
679
  }
601
680
  )}
602
681
  </div>
@@ -606,18 +685,23 @@ export class CommentEditContainer extends React.Component<CommentEditContainer.P
606
685
 
607
686
  namespace CommentsInlineAction {
608
687
  export interface Props {
609
- node: ActionMenuNode;
688
+ nodePath: MenuPath,
689
+ node: CommandMenu;
610
690
  commentThread: CommentThread;
611
691
  commentUniqueId: number;
612
692
  commands: CommandRegistry;
613
- contextKeyService: CommentsContextKeyService;
693
+ contextKeyService: ContextKeyService;
694
+ commentsContext: CommentsContext;
614
695
  }
615
696
  }
616
697
 
617
698
  export class CommentsInlineAction extends React.Component<CommentsInlineAction.Props> {
618
699
  override render(): React.ReactNode {
619
- const { node, commands, contextKeyService, commentThread, commentUniqueId } = this.props;
620
- if (node.when && !contextKeyService.match(node.when)) {
700
+ const { node, nodePath, commands, contextKeyService, commentThread, commentUniqueId } = this.props;
701
+ if (node.isVisible(nodePath, contextKeyService, undefined, {
702
+ thread: commentThread,
703
+ commentUniqueId
704
+ })) {
621
705
  return false;
622
706
  }
623
707
  return <div className='theia-comments-inline-action'>
@@ -625,9 +709,8 @@ export class CommentsInlineAction extends React.Component<CommentsInlineAction.P
625
709
  title={node.label}
626
710
  onClick={() => {
627
711
  commands.executeCommand(node.id, {
628
- commentControlHandle: commentThread.controllerHandle,
629
- commentThreadHandle: commentThread.commentThreadHandle,
630
- commentUniqueId
712
+ thread: commentThread,
713
+ commentUniqueId: commentUniqueId
631
714
  });
632
715
  }} />
633
716
  </div>;
@@ -636,8 +719,9 @@ export class CommentsInlineAction extends React.Component<CommentsInlineAction.P
636
719
 
637
720
  namespace CommentActions {
638
721
  export interface Props {
639
- contextKeyService: CommentsContextKeyService;
640
- commands: CommandRegistry;
722
+ contextKeyService: ContextKeyService;
723
+ commentsContext: CommentsContext;
724
+ menuPath: MenuPath,
641
725
  menu: CompoundMenuNode;
642
726
  commentThread: CommentThread;
643
727
  getInput: () => string;
@@ -647,30 +731,34 @@ namespace CommentActions {
647
731
 
648
732
  export class CommentActions extends React.Component<CommentActions.Props> {
649
733
  override render(): React.ReactNode {
650
- const { contextKeyService, commands, menu, commentThread, getInput, clearInput } = this.props;
734
+ const { contextKeyService, commentsContext, menuPath, menu, commentThread, getInput, clearInput } = this.props;
651
735
  return <div className={'form-actions'}>
652
- {menu.children.map((node, index) => node instanceof ActionMenuNode &&
736
+ {menu.children.map((node, index) => CommandMenu.is(node) &&
653
737
  <CommentAction key={index}
654
- commands={commands}
738
+ nodePath={menuPath}
655
739
  node={node}
656
740
  onClick={() => {
657
- commands.executeCommand(node.id, {
658
- commentControlHandle: commentThread.controllerHandle,
659
- commentThreadHandle: commentThread.commentThreadHandle,
741
+ node.run(
742
+ [...menuPath, menu.id], {
743
+ thread: commentThread,
660
744
  text: getInput()
661
745
  });
662
746
  clearInput();
663
747
  }}
748
+ commentThread={commentThread}
664
749
  contextKeyService={contextKeyService}
750
+ commentsContext={commentsContext}
665
751
  />)}
666
752
  </div>;
667
753
  }
668
754
  }
669
755
  namespace CommentAction {
670
756
  export interface Props {
671
- contextKeyService: CommentsContextKeyService;
672
- commands: CommandRegistry;
673
- node: ActionMenuNode;
757
+ commentThread: CommentThread;
758
+ contextKeyService: ContextKeyService;
759
+ commentsContext: CommentsContext;
760
+ nodePath: MenuPath,
761
+ node: CommandMenu;
674
762
  onClick: () => void;
675
763
  }
676
764
  }
@@ -678,11 +766,15 @@ namespace CommentAction {
678
766
  export class CommentAction extends React.Component<CommentAction.Props> {
679
767
  override render(): React.ReactNode {
680
768
  const classNames = ['comments-button', 'comments-text-button', 'theia-button'];
681
- const { node, commands, contextKeyService, onClick } = this.props;
682
- if (node.when && !contextKeyService.match(node.when)) {
769
+ const { node, nodePath, contextKeyService, onClick, commentThread } = this.props;
770
+ if (!node.isVisible(nodePath, contextKeyService, undefined, {
771
+ thread: commentThread
772
+ })) {
683
773
  return false;
684
774
  }
685
- const isEnabled = commands.isEnabled(node.command);
775
+ const isEnabled = node.isEnabled(nodePath, {
776
+ thread: commentThread
777
+ });
686
778
  if (!isEnabled) {
687
779
  classNames.push(DISABLED_CLASS);
688
780
  }
@@ -16,16 +16,13 @@
16
16
 
17
17
  import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
18
18
  import { ContextKeyService, ContextKey } from '@theia/core/lib/browser/context-key-service';
19
- import { Emitter } from '@theia/core/lib/common';
20
19
 
21
20
  @injectable()
22
- export class CommentsContextKeyService {
21
+ export class CommentsContext {
23
22
 
24
23
  @inject(ContextKeyService)
25
24
  protected readonly contextKeyService: ContextKeyService;
26
25
  protected readonly contextKeys: Set<string> = new Set();
27
- protected readonly onDidChangeEmitter = new Emitter<void>();
28
- readonly onDidChange = this.onDidChangeEmitter.event;
29
26
  protected _commentIsEmpty: ContextKey<boolean>;
30
27
  protected _commentController: ContextKey<string | undefined>;
31
28
  protected _comment: ContextKey<string | undefined>;
@@ -48,21 +45,5 @@ export class CommentsContextKeyService {
48
45
  this._commentController = this.contextKeyService.createKey<string | undefined>('commentController', undefined);
49
46
  this._comment = this.contextKeyService.createKey<string | undefined>('comment', undefined);
50
47
  this._commentIsEmpty = this.contextKeyService.createKey<boolean>('commentIsEmpty', true);
51
- this.contextKeyService.onDidChange(event => {
52
- if (event.affects(this.contextKeys)) {
53
- this.onDidChangeEmitter.fire();
54
- }
55
- });
56
48
  }
57
-
58
- setExpression(expression: string): void {
59
- this.contextKeyService.parseKeys(expression)?.forEach(key => {
60
- this.contextKeys.add(key);
61
- });
62
- }
63
-
64
- match(expression: string | undefined): boolean {
65
- return !expression || this.contextKeyService.match(expression);
66
- }
67
-
68
49
  }
@@ -24,9 +24,9 @@ import { CommentsService, CommentInfoMain } from './comments-service';
24
24
  import { CommentThread } from '../../../common/plugin-api-rpc-model';
25
25
  import { CommandRegistry, DisposableCollection, MenuModelRegistry } from '@theia/core/lib/common';
26
26
  import { URI } from '@theia/core/shared/vscode-uri';
27
- import { CommentsContextKeyService } from './comments-context-key-service';
28
27
  import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
29
28
  import { Uri } from '@theia/plugin';
29
+ import { CommentsContext } from './comments-context';
30
30
 
31
31
  /*---------------------------------------------------------------------------------------------
32
32
  * Copyright (c) Microsoft Corporation. All rights reserved.
@@ -43,7 +43,7 @@ export class CommentsContribution {
43
43
  private emptyThreadsToAddQueue: [number, EditorMouseEvent | undefined][] = [];
44
44
 
45
45
  @inject(MenuModelRegistry) protected readonly menus: MenuModelRegistry;
46
- @inject(CommentsContextKeyService) protected readonly commentsContextKeyService: CommentsContextKeyService;
46
+ @inject(CommentsContext) protected readonly commentsContext: CommentsContext;
47
47
  @inject(ContextKeyService) protected readonly contextKeyService: ContextKeyService;
48
48
  @inject(CommandRegistry) protected readonly commands: CommandRegistry;
49
49
 
@@ -193,10 +193,10 @@ export class CommentsContribution {
193
193
  if (editor) {
194
194
  const provider = this.commentService.getCommentController(owner);
195
195
  if (provider) {
196
- this.commentsContextKeyService.commentController.set(provider.id);
196
+ this.commentsContext.commentController.set(provider.id);
197
197
  }
198
- const zoneWidget = new CommentThreadWidget(editor, owner, thread, this.commentService, this.menus, this.commentsContextKeyService, this.commands);
199
- zoneWidget.display({ afterLineNumber: thread.range?.startLineNumber ?? 0, heightInLines: 5 }); // messages with no range are put on top of the editor
198
+ const zoneWidget = new CommentThreadWidget(editor, owner, thread, this.commentService, this.menus, this.commentsContext, this.contextKeyService, this.commands);
199
+ zoneWidget.display({ afterLineNumber: thread.range?.startLineNumber || 0, heightInLines: 5 });
200
200
  const currentEditor = this.getCurrentEditor();
201
201
  if (currentEditor) {
202
202
  currentEditor.onDispose(() => zoneWidget.dispose());
@@ -40,6 +40,7 @@ import { RPCProtocol } from '../../../common/rpc-protocol';
40
40
  import { interfaces } from '@theia/core/shared/inversify';
41
41
  import { generateUuid } from '@theia/core/lib/common/uuid';
42
42
  import { CommentsContribution } from './comments-contribution';
43
+ import { CommentAuthorInformation } from '@theia/plugin';
43
44
 
44
45
  /*---------------------------------------------------------------------------------------------
45
46
  * Copyright (c) Microsoft Corporation. All rights reserved.
@@ -139,7 +140,7 @@ export class CommentThreadImpl implements CommentThread, Disposable {
139
140
  private readonly onDidChangeStateEmitter = new Emitter<CommentThreadState | undefined>();
140
141
  readonly onDidChangeState = this.onDidChangeStateEmitter.event;
141
142
 
142
- private readonly onDidChangeCanReplyEmitter = new Emitter<boolean>();
143
+ private readonly onDidChangeCanReplyEmitter = new Emitter<boolean | CommentAuthorInformation>();
143
144
  readonly onDidChangeCanReply = this.onDidChangeCanReplyEmitter.event;
144
145
 
145
146
  private _isDisposed: boolean;
@@ -148,12 +149,12 @@ export class CommentThreadImpl implements CommentThread, Disposable {
148
149
  return this._isDisposed;
149
150
  }
150
151
 
151
- private _canReply: boolean = true;
152
- get canReply(): boolean {
152
+ private _canReply: boolean | CommentAuthorInformation = true;
153
+ get canReply(): boolean | CommentAuthorInformation {
153
154
  return this._canReply;
154
155
  }
155
156
 
156
- set canReply(canReply: boolean) {
157
+ set canReply(canReply: boolean | CommentAuthorInformation) {
157
158
  this._canReply = canReply;
158
159
  this.onDidChangeCanReplyEmitter.fire(this._canReply);
159
160
  }
@@ -54,7 +54,11 @@ export class CustomEditorOpener implements OpenHandler {
54
54
  if (DiffUris.isDiffUri(uri)) {
55
55
  const [left, right] = DiffUris.decode(uri);
56
56
  if (this.matches(selector, right) && this.matches(selector, left)) {
57
- priority = this.getPriority();
57
+ if (getDefaultHandler(right, this.preferenceService) === this.editor.viewType) {
58
+ priority = defaultHandlerPriority;
59
+ } else {
60
+ priority = this.getPriority();
61
+ }
58
62
  }
59
63
  } else if (this.matches(selector, uri)) {
60
64
  if (getDefaultHandler(uri, this.preferenceService) === this.editor.viewType) {