modesGlyphHover.js 6.7 KB

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