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