codelensWidget.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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 { renderLabelWithIcons } from '../../../base/browser/ui/iconLabel/iconLabels.js';
  7. import './codelensWidget.css';
  8. import { Range } from '../../common/core/range.js';
  9. import { ModelDecorationOptions } from '../../common/model/textModel.js';
  10. class CodeLensViewZone {
  11. constructor(afterLineNumber, heightInPx, onHeight) {
  12. this.afterLineNumber = afterLineNumber;
  13. this.heightInPx = heightInPx;
  14. this._onHeight = onHeight;
  15. this.suppressMouseDown = true;
  16. this.domNode = document.createElement('div');
  17. }
  18. onComputedHeight(height) {
  19. if (this._lastHeight === undefined) {
  20. this._lastHeight = height;
  21. }
  22. else if (this._lastHeight !== height) {
  23. this._lastHeight = height;
  24. this._onHeight();
  25. }
  26. }
  27. isVisible() {
  28. return this._lastHeight !== 0
  29. && this.domNode.hasAttribute('monaco-visible-view-zone');
  30. }
  31. }
  32. class CodeLensContentWidget {
  33. constructor(editor, className, line) {
  34. // Editor.IContentWidget.allowEditorOverflow
  35. this.allowEditorOverflow = false;
  36. this.suppressMouseDown = true;
  37. this._commands = new Map();
  38. this._isEmpty = true;
  39. this._editor = editor;
  40. this._id = `codelens.widget-${(CodeLensContentWidget._idPool++)}`;
  41. this.updatePosition(line);
  42. this._domNode = document.createElement('span');
  43. this._domNode.className = `codelens-decoration ${className}`;
  44. }
  45. withCommands(lenses, animate) {
  46. this._commands.clear();
  47. let children = [];
  48. let hasSymbol = false;
  49. for (let i = 0; i < lenses.length; i++) {
  50. const lens = lenses[i];
  51. if (!lens) {
  52. continue;
  53. }
  54. hasSymbol = true;
  55. if (lens.command) {
  56. const title = renderLabelWithIcons(lens.command.title.trim());
  57. if (lens.command.id) {
  58. children.push(dom.$('a', { id: String(i), title: lens.command.tooltip }, ...title));
  59. this._commands.set(String(i), lens.command);
  60. }
  61. else {
  62. children.push(dom.$('span', { title: lens.command.tooltip }, ...title));
  63. }
  64. if (i + 1 < lenses.length) {
  65. children.push(dom.$('span', undefined, '\u00a0|\u00a0'));
  66. }
  67. }
  68. }
  69. if (!hasSymbol) {
  70. // symbols but no commands
  71. dom.reset(this._domNode, dom.$('span', undefined, 'no commands'));
  72. }
  73. else {
  74. // symbols and commands
  75. dom.reset(this._domNode, ...children);
  76. if (this._isEmpty && animate) {
  77. this._domNode.classList.add('fadein');
  78. }
  79. this._isEmpty = false;
  80. }
  81. }
  82. getCommand(link) {
  83. return link.parentElement === this._domNode
  84. ? this._commands.get(link.id)
  85. : undefined;
  86. }
  87. getId() {
  88. return this._id;
  89. }
  90. getDomNode() {
  91. return this._domNode;
  92. }
  93. updatePosition(line) {
  94. const column = this._editor.getModel().getLineFirstNonWhitespaceColumn(line);
  95. this._widgetPosition = {
  96. position: { lineNumber: line, column: column },
  97. preference: [1 /* ABOVE */]
  98. };
  99. }
  100. getPosition() {
  101. return this._widgetPosition || null;
  102. }
  103. }
  104. CodeLensContentWidget._idPool = 0;
  105. export class CodeLensHelper {
  106. constructor() {
  107. this._removeDecorations = [];
  108. this._addDecorations = [];
  109. this._addDecorationsCallbacks = [];
  110. }
  111. addDecoration(decoration, callback) {
  112. this._addDecorations.push(decoration);
  113. this._addDecorationsCallbacks.push(callback);
  114. }
  115. removeDecoration(decorationId) {
  116. this._removeDecorations.push(decorationId);
  117. }
  118. commit(changeAccessor) {
  119. let resultingDecorations = changeAccessor.deltaDecorations(this._removeDecorations, this._addDecorations);
  120. for (let i = 0, len = resultingDecorations.length; i < len; i++) {
  121. this._addDecorationsCallbacks[i](resultingDecorations[i]);
  122. }
  123. }
  124. }
  125. export class CodeLensWidget {
  126. constructor(data, editor, className, helper, viewZoneChangeAccessor, heightInPx, updateCallback) {
  127. this._isDisposed = false;
  128. this._editor = editor;
  129. this._className = className;
  130. this._data = data;
  131. // create combined range, track all ranges with decorations,
  132. // check if there is already something to render
  133. this._decorationIds = [];
  134. let range;
  135. let lenses = [];
  136. this._data.forEach((codeLensData, i) => {
  137. if (codeLensData.symbol.command) {
  138. lenses.push(codeLensData.symbol);
  139. }
  140. helper.addDecoration({
  141. range: codeLensData.symbol.range,
  142. options: ModelDecorationOptions.EMPTY
  143. }, id => this._decorationIds[i] = id);
  144. // the range contains all lenses on this line
  145. if (!range) {
  146. range = Range.lift(codeLensData.symbol.range);
  147. }
  148. else {
  149. range = Range.plusRange(range, codeLensData.symbol.range);
  150. }
  151. });
  152. this._viewZone = new CodeLensViewZone(range.startLineNumber - 1, heightInPx, updateCallback);
  153. this._viewZoneId = viewZoneChangeAccessor.addZone(this._viewZone);
  154. if (lenses.length > 0) {
  155. this._createContentWidgetIfNecessary();
  156. this._contentWidget.withCommands(lenses, false);
  157. }
  158. }
  159. _createContentWidgetIfNecessary() {
  160. if (!this._contentWidget) {
  161. this._contentWidget = new CodeLensContentWidget(this._editor, this._className, this._viewZone.afterLineNumber + 1);
  162. this._editor.addContentWidget(this._contentWidget);
  163. }
  164. else {
  165. this._editor.layoutContentWidget(this._contentWidget);
  166. }
  167. }
  168. dispose(helper, viewZoneChangeAccessor) {
  169. this._decorationIds.forEach(helper.removeDecoration, helper);
  170. this._decorationIds = [];
  171. if (viewZoneChangeAccessor) {
  172. viewZoneChangeAccessor.removeZone(this._viewZoneId);
  173. }
  174. if (this._contentWidget) {
  175. this._editor.removeContentWidget(this._contentWidget);
  176. this._contentWidget = undefined;
  177. }
  178. this._isDisposed = true;
  179. }
  180. isDisposed() {
  181. return this._isDisposed;
  182. }
  183. isValid() {
  184. return this._decorationIds.some((id, i) => {
  185. const range = this._editor.getModel().getDecorationRange(id);
  186. const symbol = this._data[i].symbol;
  187. return !!(range && Range.isEmpty(symbol.range) === range.isEmpty());
  188. });
  189. }
  190. updateCodeLensSymbols(data, helper) {
  191. this._decorationIds.forEach(helper.removeDecoration, helper);
  192. this._decorationIds = [];
  193. this._data = data;
  194. this._data.forEach((codeLensData, i) => {
  195. helper.addDecoration({
  196. range: codeLensData.symbol.range,
  197. options: ModelDecorationOptions.EMPTY
  198. }, id => this._decorationIds[i] = id);
  199. });
  200. }
  201. updateHeight(height, viewZoneChangeAccessor) {
  202. this._viewZone.heightInPx = height;
  203. viewZoneChangeAccessor.layoutZone(this._viewZoneId);
  204. if (this._contentWidget) {
  205. this._editor.layoutContentWidget(this._contentWidget);
  206. }
  207. }
  208. computeIfNecessary(model) {
  209. if (!this._viewZone.isVisible()) {
  210. return null;
  211. }
  212. // Read editor current state
  213. for (let i = 0; i < this._decorationIds.length; i++) {
  214. const range = model.getDecorationRange(this._decorationIds[i]);
  215. if (range) {
  216. this._data[i].symbol.range = range;
  217. }
  218. }
  219. return this._data;
  220. }
  221. updateCommands(symbols) {
  222. this._createContentWidgetIfNecessary();
  223. this._contentWidget.withCommands(symbols, true);
  224. for (let i = 0; i < this._data.length; i++) {
  225. const resolved = symbols[i];
  226. if (resolved) {
  227. const { symbol } = this._data[i];
  228. symbol.command = resolved.command || symbol.command;
  229. }
  230. }
  231. }
  232. getCommand(link) {
  233. var _a;
  234. return (_a = this._contentWidget) === null || _a === void 0 ? void 0 : _a.getCommand(link);
  235. }
  236. getLineNumber() {
  237. const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]);
  238. if (range) {
  239. return range.startLineNumber;
  240. }
  241. return -1;
  242. }
  243. update(viewZoneChangeAccessor) {
  244. if (this.isValid()) {
  245. const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]);
  246. if (range) {
  247. this._viewZone.afterLineNumber = range.startLineNumber - 1;
  248. viewZoneChangeAccessor.layoutZone(this._viewZoneId);
  249. if (this._contentWidget) {
  250. this._contentWidget.updatePosition(range.startLineNumber);
  251. this._editor.layoutContentWidget(this._contentWidget);
  252. }
  253. }
  254. }
  255. }
  256. getItems() {
  257. return this._data;
  258. }
  259. }