85122517248f36ab938283fde22c5ad2c5f0f4d49c59b3fa0f0841fb25ff2429f564268efbc52fe637d6de0387e6f46cdb69bd896db4d1b05ed45a694542bc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  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 { Emitter } from '../../../base/common/event.js';
  6. import { Disposable } from '../../../base/common/lifecycle.js';
  7. import { Scrollable } from '../../../base/common/scrollable.js';
  8. import { LinesLayout } from './linesLayout.js';
  9. import { Viewport } from '../viewModel.js';
  10. import { ContentSizeChangedEvent } from '../viewModelEventDispatcher.js';
  11. const SMOOTH_SCROLLING_TIME = 125;
  12. class EditorScrollDimensions {
  13. constructor(width, contentWidth, height, contentHeight) {
  14. width = width | 0;
  15. contentWidth = contentWidth | 0;
  16. height = height | 0;
  17. contentHeight = contentHeight | 0;
  18. if (width < 0) {
  19. width = 0;
  20. }
  21. if (contentWidth < 0) {
  22. contentWidth = 0;
  23. }
  24. if (height < 0) {
  25. height = 0;
  26. }
  27. if (contentHeight < 0) {
  28. contentHeight = 0;
  29. }
  30. this.width = width;
  31. this.contentWidth = contentWidth;
  32. this.scrollWidth = Math.max(width, contentWidth);
  33. this.height = height;
  34. this.contentHeight = contentHeight;
  35. this.scrollHeight = Math.max(height, contentHeight);
  36. }
  37. equals(other) {
  38. return (this.width === other.width
  39. && this.contentWidth === other.contentWidth
  40. && this.height === other.height
  41. && this.contentHeight === other.contentHeight);
  42. }
  43. }
  44. class EditorScrollable extends Disposable {
  45. constructor(smoothScrollDuration, scheduleAtNextAnimationFrame) {
  46. super();
  47. this._onDidContentSizeChange = this._register(new Emitter());
  48. this.onDidContentSizeChange = this._onDidContentSizeChange.event;
  49. this._dimensions = new EditorScrollDimensions(0, 0, 0, 0);
  50. this._scrollable = this._register(new Scrollable({
  51. forceIntegerValues: true,
  52. smoothScrollDuration,
  53. scheduleAtNextAnimationFrame
  54. }));
  55. this.onDidScroll = this._scrollable.onScroll;
  56. }
  57. getScrollable() {
  58. return this._scrollable;
  59. }
  60. setSmoothScrollDuration(smoothScrollDuration) {
  61. this._scrollable.setSmoothScrollDuration(smoothScrollDuration);
  62. }
  63. validateScrollPosition(scrollPosition) {
  64. return this._scrollable.validateScrollPosition(scrollPosition);
  65. }
  66. getScrollDimensions() {
  67. return this._dimensions;
  68. }
  69. setScrollDimensions(dimensions) {
  70. if (this._dimensions.equals(dimensions)) {
  71. return;
  72. }
  73. const oldDimensions = this._dimensions;
  74. this._dimensions = dimensions;
  75. this._scrollable.setScrollDimensions({
  76. width: dimensions.width,
  77. scrollWidth: dimensions.scrollWidth,
  78. height: dimensions.height,
  79. scrollHeight: dimensions.scrollHeight
  80. }, true);
  81. const contentWidthChanged = (oldDimensions.contentWidth !== dimensions.contentWidth);
  82. const contentHeightChanged = (oldDimensions.contentHeight !== dimensions.contentHeight);
  83. if (contentWidthChanged || contentHeightChanged) {
  84. this._onDidContentSizeChange.fire(new ContentSizeChangedEvent(oldDimensions.contentWidth, oldDimensions.contentHeight, dimensions.contentWidth, dimensions.contentHeight));
  85. }
  86. }
  87. getFutureScrollPosition() {
  88. return this._scrollable.getFutureScrollPosition();
  89. }
  90. getCurrentScrollPosition() {
  91. return this._scrollable.getCurrentScrollPosition();
  92. }
  93. setScrollPositionNow(update) {
  94. this._scrollable.setScrollPositionNow(update);
  95. }
  96. setScrollPositionSmooth(update) {
  97. this._scrollable.setScrollPositionSmooth(update);
  98. }
  99. }
  100. export class ViewLayout extends Disposable {
  101. constructor(configuration, lineCount, scheduleAtNextAnimationFrame) {
  102. super();
  103. this._configuration = configuration;
  104. const options = this._configuration.options;
  105. const layoutInfo = options.get(133 /* EditorOption.layoutInfo */);
  106. const padding = options.get(77 /* EditorOption.padding */);
  107. this._linesLayout = new LinesLayout(lineCount, options.get(61 /* EditorOption.lineHeight */), padding.top, padding.bottom);
  108. this._scrollable = this._register(new EditorScrollable(0, scheduleAtNextAnimationFrame));
  109. this._configureSmoothScrollDuration();
  110. this._scrollable.setScrollDimensions(new EditorScrollDimensions(layoutInfo.contentWidth, 0, layoutInfo.height, 0));
  111. this.onDidScroll = this._scrollable.onDidScroll;
  112. this.onDidContentSizeChange = this._scrollable.onDidContentSizeChange;
  113. this._updateHeight();
  114. }
  115. dispose() {
  116. super.dispose();
  117. }
  118. getScrollable() {
  119. return this._scrollable.getScrollable();
  120. }
  121. onHeightMaybeChanged() {
  122. this._updateHeight();
  123. }
  124. _configureSmoothScrollDuration() {
  125. this._scrollable.setSmoothScrollDuration(this._configuration.options.get(105 /* EditorOption.smoothScrolling */) ? SMOOTH_SCROLLING_TIME : 0);
  126. }
  127. // ---- begin view event handlers
  128. onConfigurationChanged(e) {
  129. const options = this._configuration.options;
  130. if (e.hasChanged(61 /* EditorOption.lineHeight */)) {
  131. this._linesLayout.setLineHeight(options.get(61 /* EditorOption.lineHeight */));
  132. }
  133. if (e.hasChanged(77 /* EditorOption.padding */)) {
  134. const padding = options.get(77 /* EditorOption.padding */);
  135. this._linesLayout.setPadding(padding.top, padding.bottom);
  136. }
  137. if (e.hasChanged(133 /* EditorOption.layoutInfo */)) {
  138. const layoutInfo = options.get(133 /* EditorOption.layoutInfo */);
  139. const width = layoutInfo.contentWidth;
  140. const height = layoutInfo.height;
  141. const scrollDimensions = this._scrollable.getScrollDimensions();
  142. const contentWidth = scrollDimensions.contentWidth;
  143. this._scrollable.setScrollDimensions(new EditorScrollDimensions(width, scrollDimensions.contentWidth, height, this._getContentHeight(width, height, contentWidth)));
  144. }
  145. else {
  146. this._updateHeight();
  147. }
  148. if (e.hasChanged(105 /* EditorOption.smoothScrolling */)) {
  149. this._configureSmoothScrollDuration();
  150. }
  151. }
  152. onFlushed(lineCount) {
  153. this._linesLayout.onFlushed(lineCount);
  154. }
  155. onLinesDeleted(fromLineNumber, toLineNumber) {
  156. this._linesLayout.onLinesDeleted(fromLineNumber, toLineNumber);
  157. }
  158. onLinesInserted(fromLineNumber, toLineNumber) {
  159. this._linesLayout.onLinesInserted(fromLineNumber, toLineNumber);
  160. }
  161. // ---- end view event handlers
  162. _getHorizontalScrollbarHeight(width, scrollWidth) {
  163. const options = this._configuration.options;
  164. const scrollbar = options.get(94 /* EditorOption.scrollbar */);
  165. if (scrollbar.horizontal === 2 /* ScrollbarVisibility.Hidden */) {
  166. // horizontal scrollbar not visible
  167. return 0;
  168. }
  169. if (width >= scrollWidth) {
  170. // horizontal scrollbar not visible
  171. return 0;
  172. }
  173. return scrollbar.horizontalScrollbarSize;
  174. }
  175. _getContentHeight(width, height, contentWidth) {
  176. const options = this._configuration.options;
  177. let result = this._linesLayout.getLinesTotalHeight();
  178. if (options.get(96 /* EditorOption.scrollBeyondLastLine */)) {
  179. result += Math.max(0, height - options.get(61 /* EditorOption.lineHeight */) - options.get(77 /* EditorOption.padding */).bottom);
  180. }
  181. else {
  182. result += this._getHorizontalScrollbarHeight(width, contentWidth);
  183. }
  184. return result;
  185. }
  186. _updateHeight() {
  187. const scrollDimensions = this._scrollable.getScrollDimensions();
  188. const width = scrollDimensions.width;
  189. const height = scrollDimensions.height;
  190. const contentWidth = scrollDimensions.contentWidth;
  191. this._scrollable.setScrollDimensions(new EditorScrollDimensions(width, scrollDimensions.contentWidth, height, this._getContentHeight(width, height, contentWidth)));
  192. }
  193. // ---- Layouting logic
  194. getCurrentViewport() {
  195. const scrollDimensions = this._scrollable.getScrollDimensions();
  196. const currentScrollPosition = this._scrollable.getCurrentScrollPosition();
  197. return new Viewport(currentScrollPosition.scrollTop, currentScrollPosition.scrollLeft, scrollDimensions.width, scrollDimensions.height);
  198. }
  199. getFutureViewport() {
  200. const scrollDimensions = this._scrollable.getScrollDimensions();
  201. const currentScrollPosition = this._scrollable.getFutureScrollPosition();
  202. return new Viewport(currentScrollPosition.scrollTop, currentScrollPosition.scrollLeft, scrollDimensions.width, scrollDimensions.height);
  203. }
  204. _computeContentWidth(maxLineWidth) {
  205. const options = this._configuration.options;
  206. const wrappingInfo = options.get(134 /* EditorOption.wrappingInfo */);
  207. const fontInfo = options.get(46 /* EditorOption.fontInfo */);
  208. const layoutInfo = options.get(133 /* EditorOption.layoutInfo */);
  209. if (wrappingInfo.isViewportWrapping) {
  210. const minimap = options.get(67 /* EditorOption.minimap */);
  211. if (maxLineWidth > layoutInfo.contentWidth + fontInfo.typicalHalfwidthCharacterWidth) {
  212. // This is a case where viewport wrapping is on, but the line extends above the viewport
  213. if (minimap.enabled && minimap.side === 'right') {
  214. // We need to accomodate the scrollbar width
  215. return maxLineWidth + layoutInfo.verticalScrollbarWidth;
  216. }
  217. }
  218. return maxLineWidth;
  219. }
  220. else {
  221. const extraHorizontalSpace = options.get(95 /* EditorOption.scrollBeyondLastColumn */) * fontInfo.typicalHalfwidthCharacterWidth;
  222. const whitespaceMinWidth = this._linesLayout.getWhitespaceMinWidth();
  223. return Math.max(maxLineWidth + extraHorizontalSpace + layoutInfo.verticalScrollbarWidth, whitespaceMinWidth);
  224. }
  225. }
  226. setMaxLineWidth(maxLineWidth) {
  227. const scrollDimensions = this._scrollable.getScrollDimensions();
  228. // const newScrollWidth = ;
  229. this._scrollable.setScrollDimensions(new EditorScrollDimensions(scrollDimensions.width, this._computeContentWidth(maxLineWidth), scrollDimensions.height, scrollDimensions.contentHeight));
  230. // The height might depend on the fact that there is a horizontal scrollbar or not
  231. this._updateHeight();
  232. }
  233. // ---- view state
  234. saveState() {
  235. const currentScrollPosition = this._scrollable.getFutureScrollPosition();
  236. const scrollTop = currentScrollPosition.scrollTop;
  237. const firstLineNumberInViewport = this._linesLayout.getLineNumberAtOrAfterVerticalOffset(scrollTop);
  238. const whitespaceAboveFirstLine = this._linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(firstLineNumberInViewport);
  239. return {
  240. scrollTop: scrollTop,
  241. scrollTopWithoutViewZones: scrollTop - whitespaceAboveFirstLine,
  242. scrollLeft: currentScrollPosition.scrollLeft
  243. };
  244. }
  245. // ----
  246. changeWhitespace(callback) {
  247. const hadAChange = this._linesLayout.changeWhitespace(callback);
  248. if (hadAChange) {
  249. this.onHeightMaybeChanged();
  250. }
  251. return hadAChange;
  252. }
  253. getVerticalOffsetForLineNumber(lineNumber, includeViewZones = false) {
  254. return this._linesLayout.getVerticalOffsetForLineNumber(lineNumber, includeViewZones);
  255. }
  256. getVerticalOffsetAfterLineNumber(lineNumber, includeViewZones = false) {
  257. return this._linesLayout.getVerticalOffsetAfterLineNumber(lineNumber, includeViewZones);
  258. }
  259. isAfterLines(verticalOffset) {
  260. return this._linesLayout.isAfterLines(verticalOffset);
  261. }
  262. isInTopPadding(verticalOffset) {
  263. return this._linesLayout.isInTopPadding(verticalOffset);
  264. }
  265. isInBottomPadding(verticalOffset) {
  266. return this._linesLayout.isInBottomPadding(verticalOffset);
  267. }
  268. getLineNumberAtVerticalOffset(verticalOffset) {
  269. return this._linesLayout.getLineNumberAtOrAfterVerticalOffset(verticalOffset);
  270. }
  271. getWhitespaceAtVerticalOffset(verticalOffset) {
  272. return this._linesLayout.getWhitespaceAtVerticalOffset(verticalOffset);
  273. }
  274. getLinesViewportData() {
  275. const visibleBox = this.getCurrentViewport();
  276. return this._linesLayout.getLinesViewportData(visibleBox.top, visibleBox.top + visibleBox.height);
  277. }
  278. getLinesViewportDataAtScrollTop(scrollTop) {
  279. // do some minimal validations on scrollTop
  280. const scrollDimensions = this._scrollable.getScrollDimensions();
  281. if (scrollTop + scrollDimensions.height > scrollDimensions.scrollHeight) {
  282. scrollTop = scrollDimensions.scrollHeight - scrollDimensions.height;
  283. }
  284. if (scrollTop < 0) {
  285. scrollTop = 0;
  286. }
  287. return this._linesLayout.getLinesViewportData(scrollTop, scrollTop + scrollDimensions.height);
  288. }
  289. getWhitespaceViewportData() {
  290. const visibleBox = this.getCurrentViewport();
  291. return this._linesLayout.getWhitespaceViewportData(visibleBox.top, visibleBox.top + visibleBox.height);
  292. }
  293. getWhitespaces() {
  294. return this._linesLayout.getWhitespaces();
  295. }
  296. // ----
  297. getContentWidth() {
  298. const scrollDimensions = this._scrollable.getScrollDimensions();
  299. return scrollDimensions.contentWidth;
  300. }
  301. getScrollWidth() {
  302. const scrollDimensions = this._scrollable.getScrollDimensions();
  303. return scrollDimensions.scrollWidth;
  304. }
  305. getContentHeight() {
  306. const scrollDimensions = this._scrollable.getScrollDimensions();
  307. return scrollDimensions.contentHeight;
  308. }
  309. getScrollHeight() {
  310. const scrollDimensions = this._scrollable.getScrollDimensions();
  311. return scrollDimensions.scrollHeight;
  312. }
  313. getCurrentScrollLeft() {
  314. const currentScrollPosition = this._scrollable.getCurrentScrollPosition();
  315. return currentScrollPosition.scrollLeft;
  316. }
  317. getCurrentScrollTop() {
  318. const currentScrollPosition = this._scrollable.getCurrentScrollPosition();
  319. return currentScrollPosition.scrollTop;
  320. }
  321. validateScrollPosition(scrollPosition) {
  322. return this._scrollable.validateScrollPosition(scrollPosition);
  323. }
  324. setScrollPosition(position, type) {
  325. if (type === 1 /* ScrollType.Immediate */) {
  326. this._scrollable.setScrollPositionNow(position);
  327. }
  328. else {
  329. this._scrollable.setScrollPositionSmooth(position);
  330. }
  331. }
  332. deltaScrollNow(deltaScrollLeft, deltaScrollTop) {
  333. const currentScrollPosition = this._scrollable.getCurrentScrollPosition();
  334. this._scrollable.setScrollPositionNow({
  335. scrollLeft: currentScrollPosition.scrollLeft + deltaScrollLeft,
  336. scrollTop: currentScrollPosition.scrollTop + deltaScrollTop
  337. });
  338. }
  339. }