eb5d5c10b35c0ea7e7108f3212e495d5d4af8798aafdcc26b9c4666c8363dbc8e854266e71025649de7988864e80df47df9915adfdaf79fa7e45cd17063e53 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. import * as dom from '../../../../base/browser/dom.js';
  6. import { asArray } from '../../../../base/common/arrays.js';
  7. import { isEmptyMarkdownString } from '../../../../base/common/htmlContent.js';
  8. import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
  9. import { MarkdownRenderer } from '../../markdownRenderer/browser/markdownRenderer.js';
  10. import { HoverOperation } from './hoverOperation.js';
  11. import { NullOpenerService } from '../../../../platform/opener/common/opener.js';
  12. import { HoverWidget } from '../../../../base/browser/ui/hover/hoverWidget.js';
  13. const $ = dom.$;
  14. export class MarginHoverWidget extends Disposable {
  15. constructor(editor, languageService, openerService = NullOpenerService) {
  16. super();
  17. this._renderDisposeables = this._register(new DisposableStore());
  18. this._editor = editor;
  19. this._isVisible = false;
  20. this._messages = [];
  21. this._hover = this._register(new HoverWidget());
  22. this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible);
  23. this._markdownRenderer = this._register(new MarkdownRenderer({ editor: this._editor }, languageService, openerService));
  24. this._computer = new MarginHoverComputer(this._editor);
  25. this._hoverOperation = this._register(new HoverOperation(this._editor, this._computer));
  26. this._register(this._hoverOperation.onResult((result) => {
  27. this._withResult(result.value);
  28. }));
  29. this._register(this._editor.onDidChangeModelDecorations(() => this._onModelDecorationsChanged()));
  30. this._register(this._editor.onDidChangeConfiguration((e) => {
  31. if (e.hasChanged(46 /* EditorOption.fontInfo */)) {
  32. this._updateFont();
  33. }
  34. }));
  35. this._editor.addOverlayWidget(this);
  36. }
  37. dispose() {
  38. this._editor.removeOverlayWidget(this);
  39. super.dispose();
  40. }
  41. getId() {
  42. return MarginHoverWidget.ID;
  43. }
  44. getDomNode() {
  45. return this._hover.containerDomNode;
  46. }
  47. getPosition() {
  48. return null;
  49. }
  50. _updateFont() {
  51. const codeClasses = Array.prototype.slice.call(this._hover.contentsDomNode.getElementsByClassName('code'));
  52. codeClasses.forEach(node => this._editor.applyFontInfo(node));
  53. }
  54. _onModelDecorationsChanged() {
  55. if (this._isVisible) {
  56. // The decorations have changed and the hover is visible,
  57. // we need to recompute the displayed text
  58. this._hoverOperation.cancel();
  59. this._hoverOperation.start(0 /* HoverStartMode.Delayed */);
  60. }
  61. }
  62. startShowingAt(lineNumber) {
  63. if (this._computer.lineNumber === lineNumber) {
  64. // We have to show the widget at the exact same line number as before, so no work is needed
  65. return;
  66. }
  67. this._hoverOperation.cancel();
  68. this.hide();
  69. this._computer.lineNumber = lineNumber;
  70. this._hoverOperation.start(0 /* HoverStartMode.Delayed */);
  71. }
  72. hide() {
  73. this._computer.lineNumber = -1;
  74. this._hoverOperation.cancel();
  75. if (!this._isVisible) {
  76. return;
  77. }
  78. this._isVisible = false;
  79. this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible);
  80. }
  81. _withResult(result) {
  82. this._messages = result;
  83. if (this._messages.length > 0) {
  84. this._renderMessages(this._computer.lineNumber, this._messages);
  85. }
  86. else {
  87. this.hide();
  88. }
  89. }
  90. _renderMessages(lineNumber, messages) {
  91. this._renderDisposeables.clear();
  92. const fragment = document.createDocumentFragment();
  93. for (const msg of messages) {
  94. const markdownHoverElement = $('div.hover-row.markdown-hover');
  95. const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents'));
  96. const renderedContents = this._renderDisposeables.add(this._markdownRenderer.render(msg.value));
  97. hoverContentsElement.appendChild(renderedContents.element);
  98. fragment.appendChild(markdownHoverElement);
  99. }
  100. this._updateContents(fragment);
  101. this._showAt(lineNumber);
  102. }
  103. _updateContents(node) {
  104. this._hover.contentsDomNode.textContent = '';
  105. this._hover.contentsDomNode.appendChild(node);
  106. this._updateFont();
  107. }
  108. _showAt(lineNumber) {
  109. if (!this._isVisible) {
  110. this._isVisible = true;
  111. this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible);
  112. }
  113. const editorLayout = this._editor.getLayoutInfo();
  114. const topForLineNumber = this._editor.getTopForLineNumber(lineNumber);
  115. const editorScrollTop = this._editor.getScrollTop();
  116. const lineHeight = this._editor.getOption(61 /* EditorOption.lineHeight */);
  117. const nodeHeight = this._hover.containerDomNode.clientHeight;
  118. const top = topForLineNumber - editorScrollTop - ((nodeHeight - lineHeight) / 2);
  119. this._hover.containerDomNode.style.left = `${editorLayout.glyphMarginLeft + editorLayout.glyphMarginWidth}px`;
  120. this._hover.containerDomNode.style.top = `${Math.max(Math.round(top), 0)}px`;
  121. }
  122. }
  123. MarginHoverWidget.ID = 'editor.contrib.modesGlyphHoverWidget';
  124. class MarginHoverComputer {
  125. constructor(_editor) {
  126. this._editor = _editor;
  127. this._lineNumber = -1;
  128. }
  129. get lineNumber() {
  130. return this._lineNumber;
  131. }
  132. set lineNumber(value) {
  133. this._lineNumber = value;
  134. }
  135. computeSync() {
  136. const toHoverMessage = (contents) => {
  137. return {
  138. value: contents
  139. };
  140. };
  141. const lineDecorations = this._editor.getLineDecorations(this._lineNumber);
  142. const result = [];
  143. if (!lineDecorations) {
  144. return result;
  145. }
  146. for (const d of lineDecorations) {
  147. if (!d.options.glyphMarginClassName) {
  148. continue;
  149. }
  150. const hoverMessage = d.options.glyphMarginHoverMessage;
  151. if (!hoverMessage || isEmptyMarkdownString(hoverMessage)) {
  152. continue;
  153. }
  154. result.push(...asArray(hoverMessage).map(toHoverMessage));
  155. }
  156. return result;
  157. }
  158. }