b9a47e9ea2ddd34bf4964eb0893cccfc5edf2fe083b976e030d10f646663d603f308005476965066c8b73910ad64ed4a09597b300b380f1b71005432638cb5 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  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 './viewLines.css';
  6. import * as platform from '../../../../base/common/platform.js';
  7. import { RunOnceScheduler } from '../../../../base/common/async.js';
  8. import { applyFontInfo } from '../../config/domFontInfo.js';
  9. import { VisibleLinesCollection } from '../../view/viewLayer.js';
  10. import { PartFingerprints, ViewPart } from '../../view/viewPart.js';
  11. import { DomReadingContext, ViewLine, ViewLineOptions } from './viewLine.js';
  12. import { Position } from '../../../common/core/position.js';
  13. import { Range } from '../../../common/core/range.js';
  14. import { LineVisibleRanges, HorizontalPosition, HorizontalRange } from '../../view/renderingContext.js';
  15. import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from '../../../../base/browser/ui/mouseCursor/mouseCursor.js';
  16. class LastRenderedData {
  17. constructor() {
  18. this._currentVisibleRange = new Range(1, 1, 1, 1);
  19. }
  20. getCurrentVisibleRange() {
  21. return this._currentVisibleRange;
  22. }
  23. setCurrentVisibleRange(currentVisibleRange) {
  24. this._currentVisibleRange = currentVisibleRange;
  25. }
  26. }
  27. class HorizontalRevealRangeRequest {
  28. constructor(minimalReveal, lineNumber, startColumn, endColumn, startScrollTop, stopScrollTop, scrollType) {
  29. this.minimalReveal = minimalReveal;
  30. this.lineNumber = lineNumber;
  31. this.startColumn = startColumn;
  32. this.endColumn = endColumn;
  33. this.startScrollTop = startScrollTop;
  34. this.stopScrollTop = stopScrollTop;
  35. this.scrollType = scrollType;
  36. this.type = 'range';
  37. this.minLineNumber = lineNumber;
  38. this.maxLineNumber = lineNumber;
  39. }
  40. }
  41. class HorizontalRevealSelectionsRequest {
  42. constructor(minimalReveal, selections, startScrollTop, stopScrollTop, scrollType) {
  43. this.minimalReveal = minimalReveal;
  44. this.selections = selections;
  45. this.startScrollTop = startScrollTop;
  46. this.stopScrollTop = stopScrollTop;
  47. this.scrollType = scrollType;
  48. this.type = 'selections';
  49. let minLineNumber = selections[0].startLineNumber;
  50. let maxLineNumber = selections[0].endLineNumber;
  51. for (let i = 1, len = selections.length; i < len; i++) {
  52. const selection = selections[i];
  53. minLineNumber = Math.min(minLineNumber, selection.startLineNumber);
  54. maxLineNumber = Math.max(maxLineNumber, selection.endLineNumber);
  55. }
  56. this.minLineNumber = minLineNumber;
  57. this.maxLineNumber = maxLineNumber;
  58. }
  59. }
  60. export class ViewLines extends ViewPart {
  61. constructor(context, linesContent) {
  62. super(context);
  63. this._linesContent = linesContent;
  64. this._textRangeRestingSpot = document.createElement('div');
  65. this._visibleLines = new VisibleLinesCollection(this);
  66. this.domNode = this._visibleLines.domNode;
  67. const conf = this._context.configuration;
  68. const options = this._context.configuration.options;
  69. const fontInfo = options.get(46 /* EditorOption.fontInfo */);
  70. const wrappingInfo = options.get(134 /* EditorOption.wrappingInfo */);
  71. const layoutInfo = options.get(133 /* EditorOption.layoutInfo */);
  72. this._lineHeight = options.get(61 /* EditorOption.lineHeight */);
  73. this._typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth;
  74. this._isViewportWrapping = wrappingInfo.isViewportWrapping;
  75. this._revealHorizontalRightPadding = options.get(91 /* EditorOption.revealHorizontalRightPadding */);
  76. this._horizontalScrollbarHeight = layoutInfo.horizontalScrollbarHeight;
  77. this._cursorSurroundingLines = options.get(25 /* EditorOption.cursorSurroundingLines */);
  78. this._cursorSurroundingLinesStyle = options.get(26 /* EditorOption.cursorSurroundingLinesStyle */);
  79. this._canUseLayerHinting = !options.get(28 /* EditorOption.disableLayerHinting */);
  80. this._viewLineOptions = new ViewLineOptions(conf, this._context.theme.type);
  81. PartFingerprints.write(this.domNode, 7 /* PartFingerprint.ViewLines */);
  82. this.domNode.setClassName(`view-lines ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`);
  83. applyFontInfo(this.domNode, fontInfo);
  84. // --- width & height
  85. this._maxLineWidth = 0;
  86. this._asyncUpdateLineWidths = new RunOnceScheduler(() => {
  87. this._updateLineWidthsSlow();
  88. }, 200);
  89. this._asyncCheckMonospaceFontAssumptions = new RunOnceScheduler(() => {
  90. this._checkMonospaceFontAssumptions();
  91. }, 2000);
  92. this._lastRenderedData = new LastRenderedData();
  93. this._horizontalRevealRequest = null;
  94. }
  95. dispose() {
  96. this._asyncUpdateLineWidths.dispose();
  97. this._asyncCheckMonospaceFontAssumptions.dispose();
  98. super.dispose();
  99. }
  100. getDomNode() {
  101. return this.domNode;
  102. }
  103. // ---- begin IVisibleLinesHost
  104. createVisibleLine() {
  105. return new ViewLine(this._viewLineOptions);
  106. }
  107. // ---- end IVisibleLinesHost
  108. // ---- begin view event handlers
  109. onConfigurationChanged(e) {
  110. this._visibleLines.onConfigurationChanged(e);
  111. if (e.hasChanged(134 /* EditorOption.wrappingInfo */)) {
  112. this._maxLineWidth = 0;
  113. }
  114. const options = this._context.configuration.options;
  115. const fontInfo = options.get(46 /* EditorOption.fontInfo */);
  116. const wrappingInfo = options.get(134 /* EditorOption.wrappingInfo */);
  117. const layoutInfo = options.get(133 /* EditorOption.layoutInfo */);
  118. this._lineHeight = options.get(61 /* EditorOption.lineHeight */);
  119. this._typicalHalfwidthCharacterWidth = fontInfo.typicalHalfwidthCharacterWidth;
  120. this._isViewportWrapping = wrappingInfo.isViewportWrapping;
  121. this._revealHorizontalRightPadding = options.get(91 /* EditorOption.revealHorizontalRightPadding */);
  122. this._horizontalScrollbarHeight = layoutInfo.horizontalScrollbarHeight;
  123. this._cursorSurroundingLines = options.get(25 /* EditorOption.cursorSurroundingLines */);
  124. this._cursorSurroundingLinesStyle = options.get(26 /* EditorOption.cursorSurroundingLinesStyle */);
  125. this._canUseLayerHinting = !options.get(28 /* EditorOption.disableLayerHinting */);
  126. applyFontInfo(this.domNode, fontInfo);
  127. this._onOptionsMaybeChanged();
  128. if (e.hasChanged(133 /* EditorOption.layoutInfo */)) {
  129. this._maxLineWidth = 0;
  130. }
  131. return true;
  132. }
  133. _onOptionsMaybeChanged() {
  134. const conf = this._context.configuration;
  135. const newViewLineOptions = new ViewLineOptions(conf, this._context.theme.type);
  136. if (!this._viewLineOptions.equals(newViewLineOptions)) {
  137. this._viewLineOptions = newViewLineOptions;
  138. const startLineNumber = this._visibleLines.getStartLineNumber();
  139. const endLineNumber = this._visibleLines.getEndLineNumber();
  140. for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
  141. const line = this._visibleLines.getVisibleLine(lineNumber);
  142. line.onOptionsChanged(this._viewLineOptions);
  143. }
  144. return true;
  145. }
  146. return false;
  147. }
  148. onCursorStateChanged(e) {
  149. const rendStartLineNumber = this._visibleLines.getStartLineNumber();
  150. const rendEndLineNumber = this._visibleLines.getEndLineNumber();
  151. let r = false;
  152. for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {
  153. r = this._visibleLines.getVisibleLine(lineNumber).onSelectionChanged() || r;
  154. }
  155. return r;
  156. }
  157. onDecorationsChanged(e) {
  158. if (true /*e.inlineDecorationsChanged*/) {
  159. const rendStartLineNumber = this._visibleLines.getStartLineNumber();
  160. const rendEndLineNumber = this._visibleLines.getEndLineNumber();
  161. for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {
  162. this._visibleLines.getVisibleLine(lineNumber).onDecorationsChanged();
  163. }
  164. }
  165. return true;
  166. }
  167. onFlushed(e) {
  168. const shouldRender = this._visibleLines.onFlushed(e);
  169. this._maxLineWidth = 0;
  170. return shouldRender;
  171. }
  172. onLinesChanged(e) {
  173. return this._visibleLines.onLinesChanged(e);
  174. }
  175. onLinesDeleted(e) {
  176. return this._visibleLines.onLinesDeleted(e);
  177. }
  178. onLinesInserted(e) {
  179. return this._visibleLines.onLinesInserted(e);
  180. }
  181. onRevealRangeRequest(e) {
  182. // Using the future viewport here in order to handle multiple
  183. // incoming reveal range requests that might all desire to be animated
  184. const desiredScrollTop = this._computeScrollTopToRevealRange(this._context.viewLayout.getFutureViewport(), e.source, e.minimalReveal, e.range, e.selections, e.verticalType);
  185. if (desiredScrollTop === -1) {
  186. // marker to abort the reveal range request
  187. return false;
  188. }
  189. // validate the new desired scroll top
  190. let newScrollPosition = this._context.viewLayout.validateScrollPosition({ scrollTop: desiredScrollTop });
  191. if (e.revealHorizontal) {
  192. if (e.range && e.range.startLineNumber !== e.range.endLineNumber) {
  193. // Two or more lines? => scroll to base (That's how you see most of the two lines)
  194. newScrollPosition = {
  195. scrollTop: newScrollPosition.scrollTop,
  196. scrollLeft: 0
  197. };
  198. }
  199. else if (e.range) {
  200. // We don't necessarily know the horizontal offset of this range since the line might not be in the view...
  201. this._horizontalRevealRequest = new HorizontalRevealRangeRequest(e.minimalReveal, e.range.startLineNumber, e.range.startColumn, e.range.endColumn, this._context.viewLayout.getCurrentScrollTop(), newScrollPosition.scrollTop, e.scrollType);
  202. }
  203. else if (e.selections && e.selections.length > 0) {
  204. this._horizontalRevealRequest = new HorizontalRevealSelectionsRequest(e.minimalReveal, e.selections, this._context.viewLayout.getCurrentScrollTop(), newScrollPosition.scrollTop, e.scrollType);
  205. }
  206. }
  207. else {
  208. this._horizontalRevealRequest = null;
  209. }
  210. const scrollTopDelta = Math.abs(this._context.viewLayout.getCurrentScrollTop() - newScrollPosition.scrollTop);
  211. const scrollType = (scrollTopDelta <= this._lineHeight ? 1 /* ScrollType.Immediate */ : e.scrollType);
  212. this._context.viewModel.viewLayout.setScrollPosition(newScrollPosition, scrollType);
  213. return true;
  214. }
  215. onScrollChanged(e) {
  216. if (this._horizontalRevealRequest && e.scrollLeftChanged) {
  217. // cancel any outstanding horizontal reveal request if someone else scrolls horizontally.
  218. this._horizontalRevealRequest = null;
  219. }
  220. if (this._horizontalRevealRequest && e.scrollTopChanged) {
  221. const min = Math.min(this._horizontalRevealRequest.startScrollTop, this._horizontalRevealRequest.stopScrollTop);
  222. const max = Math.max(this._horizontalRevealRequest.startScrollTop, this._horizontalRevealRequest.stopScrollTop);
  223. if (e.scrollTop < min || e.scrollTop > max) {
  224. // cancel any outstanding horizontal reveal request if someone else scrolls vertically.
  225. this._horizontalRevealRequest = null;
  226. }
  227. }
  228. this.domNode.setWidth(e.scrollWidth);
  229. return this._visibleLines.onScrollChanged(e) || true;
  230. }
  231. onTokensChanged(e) {
  232. return this._visibleLines.onTokensChanged(e);
  233. }
  234. onZonesChanged(e) {
  235. this._context.viewModel.viewLayout.setMaxLineWidth(this._maxLineWidth);
  236. return this._visibleLines.onZonesChanged(e);
  237. }
  238. onThemeChanged(e) {
  239. return this._onOptionsMaybeChanged();
  240. }
  241. // ---- end view event handlers
  242. // ----------- HELPERS FOR OTHERS
  243. getPositionFromDOMInfo(spanNode, offset) {
  244. const viewLineDomNode = this._getViewLineDomNode(spanNode);
  245. if (viewLineDomNode === null) {
  246. // Couldn't find view line node
  247. return null;
  248. }
  249. const lineNumber = this._getLineNumberFor(viewLineDomNode);
  250. if (lineNumber === -1) {
  251. // Couldn't find view line node
  252. return null;
  253. }
  254. if (lineNumber < 1 || lineNumber > this._context.viewModel.getLineCount()) {
  255. // lineNumber is outside range
  256. return null;
  257. }
  258. if (this._context.viewModel.getLineMaxColumn(lineNumber) === 1) {
  259. // Line is empty
  260. return new Position(lineNumber, 1);
  261. }
  262. const rendStartLineNumber = this._visibleLines.getStartLineNumber();
  263. const rendEndLineNumber = this._visibleLines.getEndLineNumber();
  264. if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) {
  265. // Couldn't find line
  266. return null;
  267. }
  268. let column = this._visibleLines.getVisibleLine(lineNumber).getColumnOfNodeOffset(lineNumber, spanNode, offset);
  269. const minColumn = this._context.viewModel.getLineMinColumn(lineNumber);
  270. if (column < minColumn) {
  271. column = minColumn;
  272. }
  273. return new Position(lineNumber, column);
  274. }
  275. _getViewLineDomNode(node) {
  276. while (node && node.nodeType === 1) {
  277. if (node.className === ViewLine.CLASS_NAME) {
  278. return node;
  279. }
  280. node = node.parentElement;
  281. }
  282. return null;
  283. }
  284. /**
  285. * @returns the line number of this view line dom node.
  286. */
  287. _getLineNumberFor(domNode) {
  288. const startLineNumber = this._visibleLines.getStartLineNumber();
  289. const endLineNumber = this._visibleLines.getEndLineNumber();
  290. for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
  291. const line = this._visibleLines.getVisibleLine(lineNumber);
  292. if (domNode === line.getDomNode()) {
  293. return lineNumber;
  294. }
  295. }
  296. return -1;
  297. }
  298. getLineWidth(lineNumber) {
  299. const rendStartLineNumber = this._visibleLines.getStartLineNumber();
  300. const rendEndLineNumber = this._visibleLines.getEndLineNumber();
  301. if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) {
  302. // Couldn't find line
  303. return -1;
  304. }
  305. return this._visibleLines.getVisibleLine(lineNumber).getWidth();
  306. }
  307. linesVisibleRangesForRange(_range, includeNewLines) {
  308. if (this.shouldRender()) {
  309. // Cannot read from the DOM because it is dirty
  310. // i.e. the model & the dom are out of sync, so I'd be reading something stale
  311. return null;
  312. }
  313. const originalEndLineNumber = _range.endLineNumber;
  314. const range = Range.intersectRanges(_range, this._lastRenderedData.getCurrentVisibleRange());
  315. if (!range) {
  316. return null;
  317. }
  318. const visibleRanges = [];
  319. let visibleRangesLen = 0;
  320. const domReadingContext = new DomReadingContext(this.domNode.domNode, this._textRangeRestingSpot);
  321. let nextLineModelLineNumber = 0;
  322. if (includeNewLines) {
  323. nextLineModelLineNumber = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(range.startLineNumber, 1)).lineNumber;
  324. }
  325. const rendStartLineNumber = this._visibleLines.getStartLineNumber();
  326. const rendEndLineNumber = this._visibleLines.getEndLineNumber();
  327. for (let lineNumber = range.startLineNumber; lineNumber <= range.endLineNumber; lineNumber++) {
  328. if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) {
  329. continue;
  330. }
  331. const startColumn = lineNumber === range.startLineNumber ? range.startColumn : 1;
  332. const endColumn = lineNumber === range.endLineNumber ? range.endColumn : this._context.viewModel.getLineMaxColumn(lineNumber);
  333. const visibleRangesForLine = this._visibleLines.getVisibleLine(lineNumber).getVisibleRangesForRange(lineNumber, startColumn, endColumn, domReadingContext);
  334. if (!visibleRangesForLine) {
  335. continue;
  336. }
  337. if (includeNewLines && lineNumber < originalEndLineNumber) {
  338. const currentLineModelLineNumber = nextLineModelLineNumber;
  339. nextLineModelLineNumber = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber + 1, 1)).lineNumber;
  340. if (currentLineModelLineNumber !== nextLineModelLineNumber) {
  341. visibleRangesForLine.ranges[visibleRangesForLine.ranges.length - 1].width += this._typicalHalfwidthCharacterWidth;
  342. }
  343. }
  344. visibleRanges[visibleRangesLen++] = new LineVisibleRanges(visibleRangesForLine.outsideRenderedLine, lineNumber, HorizontalRange.from(visibleRangesForLine.ranges));
  345. }
  346. if (visibleRangesLen === 0) {
  347. return null;
  348. }
  349. return visibleRanges;
  350. }
  351. _visibleRangesForLineRange(lineNumber, startColumn, endColumn) {
  352. if (this.shouldRender()) {
  353. // Cannot read from the DOM because it is dirty
  354. // i.e. the model & the dom are out of sync, so I'd be reading something stale
  355. return null;
  356. }
  357. if (lineNumber < this._visibleLines.getStartLineNumber() || lineNumber > this._visibleLines.getEndLineNumber()) {
  358. return null;
  359. }
  360. return this._visibleLines.getVisibleLine(lineNumber).getVisibleRangesForRange(lineNumber, startColumn, endColumn, new DomReadingContext(this.domNode.domNode, this._textRangeRestingSpot));
  361. }
  362. visibleRangeForPosition(position) {
  363. const visibleRanges = this._visibleRangesForLineRange(position.lineNumber, position.column, position.column);
  364. if (!visibleRanges) {
  365. return null;
  366. }
  367. return new HorizontalPosition(visibleRanges.outsideRenderedLine, visibleRanges.ranges[0].left);
  368. }
  369. // --- implementation
  370. updateLineWidths() {
  371. this._updateLineWidths(false);
  372. }
  373. /**
  374. * Updates the max line width if it is fast to compute.
  375. * Returns true if all lines were taken into account.
  376. * Returns false if some lines need to be reevaluated (in a slow fashion).
  377. */
  378. _updateLineWidthsFast() {
  379. return this._updateLineWidths(true);
  380. }
  381. _updateLineWidthsSlow() {
  382. this._updateLineWidths(false);
  383. }
  384. _updateLineWidths(fast) {
  385. const rendStartLineNumber = this._visibleLines.getStartLineNumber();
  386. const rendEndLineNumber = this._visibleLines.getEndLineNumber();
  387. let localMaxLineWidth = 1;
  388. let allWidthsComputed = true;
  389. for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {
  390. const visibleLine = this._visibleLines.getVisibleLine(lineNumber);
  391. if (fast && !visibleLine.getWidthIsFast()) {
  392. // Cannot compute width in a fast way for this line
  393. allWidthsComputed = false;
  394. continue;
  395. }
  396. localMaxLineWidth = Math.max(localMaxLineWidth, visibleLine.getWidth());
  397. }
  398. if (allWidthsComputed && rendStartLineNumber === 1 && rendEndLineNumber === this._context.viewModel.getLineCount()) {
  399. // we know the max line width for all the lines
  400. this._maxLineWidth = 0;
  401. }
  402. this._ensureMaxLineWidth(localMaxLineWidth);
  403. return allWidthsComputed;
  404. }
  405. _checkMonospaceFontAssumptions() {
  406. // Problems with monospace assumptions are more apparent for longer lines,
  407. // as small rounding errors start to sum up, so we will select the longest
  408. // line for a closer inspection
  409. let longestLineNumber = -1;
  410. let longestWidth = -1;
  411. const rendStartLineNumber = this._visibleLines.getStartLineNumber();
  412. const rendEndLineNumber = this._visibleLines.getEndLineNumber();
  413. for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {
  414. const visibleLine = this._visibleLines.getVisibleLine(lineNumber);
  415. if (visibleLine.needsMonospaceFontCheck()) {
  416. const lineWidth = visibleLine.getWidth();
  417. if (lineWidth > longestWidth) {
  418. longestWidth = lineWidth;
  419. longestLineNumber = lineNumber;
  420. }
  421. }
  422. }
  423. if (longestLineNumber === -1) {
  424. return;
  425. }
  426. if (!this._visibleLines.getVisibleLine(longestLineNumber).monospaceAssumptionsAreValid()) {
  427. for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {
  428. const visibleLine = this._visibleLines.getVisibleLine(lineNumber);
  429. visibleLine.onMonospaceAssumptionsInvalidated();
  430. }
  431. }
  432. }
  433. prepareRender() {
  434. throw new Error('Not supported');
  435. }
  436. render() {
  437. throw new Error('Not supported');
  438. }
  439. renderText(viewportData) {
  440. // (1) render lines - ensures lines are in the DOM
  441. this._visibleLines.renderLines(viewportData);
  442. this._lastRenderedData.setCurrentVisibleRange(viewportData.visibleRange);
  443. this.domNode.setWidth(this._context.viewLayout.getScrollWidth());
  444. this.domNode.setHeight(Math.min(this._context.viewLayout.getScrollHeight(), 1000000));
  445. // (2) compute horizontal scroll position:
  446. // - this must happen after the lines are in the DOM since it might need a line that rendered just now
  447. // - it might change `scrollWidth` and `scrollLeft`
  448. if (this._horizontalRevealRequest) {
  449. const horizontalRevealRequest = this._horizontalRevealRequest;
  450. // Check that we have the line that contains the horizontal range in the viewport
  451. if (viewportData.startLineNumber <= horizontalRevealRequest.minLineNumber && horizontalRevealRequest.maxLineNumber <= viewportData.endLineNumber) {
  452. this._horizontalRevealRequest = null;
  453. // allow `visibleRangesForRange2` to work
  454. this.onDidRender();
  455. // compute new scroll position
  456. const newScrollLeft = this._computeScrollLeftToReveal(horizontalRevealRequest);
  457. if (newScrollLeft) {
  458. if (!this._isViewportWrapping) {
  459. // ensure `scrollWidth` is large enough
  460. this._ensureMaxLineWidth(newScrollLeft.maxHorizontalOffset);
  461. }
  462. // set `scrollLeft`
  463. this._context.viewModel.viewLayout.setScrollPosition({
  464. scrollLeft: newScrollLeft.scrollLeft
  465. }, horizontalRevealRequest.scrollType);
  466. }
  467. }
  468. }
  469. // Update max line width (not so important, it is just so the horizontal scrollbar doesn't get too small)
  470. if (!this._updateLineWidthsFast()) {
  471. // Computing the width of some lines would be slow => delay it
  472. this._asyncUpdateLineWidths.schedule();
  473. }
  474. if (platform.isLinux && !this._asyncCheckMonospaceFontAssumptions.isScheduled()) {
  475. const rendStartLineNumber = this._visibleLines.getStartLineNumber();
  476. const rendEndLineNumber = this._visibleLines.getEndLineNumber();
  477. for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) {
  478. const visibleLine = this._visibleLines.getVisibleLine(lineNumber);
  479. if (visibleLine.needsMonospaceFontCheck()) {
  480. this._asyncCheckMonospaceFontAssumptions.schedule();
  481. break;
  482. }
  483. }
  484. }
  485. // (3) handle scrolling
  486. this._linesContent.setLayerHinting(this._canUseLayerHinting);
  487. this._linesContent.setContain('strict');
  488. const adjustedScrollTop = this._context.viewLayout.getCurrentScrollTop() - viewportData.bigNumbersDelta;
  489. this._linesContent.setTop(-adjustedScrollTop);
  490. this._linesContent.setLeft(-this._context.viewLayout.getCurrentScrollLeft());
  491. }
  492. // --- width
  493. _ensureMaxLineWidth(lineWidth) {
  494. const iLineWidth = Math.ceil(lineWidth);
  495. if (this._maxLineWidth < iLineWidth) {
  496. this._maxLineWidth = iLineWidth;
  497. this._context.viewModel.viewLayout.setMaxLineWidth(this._maxLineWidth);
  498. }
  499. }
  500. _computeScrollTopToRevealRange(viewport, source, minimalReveal, range, selections, verticalType) {
  501. const viewportStartY = viewport.top;
  502. const viewportHeight = viewport.height;
  503. const viewportEndY = viewportStartY + viewportHeight;
  504. let boxIsSingleRange;
  505. let boxStartY;
  506. let boxEndY;
  507. if (selections && selections.length > 0) {
  508. let minLineNumber = selections[0].startLineNumber;
  509. let maxLineNumber = selections[0].endLineNumber;
  510. for (let i = 1, len = selections.length; i < len; i++) {
  511. const selection = selections[i];
  512. minLineNumber = Math.min(minLineNumber, selection.startLineNumber);
  513. maxLineNumber = Math.max(maxLineNumber, selection.endLineNumber);
  514. }
  515. boxIsSingleRange = false;
  516. boxStartY = this._context.viewLayout.getVerticalOffsetForLineNumber(minLineNumber);
  517. boxEndY = this._context.viewLayout.getVerticalOffsetForLineNumber(maxLineNumber) + this._lineHeight;
  518. }
  519. else if (range) {
  520. boxIsSingleRange = true;
  521. boxStartY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.startLineNumber);
  522. boxEndY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.endLineNumber) + this._lineHeight;
  523. }
  524. else {
  525. return -1;
  526. }
  527. const shouldIgnoreScrollOff = (source === 'mouse' || minimalReveal) && this._cursorSurroundingLinesStyle === 'default';
  528. if (!shouldIgnoreScrollOff) {
  529. const context = Math.min((viewportHeight / this._lineHeight) / 2, this._cursorSurroundingLines);
  530. boxStartY -= context * this._lineHeight;
  531. boxEndY += Math.max(0, (context - 1)) * this._lineHeight;
  532. }
  533. else {
  534. if (!minimalReveal) {
  535. // Reveal one more line above (this case is hit when dragging)
  536. boxStartY -= this._lineHeight;
  537. }
  538. }
  539. if (verticalType === 0 /* viewEvents.VerticalRevealType.Simple */ || verticalType === 4 /* viewEvents.VerticalRevealType.Bottom */) {
  540. // Reveal one line more when the last line would be covered by the scrollbar - arrow down case or revealing a line explicitly at bottom
  541. boxEndY += (minimalReveal ? this._horizontalScrollbarHeight : this._lineHeight);
  542. }
  543. let newScrollTop;
  544. if (boxEndY - boxStartY > viewportHeight) {
  545. // the box is larger than the viewport ... scroll to its top
  546. if (!boxIsSingleRange) {
  547. // do not reveal multiple cursors if there are more than fit the viewport
  548. return -1;
  549. }
  550. newScrollTop = boxStartY;
  551. }
  552. else if (verticalType === 5 /* viewEvents.VerticalRevealType.NearTop */ || verticalType === 6 /* viewEvents.VerticalRevealType.NearTopIfOutsideViewport */) {
  553. if (verticalType === 6 /* viewEvents.VerticalRevealType.NearTopIfOutsideViewport */ && viewportStartY <= boxStartY && boxEndY <= viewportEndY) {
  554. // Box is already in the viewport... do nothing
  555. newScrollTop = viewportStartY;
  556. }
  557. else {
  558. // We want a gap that is 20% of the viewport, but with a minimum of 5 lines
  559. const desiredGapAbove = Math.max(5 * this._lineHeight, viewportHeight * 0.2);
  560. // Try to scroll just above the box with the desired gap
  561. const desiredScrollTop = boxStartY - desiredGapAbove;
  562. // But ensure that the box is not pushed out of viewport
  563. const minScrollTop = boxEndY - viewportHeight;
  564. newScrollTop = Math.max(minScrollTop, desiredScrollTop);
  565. }
  566. }
  567. else if (verticalType === 1 /* viewEvents.VerticalRevealType.Center */ || verticalType === 2 /* viewEvents.VerticalRevealType.CenterIfOutsideViewport */) {
  568. if (verticalType === 2 /* viewEvents.VerticalRevealType.CenterIfOutsideViewport */ && viewportStartY <= boxStartY && boxEndY <= viewportEndY) {
  569. // Box is already in the viewport... do nothing
  570. newScrollTop = viewportStartY;
  571. }
  572. else {
  573. // Box is outside the viewport... center it
  574. const boxMiddleY = (boxStartY + boxEndY) / 2;
  575. newScrollTop = Math.max(0, boxMiddleY - viewportHeight / 2);
  576. }
  577. }
  578. else {
  579. newScrollTop = this._computeMinimumScrolling(viewportStartY, viewportEndY, boxStartY, boxEndY, verticalType === 3 /* viewEvents.VerticalRevealType.Top */, verticalType === 4 /* viewEvents.VerticalRevealType.Bottom */);
  580. }
  581. return newScrollTop;
  582. }
  583. _computeScrollLeftToReveal(horizontalRevealRequest) {
  584. const viewport = this._context.viewLayout.getCurrentViewport();
  585. const viewportStartX = viewport.left;
  586. const viewportEndX = viewportStartX + viewport.width;
  587. let boxStartX = 1073741824 /* Constants.MAX_SAFE_SMALL_INTEGER */;
  588. let boxEndX = 0;
  589. if (horizontalRevealRequest.type === 'range') {
  590. const visibleRanges = this._visibleRangesForLineRange(horizontalRevealRequest.lineNumber, horizontalRevealRequest.startColumn, horizontalRevealRequest.endColumn);
  591. if (!visibleRanges) {
  592. return null;
  593. }
  594. for (const visibleRange of visibleRanges.ranges) {
  595. boxStartX = Math.min(boxStartX, Math.round(visibleRange.left));
  596. boxEndX = Math.max(boxEndX, Math.round(visibleRange.left + visibleRange.width));
  597. }
  598. }
  599. else {
  600. for (const selection of horizontalRevealRequest.selections) {
  601. if (selection.startLineNumber !== selection.endLineNumber) {
  602. return null;
  603. }
  604. const visibleRanges = this._visibleRangesForLineRange(selection.startLineNumber, selection.startColumn, selection.endColumn);
  605. if (!visibleRanges) {
  606. return null;
  607. }
  608. for (const visibleRange of visibleRanges.ranges) {
  609. boxStartX = Math.min(boxStartX, Math.round(visibleRange.left));
  610. boxEndX = Math.max(boxEndX, Math.round(visibleRange.left + visibleRange.width));
  611. }
  612. }
  613. }
  614. if (!horizontalRevealRequest.minimalReveal) {
  615. boxStartX = Math.max(0, boxStartX - ViewLines.HORIZONTAL_EXTRA_PX);
  616. boxEndX += this._revealHorizontalRightPadding;
  617. }
  618. if (horizontalRevealRequest.type === 'selections' && boxEndX - boxStartX > viewport.width) {
  619. return null;
  620. }
  621. const newScrollLeft = this._computeMinimumScrolling(viewportStartX, viewportEndX, boxStartX, boxEndX);
  622. return {
  623. scrollLeft: newScrollLeft,
  624. maxHorizontalOffset: boxEndX
  625. };
  626. }
  627. _computeMinimumScrolling(viewportStart, viewportEnd, boxStart, boxEnd, revealAtStart, revealAtEnd) {
  628. viewportStart = viewportStart | 0;
  629. viewportEnd = viewportEnd | 0;
  630. boxStart = boxStart | 0;
  631. boxEnd = boxEnd | 0;
  632. revealAtStart = !!revealAtStart;
  633. revealAtEnd = !!revealAtEnd;
  634. const viewportLength = viewportEnd - viewportStart;
  635. const boxLength = boxEnd - boxStart;
  636. if (boxLength < viewportLength) {
  637. // The box would fit in the viewport
  638. if (revealAtStart) {
  639. return boxStart;
  640. }
  641. if (revealAtEnd) {
  642. return Math.max(0, boxEnd - viewportLength);
  643. }
  644. if (boxStart < viewportStart) {
  645. // The box is above the viewport
  646. return boxStart;
  647. }
  648. else if (boxEnd > viewportEnd) {
  649. // The box is below the viewport
  650. return Math.max(0, boxEnd - viewportLength);
  651. }
  652. }
  653. else {
  654. // The box would not fit in the viewport
  655. // Reveal the beginning of the box
  656. return boxStart;
  657. }
  658. return viewportStart;
  659. }
  660. }
  661. /**
  662. * Adds this amount of pixels to the right of lines (no-one wants to type near the edge of the viewport)
  663. */
  664. ViewLines.HORIZONTAL_EXTRA_PX = 30;