c76be58d684791b321721dac919ba7dca8d7ec9c4eaccb8520148b17479bb77b75323ecd91bec7a3b55d54f96f1b2d7b8e66f5d9d7a32cec31f3eca97d0bc3 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  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 { createFastDomNode } from '../../../../base/browser/fastDomNode.js';
  6. import { onUnexpectedError } from '../../../../base/common/errors.js';
  7. import { ViewPart } from '../../view/viewPart.js';
  8. import { Position } from '../../../common/core/position.js';
  9. const invalidFunc = () => { throw new Error(`Invalid change accessor`); };
  10. export class ViewZones extends ViewPart {
  11. constructor(context) {
  12. super(context);
  13. const options = this._context.configuration.options;
  14. const layoutInfo = options.get(133 /* EditorOption.layoutInfo */);
  15. this._lineHeight = options.get(61 /* EditorOption.lineHeight */);
  16. this._contentWidth = layoutInfo.contentWidth;
  17. this._contentLeft = layoutInfo.contentLeft;
  18. this.domNode = createFastDomNode(document.createElement('div'));
  19. this.domNode.setClassName('view-zones');
  20. this.domNode.setPosition('absolute');
  21. this.domNode.setAttribute('role', 'presentation');
  22. this.domNode.setAttribute('aria-hidden', 'true');
  23. this.marginDomNode = createFastDomNode(document.createElement('div'));
  24. this.marginDomNode.setClassName('margin-view-zones');
  25. this.marginDomNode.setPosition('absolute');
  26. this.marginDomNode.setAttribute('role', 'presentation');
  27. this.marginDomNode.setAttribute('aria-hidden', 'true');
  28. this._zones = {};
  29. }
  30. dispose() {
  31. super.dispose();
  32. this._zones = {};
  33. }
  34. // ---- begin view event handlers
  35. _recomputeWhitespacesProps() {
  36. const whitespaces = this._context.viewLayout.getWhitespaces();
  37. const oldWhitespaces = new Map();
  38. for (const whitespace of whitespaces) {
  39. oldWhitespaces.set(whitespace.id, whitespace);
  40. }
  41. let hadAChange = false;
  42. this._context.viewModel.changeWhitespace((whitespaceAccessor) => {
  43. const keys = Object.keys(this._zones);
  44. for (let i = 0, len = keys.length; i < len; i++) {
  45. const id = keys[i];
  46. const zone = this._zones[id];
  47. const props = this._computeWhitespaceProps(zone.delegate);
  48. zone.isInHiddenArea = props.isInHiddenArea;
  49. const oldWhitespace = oldWhitespaces.get(id);
  50. if (oldWhitespace && (oldWhitespace.afterLineNumber !== props.afterViewLineNumber || oldWhitespace.height !== props.heightInPx)) {
  51. whitespaceAccessor.changeOneWhitespace(id, props.afterViewLineNumber, props.heightInPx);
  52. this._safeCallOnComputedHeight(zone.delegate, props.heightInPx);
  53. hadAChange = true;
  54. }
  55. }
  56. });
  57. return hadAChange;
  58. }
  59. onConfigurationChanged(e) {
  60. const options = this._context.configuration.options;
  61. const layoutInfo = options.get(133 /* EditorOption.layoutInfo */);
  62. this._lineHeight = options.get(61 /* EditorOption.lineHeight */);
  63. this._contentWidth = layoutInfo.contentWidth;
  64. this._contentLeft = layoutInfo.contentLeft;
  65. if (e.hasChanged(61 /* EditorOption.lineHeight */)) {
  66. this._recomputeWhitespacesProps();
  67. }
  68. return true;
  69. }
  70. onLineMappingChanged(e) {
  71. return this._recomputeWhitespacesProps();
  72. }
  73. onLinesDeleted(e) {
  74. return true;
  75. }
  76. onScrollChanged(e) {
  77. return e.scrollTopChanged || e.scrollWidthChanged;
  78. }
  79. onZonesChanged(e) {
  80. return true;
  81. }
  82. onLinesInserted(e) {
  83. return true;
  84. }
  85. // ---- end view event handlers
  86. _getZoneOrdinal(zone) {
  87. if (typeof zone.afterColumn !== 'undefined') {
  88. return zone.afterColumn;
  89. }
  90. return 10000;
  91. }
  92. _computeWhitespaceProps(zone) {
  93. if (zone.afterLineNumber === 0) {
  94. return {
  95. isInHiddenArea: false,
  96. afterViewLineNumber: 0,
  97. heightInPx: this._heightInPixels(zone),
  98. minWidthInPx: this._minWidthInPixels(zone)
  99. };
  100. }
  101. let zoneAfterModelPosition;
  102. if (typeof zone.afterColumn !== 'undefined') {
  103. zoneAfterModelPosition = this._context.viewModel.model.validatePosition({
  104. lineNumber: zone.afterLineNumber,
  105. column: zone.afterColumn
  106. });
  107. }
  108. else {
  109. const validAfterLineNumber = this._context.viewModel.model.validatePosition({
  110. lineNumber: zone.afterLineNumber,
  111. column: 1
  112. }).lineNumber;
  113. zoneAfterModelPosition = new Position(validAfterLineNumber, this._context.viewModel.model.getLineMaxColumn(validAfterLineNumber));
  114. }
  115. let zoneBeforeModelPosition;
  116. if (zoneAfterModelPosition.column === this._context.viewModel.model.getLineMaxColumn(zoneAfterModelPosition.lineNumber)) {
  117. zoneBeforeModelPosition = this._context.viewModel.model.validatePosition({
  118. lineNumber: zoneAfterModelPosition.lineNumber + 1,
  119. column: 1
  120. });
  121. }
  122. else {
  123. zoneBeforeModelPosition = this._context.viewModel.model.validatePosition({
  124. lineNumber: zoneAfterModelPosition.lineNumber,
  125. column: zoneAfterModelPosition.column + 1
  126. });
  127. }
  128. const viewPosition = this._context.viewModel.coordinatesConverter.convertModelPositionToViewPosition(zoneAfterModelPosition, zone.afterColumnAffinity);
  129. const isVisible = this._context.viewModel.coordinatesConverter.modelPositionIsVisible(zoneBeforeModelPosition);
  130. return {
  131. isInHiddenArea: !isVisible,
  132. afterViewLineNumber: viewPosition.lineNumber,
  133. heightInPx: (isVisible ? this._heightInPixels(zone) : 0),
  134. minWidthInPx: this._minWidthInPixels(zone)
  135. };
  136. }
  137. changeViewZones(callback) {
  138. let zonesHaveChanged = false;
  139. this._context.viewModel.changeWhitespace((whitespaceAccessor) => {
  140. const changeAccessor = {
  141. addZone: (zone) => {
  142. zonesHaveChanged = true;
  143. return this._addZone(whitespaceAccessor, zone);
  144. },
  145. removeZone: (id) => {
  146. if (!id) {
  147. return;
  148. }
  149. zonesHaveChanged = this._removeZone(whitespaceAccessor, id) || zonesHaveChanged;
  150. },
  151. layoutZone: (id) => {
  152. if (!id) {
  153. return;
  154. }
  155. zonesHaveChanged = this._layoutZone(whitespaceAccessor, id) || zonesHaveChanged;
  156. }
  157. };
  158. safeInvoke1Arg(callback, changeAccessor);
  159. // Invalidate changeAccessor
  160. changeAccessor.addZone = invalidFunc;
  161. changeAccessor.removeZone = invalidFunc;
  162. changeAccessor.layoutZone = invalidFunc;
  163. });
  164. return zonesHaveChanged;
  165. }
  166. _addZone(whitespaceAccessor, zone) {
  167. const props = this._computeWhitespaceProps(zone);
  168. const whitespaceId = whitespaceAccessor.insertWhitespace(props.afterViewLineNumber, this._getZoneOrdinal(zone), props.heightInPx, props.minWidthInPx);
  169. const myZone = {
  170. whitespaceId: whitespaceId,
  171. delegate: zone,
  172. isInHiddenArea: props.isInHiddenArea,
  173. isVisible: false,
  174. domNode: createFastDomNode(zone.domNode),
  175. marginDomNode: zone.marginDomNode ? createFastDomNode(zone.marginDomNode) : null
  176. };
  177. this._safeCallOnComputedHeight(myZone.delegate, props.heightInPx);
  178. myZone.domNode.setPosition('absolute');
  179. myZone.domNode.domNode.style.width = '100%';
  180. myZone.domNode.setDisplay('none');
  181. myZone.domNode.setAttribute('monaco-view-zone', myZone.whitespaceId);
  182. this.domNode.appendChild(myZone.domNode);
  183. if (myZone.marginDomNode) {
  184. myZone.marginDomNode.setPosition('absolute');
  185. myZone.marginDomNode.domNode.style.width = '100%';
  186. myZone.marginDomNode.setDisplay('none');
  187. myZone.marginDomNode.setAttribute('monaco-view-zone', myZone.whitespaceId);
  188. this.marginDomNode.appendChild(myZone.marginDomNode);
  189. }
  190. this._zones[myZone.whitespaceId] = myZone;
  191. this.setShouldRender();
  192. return myZone.whitespaceId;
  193. }
  194. _removeZone(whitespaceAccessor, id) {
  195. if (this._zones.hasOwnProperty(id)) {
  196. const zone = this._zones[id];
  197. delete this._zones[id];
  198. whitespaceAccessor.removeWhitespace(zone.whitespaceId);
  199. zone.domNode.removeAttribute('monaco-visible-view-zone');
  200. zone.domNode.removeAttribute('monaco-view-zone');
  201. zone.domNode.domNode.parentNode.removeChild(zone.domNode.domNode);
  202. if (zone.marginDomNode) {
  203. zone.marginDomNode.removeAttribute('monaco-visible-view-zone');
  204. zone.marginDomNode.removeAttribute('monaco-view-zone');
  205. zone.marginDomNode.domNode.parentNode.removeChild(zone.marginDomNode.domNode);
  206. }
  207. this.setShouldRender();
  208. return true;
  209. }
  210. return false;
  211. }
  212. _layoutZone(whitespaceAccessor, id) {
  213. if (this._zones.hasOwnProperty(id)) {
  214. const zone = this._zones[id];
  215. const props = this._computeWhitespaceProps(zone.delegate);
  216. zone.isInHiddenArea = props.isInHiddenArea;
  217. // const newOrdinal = this._getZoneOrdinal(zone.delegate);
  218. whitespaceAccessor.changeOneWhitespace(zone.whitespaceId, props.afterViewLineNumber, props.heightInPx);
  219. // TODO@Alex: change `newOrdinal` too
  220. this._safeCallOnComputedHeight(zone.delegate, props.heightInPx);
  221. this.setShouldRender();
  222. return true;
  223. }
  224. return false;
  225. }
  226. shouldSuppressMouseDownOnViewZone(id) {
  227. if (this._zones.hasOwnProperty(id)) {
  228. const zone = this._zones[id];
  229. return Boolean(zone.delegate.suppressMouseDown);
  230. }
  231. return false;
  232. }
  233. _heightInPixels(zone) {
  234. if (typeof zone.heightInPx === 'number') {
  235. return zone.heightInPx;
  236. }
  237. if (typeof zone.heightInLines === 'number') {
  238. return this._lineHeight * zone.heightInLines;
  239. }
  240. return this._lineHeight;
  241. }
  242. _minWidthInPixels(zone) {
  243. if (typeof zone.minWidthInPx === 'number') {
  244. return zone.minWidthInPx;
  245. }
  246. return 0;
  247. }
  248. _safeCallOnComputedHeight(zone, height) {
  249. if (typeof zone.onComputedHeight === 'function') {
  250. try {
  251. zone.onComputedHeight(height);
  252. }
  253. catch (e) {
  254. onUnexpectedError(e);
  255. }
  256. }
  257. }
  258. _safeCallOnDomNodeTop(zone, top) {
  259. if (typeof zone.onDomNodeTop === 'function') {
  260. try {
  261. zone.onDomNodeTop(top);
  262. }
  263. catch (e) {
  264. onUnexpectedError(e);
  265. }
  266. }
  267. }
  268. prepareRender(ctx) {
  269. // Nothing to read
  270. }
  271. render(ctx) {
  272. const visibleWhitespaces = ctx.viewportData.whitespaceViewportData;
  273. const visibleZones = {};
  274. let hasVisibleZone = false;
  275. for (const visibleWhitespace of visibleWhitespaces) {
  276. if (this._zones[visibleWhitespace.id].isInHiddenArea) {
  277. continue;
  278. }
  279. visibleZones[visibleWhitespace.id] = visibleWhitespace;
  280. hasVisibleZone = true;
  281. }
  282. const keys = Object.keys(this._zones);
  283. for (let i = 0, len = keys.length; i < len; i++) {
  284. const id = keys[i];
  285. const zone = this._zones[id];
  286. let newTop = 0;
  287. let newHeight = 0;
  288. let newDisplay = 'none';
  289. if (visibleZones.hasOwnProperty(id)) {
  290. newTop = visibleZones[id].verticalOffset - ctx.bigNumbersDelta;
  291. newHeight = visibleZones[id].height;
  292. newDisplay = 'block';
  293. // zone is visible
  294. if (!zone.isVisible) {
  295. zone.domNode.setAttribute('monaco-visible-view-zone', 'true');
  296. zone.isVisible = true;
  297. }
  298. this._safeCallOnDomNodeTop(zone.delegate, ctx.getScrolledTopFromAbsoluteTop(visibleZones[id].verticalOffset));
  299. }
  300. else {
  301. if (zone.isVisible) {
  302. zone.domNode.removeAttribute('monaco-visible-view-zone');
  303. zone.isVisible = false;
  304. }
  305. this._safeCallOnDomNodeTop(zone.delegate, ctx.getScrolledTopFromAbsoluteTop(-1000000));
  306. }
  307. zone.domNode.setTop(newTop);
  308. zone.domNode.setHeight(newHeight);
  309. zone.domNode.setDisplay(newDisplay);
  310. if (zone.marginDomNode) {
  311. zone.marginDomNode.setTop(newTop);
  312. zone.marginDomNode.setHeight(newHeight);
  313. zone.marginDomNode.setDisplay(newDisplay);
  314. }
  315. }
  316. if (hasVisibleZone) {
  317. this.domNode.setWidth(Math.max(ctx.scrollWidth, this._contentWidth));
  318. this.marginDomNode.setWidth(this._contentLeft);
  319. }
  320. }
  321. }
  322. function safeInvoke1Arg(func, arg1) {
  323. try {
  324. return func(arg1);
  325. }
  326. catch (e) {
  327. onUnexpectedError(e);
  328. }
  329. }