40920204d571ca339203fa007c2b18d27a7255e678a58fc1a3b9dd7691033d6ecd067571c9ad9598a75bd1c036d2f9807fa3d26c74ff2249ee7ad7b9b6e9af 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  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 { createFastDomNode } from '../../../../base/browser/fastDomNode.js';
  7. import { PartFingerprints, ViewPart } from '../../view/viewPart.js';
  8. class Coordinate {
  9. constructor(top, left) {
  10. this._coordinateBrand = undefined;
  11. this.top = top;
  12. this.left = left;
  13. }
  14. }
  15. export class ViewContentWidgets extends ViewPart {
  16. constructor(context, viewDomNode) {
  17. super(context);
  18. this._viewDomNode = viewDomNode;
  19. this._widgets = {};
  20. this.domNode = createFastDomNode(document.createElement('div'));
  21. PartFingerprints.write(this.domNode, 1 /* PartFingerprint.ContentWidgets */);
  22. this.domNode.setClassName('contentWidgets');
  23. this.domNode.setPosition('absolute');
  24. this.domNode.setTop(0);
  25. this.overflowingContentWidgetsDomNode = createFastDomNode(document.createElement('div'));
  26. PartFingerprints.write(this.overflowingContentWidgetsDomNode, 2 /* PartFingerprint.OverflowingContentWidgets */);
  27. this.overflowingContentWidgetsDomNode.setClassName('overflowingContentWidgets');
  28. }
  29. dispose() {
  30. super.dispose();
  31. this._widgets = {};
  32. }
  33. // --- begin event handlers
  34. onConfigurationChanged(e) {
  35. const keys = Object.keys(this._widgets);
  36. for (const widgetId of keys) {
  37. this._widgets[widgetId].onConfigurationChanged(e);
  38. }
  39. return true;
  40. }
  41. onDecorationsChanged(e) {
  42. // true for inline decorations that can end up relayouting text
  43. return true;
  44. }
  45. onFlushed(e) {
  46. return true;
  47. }
  48. onLineMappingChanged(e) {
  49. const keys = Object.keys(this._widgets);
  50. for (const widgetId of keys) {
  51. this._widgets[widgetId].onLineMappingChanged(e);
  52. }
  53. return true;
  54. }
  55. onLinesChanged(e) {
  56. return true;
  57. }
  58. onLinesDeleted(e) {
  59. return true;
  60. }
  61. onLinesInserted(e) {
  62. return true;
  63. }
  64. onScrollChanged(e) {
  65. return true;
  66. }
  67. onZonesChanged(e) {
  68. return true;
  69. }
  70. // ---- end view event handlers
  71. addWidget(_widget) {
  72. const myWidget = new Widget(this._context, this._viewDomNode, _widget);
  73. this._widgets[myWidget.id] = myWidget;
  74. if (myWidget.allowEditorOverflow) {
  75. this.overflowingContentWidgetsDomNode.appendChild(myWidget.domNode);
  76. }
  77. else {
  78. this.domNode.appendChild(myWidget.domNode);
  79. }
  80. this.setShouldRender();
  81. }
  82. setWidgetPosition(widget, range, preference, affinity) {
  83. const myWidget = this._widgets[widget.getId()];
  84. myWidget.setPosition(range, preference, affinity);
  85. this.setShouldRender();
  86. }
  87. removeWidget(widget) {
  88. const widgetId = widget.getId();
  89. if (this._widgets.hasOwnProperty(widgetId)) {
  90. const myWidget = this._widgets[widgetId];
  91. delete this._widgets[widgetId];
  92. const domNode = myWidget.domNode.domNode;
  93. domNode.parentNode.removeChild(domNode);
  94. domNode.removeAttribute('monaco-visible-content-widget');
  95. this.setShouldRender();
  96. }
  97. }
  98. shouldSuppressMouseDownOnWidget(widgetId) {
  99. if (this._widgets.hasOwnProperty(widgetId)) {
  100. return this._widgets[widgetId].suppressMouseDown;
  101. }
  102. return false;
  103. }
  104. onBeforeRender(viewportData) {
  105. const keys = Object.keys(this._widgets);
  106. for (const widgetId of keys) {
  107. this._widgets[widgetId].onBeforeRender(viewportData);
  108. }
  109. }
  110. prepareRender(ctx) {
  111. const keys = Object.keys(this._widgets);
  112. for (const widgetId of keys) {
  113. this._widgets[widgetId].prepareRender(ctx);
  114. }
  115. }
  116. render(ctx) {
  117. const keys = Object.keys(this._widgets);
  118. for (const widgetId of keys) {
  119. this._widgets[widgetId].render(ctx);
  120. }
  121. }
  122. }
  123. class Widget {
  124. constructor(context, viewDomNode, actual) {
  125. this._context = context;
  126. this._viewDomNode = viewDomNode;
  127. this._actual = actual;
  128. this.domNode = createFastDomNode(this._actual.getDomNode());
  129. this.id = this._actual.getId();
  130. this.allowEditorOverflow = this._actual.allowEditorOverflow || false;
  131. this.suppressMouseDown = this._actual.suppressMouseDown || false;
  132. const options = this._context.configuration.options;
  133. const layoutInfo = options.get(133 /* EditorOption.layoutInfo */);
  134. this._fixedOverflowWidgets = options.get(38 /* EditorOption.fixedOverflowWidgets */);
  135. this._contentWidth = layoutInfo.contentWidth;
  136. this._contentLeft = layoutInfo.contentLeft;
  137. this._lineHeight = options.get(61 /* EditorOption.lineHeight */);
  138. this._range = null;
  139. this._viewRange = null;
  140. this._affinity = null;
  141. this._preference = [];
  142. this._cachedDomNodeOffsetWidth = -1;
  143. this._cachedDomNodeOffsetHeight = -1;
  144. this._maxWidth = this._getMaxWidth();
  145. this._isVisible = false;
  146. this._renderData = null;
  147. this.domNode.setPosition((this._fixedOverflowWidgets && this.allowEditorOverflow) ? 'fixed' : 'absolute');
  148. this.domNode.setDisplay('none');
  149. this.domNode.setVisibility('hidden');
  150. this.domNode.setAttribute('widgetId', this.id);
  151. this.domNode.setMaxWidth(this._maxWidth);
  152. }
  153. onConfigurationChanged(e) {
  154. const options = this._context.configuration.options;
  155. this._lineHeight = options.get(61 /* EditorOption.lineHeight */);
  156. if (e.hasChanged(133 /* EditorOption.layoutInfo */)) {
  157. const layoutInfo = options.get(133 /* EditorOption.layoutInfo */);
  158. this._contentLeft = layoutInfo.contentLeft;
  159. this._contentWidth = layoutInfo.contentWidth;
  160. this._maxWidth = this._getMaxWidth();
  161. }
  162. }
  163. onLineMappingChanged(e) {
  164. this._setPosition(this._range, this._affinity);
  165. }
  166. _setPosition(range, affinity) {
  167. var _a;
  168. this._range = range;
  169. this._viewRange = null;
  170. this._affinity = affinity;
  171. if (this._range) {
  172. // Do not trust that widgets give a valid position
  173. const validModelRange = this._context.viewModel.model.validateRange(this._range);
  174. if (this._context.viewModel.coordinatesConverter.modelPositionIsVisible(validModelRange.getStartPosition()) || this._context.viewModel.coordinatesConverter.modelPositionIsVisible(validModelRange.getEndPosition())) {
  175. this._viewRange = this._context.viewModel.coordinatesConverter.convertModelRangeToViewRange(validModelRange, (_a = this._affinity) !== null && _a !== void 0 ? _a : undefined);
  176. }
  177. }
  178. }
  179. _getMaxWidth() {
  180. return (this.allowEditorOverflow
  181. ? window.innerWidth || document.documentElement.offsetWidth || document.body.offsetWidth
  182. : this._contentWidth);
  183. }
  184. setPosition(range, preference, affinity) {
  185. this._setPosition(range, affinity);
  186. this._preference = preference;
  187. if (this._viewRange && this._preference && this._preference.length > 0) {
  188. // this content widget would like to be visible if possible
  189. // we change it from `display:none` to `display:block` even if it
  190. // might be outside the viewport such that we can measure its size
  191. // in `prepareRender`
  192. this.domNode.setDisplay('block');
  193. }
  194. else {
  195. this.domNode.setDisplay('none');
  196. }
  197. this._cachedDomNodeOffsetWidth = -1;
  198. this._cachedDomNodeOffsetHeight = -1;
  199. }
  200. _layoutBoxInViewport(topLeft, bottomLeft, width, height, ctx) {
  201. // Our visible box is split horizontally by the current line => 2 boxes
  202. // a) the box above the line
  203. const aboveLineTop = topLeft.top;
  204. const heightAboveLine = aboveLineTop;
  205. // b) the box under the line
  206. const underLineTop = bottomLeft.top + this._lineHeight;
  207. const heightUnderLine = ctx.viewportHeight - underLineTop;
  208. const aboveTop = aboveLineTop - height;
  209. const fitsAbove = (heightAboveLine >= height);
  210. const belowTop = underLineTop;
  211. const fitsBelow = (heightUnderLine >= height);
  212. // And its left
  213. let actualAboveLeft = topLeft.left;
  214. let actualBelowLeft = bottomLeft.left;
  215. if (actualAboveLeft + width > ctx.scrollLeft + ctx.viewportWidth) {
  216. actualAboveLeft = ctx.scrollLeft + ctx.viewportWidth - width;
  217. }
  218. if (actualBelowLeft + width > ctx.scrollLeft + ctx.viewportWidth) {
  219. actualBelowLeft = ctx.scrollLeft + ctx.viewportWidth - width;
  220. }
  221. if (actualAboveLeft < ctx.scrollLeft) {
  222. actualAboveLeft = ctx.scrollLeft;
  223. }
  224. if (actualBelowLeft < ctx.scrollLeft) {
  225. actualBelowLeft = ctx.scrollLeft;
  226. }
  227. return {
  228. fitsAbove: fitsAbove,
  229. aboveTop: aboveTop,
  230. aboveLeft: actualAboveLeft,
  231. fitsBelow: fitsBelow,
  232. belowTop: belowTop,
  233. belowLeft: actualBelowLeft,
  234. };
  235. }
  236. _layoutHorizontalSegmentInPage(windowSize, domNodePosition, left, width) {
  237. // Initially, the limits are defined as the dom node limits
  238. const MIN_LIMIT = Math.max(0, domNodePosition.left - width);
  239. const MAX_LIMIT = Math.min(domNodePosition.left + domNodePosition.width + width, windowSize.width);
  240. let absoluteLeft = domNodePosition.left + left - dom.StandardWindow.scrollX;
  241. if (absoluteLeft + width > MAX_LIMIT) {
  242. const delta = absoluteLeft - (MAX_LIMIT - width);
  243. absoluteLeft -= delta;
  244. left -= delta;
  245. }
  246. if (absoluteLeft < MIN_LIMIT) {
  247. const delta = absoluteLeft - MIN_LIMIT;
  248. absoluteLeft -= delta;
  249. left -= delta;
  250. }
  251. return [left, absoluteLeft];
  252. }
  253. _layoutBoxInPage(topLeft, bottomLeft, width, height, ctx) {
  254. const aboveTop = topLeft.top - height;
  255. const belowTop = bottomLeft.top + this._lineHeight;
  256. const domNodePosition = dom.getDomNodePagePosition(this._viewDomNode.domNode);
  257. const absoluteAboveTop = domNodePosition.top + aboveTop - dom.StandardWindow.scrollY;
  258. const absoluteBelowTop = domNodePosition.top + belowTop - dom.StandardWindow.scrollY;
  259. const windowSize = dom.getClientArea(document.body);
  260. const [aboveLeft, absoluteAboveLeft] = this._layoutHorizontalSegmentInPage(windowSize, domNodePosition, topLeft.left - ctx.scrollLeft + this._contentLeft, width);
  261. const [belowLeft, absoluteBelowLeft] = this._layoutHorizontalSegmentInPage(windowSize, domNodePosition, bottomLeft.left - ctx.scrollLeft + this._contentLeft, width);
  262. // Leave some clearance to the top/bottom
  263. const TOP_PADDING = 22;
  264. const BOTTOM_PADDING = 22;
  265. const fitsAbove = (absoluteAboveTop >= TOP_PADDING);
  266. const fitsBelow = (absoluteBelowTop + height <= windowSize.height - BOTTOM_PADDING);
  267. if (this._fixedOverflowWidgets) {
  268. return {
  269. fitsAbove,
  270. aboveTop: Math.max(absoluteAboveTop, TOP_PADDING),
  271. aboveLeft: absoluteAboveLeft,
  272. fitsBelow,
  273. belowTop: absoluteBelowTop,
  274. belowLeft: absoluteBelowLeft
  275. };
  276. }
  277. return {
  278. fitsAbove,
  279. aboveTop: aboveTop,
  280. aboveLeft,
  281. fitsBelow,
  282. belowTop,
  283. belowLeft
  284. };
  285. }
  286. _prepareRenderWidgetAtExactPositionOverflowing(topLeft) {
  287. return new Coordinate(topLeft.top, topLeft.left + this._contentLeft);
  288. }
  289. /**
  290. * Compute `this._topLeft`
  291. */
  292. _getTopAndBottomLeft(ctx) {
  293. if (!this._viewRange) {
  294. return [null, null];
  295. }
  296. const visibleRangesForRange = ctx.linesVisibleRangesForRange(this._viewRange, false);
  297. if (!visibleRangesForRange || visibleRangesForRange.length === 0) {
  298. return [null, null];
  299. }
  300. let firstLine = visibleRangesForRange[0];
  301. let lastLine = visibleRangesForRange[0];
  302. for (const visibleRangesForLine of visibleRangesForRange) {
  303. if (visibleRangesForLine.lineNumber < firstLine.lineNumber) {
  304. firstLine = visibleRangesForLine;
  305. }
  306. if (visibleRangesForLine.lineNumber > lastLine.lineNumber) {
  307. lastLine = visibleRangesForLine;
  308. }
  309. }
  310. let firstLineMinLeft = 1073741824 /* Constants.MAX_SAFE_SMALL_INTEGER */; //firstLine.Constants.MAX_SAFE_SMALL_INTEGER;
  311. for (const visibleRange of firstLine.ranges) {
  312. if (visibleRange.left < firstLineMinLeft) {
  313. firstLineMinLeft = visibleRange.left;
  314. }
  315. }
  316. let lastLineMinLeft = 1073741824 /* Constants.MAX_SAFE_SMALL_INTEGER */; //lastLine.Constants.MAX_SAFE_SMALL_INTEGER;
  317. for (const visibleRange of lastLine.ranges) {
  318. if (visibleRange.left < lastLineMinLeft) {
  319. lastLineMinLeft = visibleRange.left;
  320. }
  321. }
  322. const topForPosition = ctx.getVerticalOffsetForLineNumber(firstLine.lineNumber) - ctx.scrollTop;
  323. const topLeft = new Coordinate(topForPosition, firstLineMinLeft);
  324. const topForBottomLine = ctx.getVerticalOffsetForLineNumber(lastLine.lineNumber) - ctx.scrollTop;
  325. const bottomLeft = new Coordinate(topForBottomLine, lastLineMinLeft);
  326. return [topLeft, bottomLeft];
  327. }
  328. _prepareRenderWidget(ctx) {
  329. if (!this._preference || this._preference.length === 0) {
  330. return null;
  331. }
  332. const [topLeft, bottomLeft] = this._getTopAndBottomLeft(ctx);
  333. if (!topLeft || !bottomLeft) {
  334. return null;
  335. }
  336. if (this._cachedDomNodeOffsetWidth === -1 || this._cachedDomNodeOffsetHeight === -1) {
  337. let preferredDimensions = null;
  338. if (typeof this._actual.beforeRender === 'function') {
  339. preferredDimensions = safeInvoke(this._actual.beforeRender, this._actual);
  340. }
  341. if (preferredDimensions) {
  342. this._cachedDomNodeOffsetWidth = preferredDimensions.width;
  343. this._cachedDomNodeOffsetHeight = preferredDimensions.height;
  344. }
  345. else {
  346. const domNode = this.domNode.domNode;
  347. const clientRect = domNode.getBoundingClientRect();
  348. this._cachedDomNodeOffsetWidth = Math.round(clientRect.width);
  349. this._cachedDomNodeOffsetHeight = Math.round(clientRect.height);
  350. }
  351. }
  352. let placement;
  353. if (this.allowEditorOverflow) {
  354. placement = this._layoutBoxInPage(topLeft, bottomLeft, this._cachedDomNodeOffsetWidth, this._cachedDomNodeOffsetHeight, ctx);
  355. }
  356. else {
  357. placement = this._layoutBoxInViewport(topLeft, bottomLeft, this._cachedDomNodeOffsetWidth, this._cachedDomNodeOffsetHeight, ctx);
  358. }
  359. // Do two passes, first for perfect fit, second picks first option
  360. for (let pass = 1; pass <= 2; pass++) {
  361. for (const pref of this._preference) {
  362. // placement
  363. if (pref === 1 /* ContentWidgetPositionPreference.ABOVE */) {
  364. if (!placement) {
  365. // Widget outside of viewport
  366. return null;
  367. }
  368. if (pass === 2 || placement.fitsAbove) {
  369. return { coordinate: new Coordinate(placement.aboveTop, placement.aboveLeft), position: 1 /* ContentWidgetPositionPreference.ABOVE */ };
  370. }
  371. }
  372. else if (pref === 2 /* ContentWidgetPositionPreference.BELOW */) {
  373. if (!placement) {
  374. // Widget outside of viewport
  375. return null;
  376. }
  377. if (pass === 2 || placement.fitsBelow) {
  378. return { coordinate: new Coordinate(placement.belowTop, placement.belowLeft), position: 2 /* ContentWidgetPositionPreference.BELOW */ };
  379. }
  380. }
  381. else {
  382. if (this.allowEditorOverflow) {
  383. return { coordinate: this._prepareRenderWidgetAtExactPositionOverflowing(topLeft), position: 0 /* ContentWidgetPositionPreference.EXACT */ };
  384. }
  385. else {
  386. return { coordinate: topLeft, position: 0 /* ContentWidgetPositionPreference.EXACT */ };
  387. }
  388. }
  389. }
  390. }
  391. return null;
  392. }
  393. /**
  394. * On this first pass, we ensure that the content widget (if it is in the viewport) has the max width set correctly.
  395. */
  396. onBeforeRender(viewportData) {
  397. if (!this._viewRange || !this._preference) {
  398. return;
  399. }
  400. if (this._viewRange.endLineNumber < viewportData.startLineNumber || this._viewRange.startLineNumber > viewportData.endLineNumber) {
  401. // Outside of viewport
  402. return;
  403. }
  404. this.domNode.setMaxWidth(this._maxWidth);
  405. }
  406. prepareRender(ctx) {
  407. this._renderData = this._prepareRenderWidget(ctx);
  408. }
  409. render(ctx) {
  410. if (!this._renderData) {
  411. // This widget should be invisible
  412. if (this._isVisible) {
  413. this.domNode.removeAttribute('monaco-visible-content-widget');
  414. this._isVisible = false;
  415. this.domNode.setVisibility('hidden');
  416. }
  417. if (typeof this._actual.afterRender === 'function') {
  418. safeInvoke(this._actual.afterRender, this._actual, null);
  419. }
  420. return;
  421. }
  422. // This widget should be visible
  423. if (this.allowEditorOverflow) {
  424. this.domNode.setTop(this._renderData.coordinate.top);
  425. this.domNode.setLeft(this._renderData.coordinate.left);
  426. }
  427. else {
  428. this.domNode.setTop(this._renderData.coordinate.top + ctx.scrollTop - ctx.bigNumbersDelta);
  429. this.domNode.setLeft(this._renderData.coordinate.left);
  430. }
  431. if (!this._isVisible) {
  432. this.domNode.setVisibility('inherit');
  433. this.domNode.setAttribute('monaco-visible-content-widget', 'true');
  434. this._isVisible = true;
  435. }
  436. if (typeof this._actual.afterRender === 'function') {
  437. safeInvoke(this._actual.afterRender, this._actual, this._renderData.position);
  438. }
  439. }
  440. }
  441. function safeInvoke(fn, thisArg, ...args) {
  442. try {
  443. return fn.call(thisArg, ...args);
  444. }
  445. catch (_a) {
  446. // ignore
  447. return null;
  448. }
  449. }