viewLineRenderer.js 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875
  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 strings from '../../../base/common/strings.js';
  6. import { createStringBuilder } from '../core/stringBuilder.js';
  7. import { LineDecoration, LineDecorationsNormalizer } from './lineDecorations.js';
  8. class LinePart {
  9. constructor(endIndex, type, metadata) {
  10. this._linePartBrand = undefined;
  11. this.endIndex = endIndex;
  12. this.type = type;
  13. this.metadata = metadata;
  14. }
  15. isWhitespace() {
  16. return (this.metadata & 1 /* IS_WHITESPACE_MASK */ ? true : false);
  17. }
  18. isPseudoAfter() {
  19. return (this.metadata & 4 /* PSEUDO_AFTER_MASK */ ? true : false);
  20. }
  21. }
  22. export class LineRange {
  23. constructor(startIndex, endIndex) {
  24. this.startOffset = startIndex;
  25. this.endOffset = endIndex;
  26. }
  27. equals(otherLineRange) {
  28. return this.startOffset === otherLineRange.startOffset
  29. && this.endOffset === otherLineRange.endOffset;
  30. }
  31. }
  32. export class RenderLineInput {
  33. constructor(useMonospaceOptimizations, canUseHalfwidthRightwardsArrow, lineContent, continuesWithWrappedLine, isBasicASCII, containsRTL, fauxIndentLength, lineTokens, lineDecorations, tabSize, startVisibleColumn, spaceWidth, middotWidth, wsmiddotWidth, stopRenderingLineAfter, renderWhitespace, renderControlCharacters, fontLigatures, selectionsOnLine) {
  34. this.useMonospaceOptimizations = useMonospaceOptimizations;
  35. this.canUseHalfwidthRightwardsArrow = canUseHalfwidthRightwardsArrow;
  36. this.lineContent = lineContent;
  37. this.continuesWithWrappedLine = continuesWithWrappedLine;
  38. this.isBasicASCII = isBasicASCII;
  39. this.containsRTL = containsRTL;
  40. this.fauxIndentLength = fauxIndentLength;
  41. this.lineTokens = lineTokens;
  42. this.lineDecorations = lineDecorations.sort(LineDecoration.compare);
  43. this.tabSize = tabSize;
  44. this.startVisibleColumn = startVisibleColumn;
  45. this.spaceWidth = spaceWidth;
  46. this.stopRenderingLineAfter = stopRenderingLineAfter;
  47. this.renderWhitespace = (renderWhitespace === 'all'
  48. ? 4 /* All */
  49. : renderWhitespace === 'boundary'
  50. ? 1 /* Boundary */
  51. : renderWhitespace === 'selection'
  52. ? 2 /* Selection */
  53. : renderWhitespace === 'trailing'
  54. ? 3 /* Trailing */
  55. : 0 /* None */);
  56. this.renderControlCharacters = renderControlCharacters;
  57. this.fontLigatures = fontLigatures;
  58. this.selectionsOnLine = selectionsOnLine && selectionsOnLine.sort((a, b) => a.startOffset < b.startOffset ? -1 : 1);
  59. const wsmiddotDiff = Math.abs(wsmiddotWidth - spaceWidth);
  60. const middotDiff = Math.abs(middotWidth - spaceWidth);
  61. if (wsmiddotDiff < middotDiff) {
  62. this.renderSpaceWidth = wsmiddotWidth;
  63. this.renderSpaceCharCode = 0x2E31; // U+2E31 - WORD SEPARATOR MIDDLE DOT
  64. }
  65. else {
  66. this.renderSpaceWidth = middotWidth;
  67. this.renderSpaceCharCode = 0xB7; // U+00B7 - MIDDLE DOT
  68. }
  69. }
  70. sameSelection(otherSelections) {
  71. if (this.selectionsOnLine === null) {
  72. return otherSelections === null;
  73. }
  74. if (otherSelections === null) {
  75. return false;
  76. }
  77. if (otherSelections.length !== this.selectionsOnLine.length) {
  78. return false;
  79. }
  80. for (let i = 0; i < this.selectionsOnLine.length; i++) {
  81. if (!this.selectionsOnLine[i].equals(otherSelections[i])) {
  82. return false;
  83. }
  84. }
  85. return true;
  86. }
  87. equals(other) {
  88. return (this.useMonospaceOptimizations === other.useMonospaceOptimizations
  89. && this.canUseHalfwidthRightwardsArrow === other.canUseHalfwidthRightwardsArrow
  90. && this.lineContent === other.lineContent
  91. && this.continuesWithWrappedLine === other.continuesWithWrappedLine
  92. && this.isBasicASCII === other.isBasicASCII
  93. && this.containsRTL === other.containsRTL
  94. && this.fauxIndentLength === other.fauxIndentLength
  95. && this.tabSize === other.tabSize
  96. && this.startVisibleColumn === other.startVisibleColumn
  97. && this.spaceWidth === other.spaceWidth
  98. && this.renderSpaceWidth === other.renderSpaceWidth
  99. && this.renderSpaceCharCode === other.renderSpaceCharCode
  100. && this.stopRenderingLineAfter === other.stopRenderingLineAfter
  101. && this.renderWhitespace === other.renderWhitespace
  102. && this.renderControlCharacters === other.renderControlCharacters
  103. && this.fontLigatures === other.fontLigatures
  104. && LineDecoration.equalsArr(this.lineDecorations, other.lineDecorations)
  105. && this.lineTokens.equals(other.lineTokens)
  106. && this.sameSelection(other.selectionsOnLine));
  107. }
  108. }
  109. export class DomPosition {
  110. constructor(partIndex, charIndex) {
  111. this.partIndex = partIndex;
  112. this.charIndex = charIndex;
  113. }
  114. }
  115. /**
  116. * Provides a both direction mapping between a line's character and its rendered position.
  117. */
  118. export class CharacterMapping {
  119. constructor(length, partCount) {
  120. this.length = length;
  121. this._data = new Uint32Array(this.length);
  122. this._absoluteOffsets = new Uint32Array(this.length);
  123. }
  124. static getPartIndex(partData) {
  125. return (partData & 4294901760 /* PART_INDEX_MASK */) >>> 16 /* PART_INDEX_OFFSET */;
  126. }
  127. static getCharIndex(partData) {
  128. return (partData & 65535 /* CHAR_INDEX_MASK */) >>> 0 /* CHAR_INDEX_OFFSET */;
  129. }
  130. setColumnInfo(column, partIndex, charIndex, partAbsoluteOffset) {
  131. const partData = ((partIndex << 16 /* PART_INDEX_OFFSET */)
  132. | (charIndex << 0 /* CHAR_INDEX_OFFSET */)) >>> 0;
  133. this._data[column - 1] = partData;
  134. this._absoluteOffsets[column - 1] = partAbsoluteOffset + charIndex;
  135. }
  136. getAbsoluteOffset(column) {
  137. if (this._absoluteOffsets.length === 0) {
  138. // No characters on this line
  139. return 0;
  140. }
  141. return this._absoluteOffsets[column - 1];
  142. }
  143. charOffsetToPartData(charOffset) {
  144. if (this.length === 0) {
  145. return 0;
  146. }
  147. if (charOffset < 0) {
  148. return this._data[0];
  149. }
  150. if (charOffset >= this.length) {
  151. return this._data[this.length - 1];
  152. }
  153. return this._data[charOffset];
  154. }
  155. getDomPosition(column) {
  156. const partData = this.charOffsetToPartData(column - 1);
  157. const partIndex = CharacterMapping.getPartIndex(partData);
  158. const charIndex = CharacterMapping.getCharIndex(partData);
  159. return new DomPosition(partIndex, charIndex);
  160. }
  161. getColumn(domPosition, partLength) {
  162. const charOffset = this.partDataToCharOffset(domPosition.partIndex, partLength, domPosition.charIndex);
  163. return charOffset + 1;
  164. }
  165. partDataToCharOffset(partIndex, partLength, charIndex) {
  166. if (this.length === 0) {
  167. return 0;
  168. }
  169. let searchEntry = ((partIndex << 16 /* PART_INDEX_OFFSET */)
  170. | (charIndex << 0 /* CHAR_INDEX_OFFSET */)) >>> 0;
  171. let min = 0;
  172. let max = this.length - 1;
  173. while (min + 1 < max) {
  174. let mid = ((min + max) >>> 1);
  175. let midEntry = this._data[mid];
  176. if (midEntry === searchEntry) {
  177. return mid;
  178. }
  179. else if (midEntry > searchEntry) {
  180. max = mid;
  181. }
  182. else {
  183. min = mid;
  184. }
  185. }
  186. if (min === max) {
  187. return min;
  188. }
  189. let minEntry = this._data[min];
  190. let maxEntry = this._data[max];
  191. if (minEntry === searchEntry) {
  192. return min;
  193. }
  194. if (maxEntry === searchEntry) {
  195. return max;
  196. }
  197. let minPartIndex = CharacterMapping.getPartIndex(minEntry);
  198. let minCharIndex = CharacterMapping.getCharIndex(minEntry);
  199. let maxPartIndex = CharacterMapping.getPartIndex(maxEntry);
  200. let maxCharIndex;
  201. if (minPartIndex !== maxPartIndex) {
  202. // sitting between parts
  203. maxCharIndex = partLength;
  204. }
  205. else {
  206. maxCharIndex = CharacterMapping.getCharIndex(maxEntry);
  207. }
  208. let minEntryDistance = charIndex - minCharIndex;
  209. let maxEntryDistance = maxCharIndex - charIndex;
  210. if (minEntryDistance <= maxEntryDistance) {
  211. return min;
  212. }
  213. return max;
  214. }
  215. }
  216. export class RenderLineOutput {
  217. constructor(characterMapping, containsRTL, containsForeignElements) {
  218. this._renderLineOutputBrand = undefined;
  219. this.characterMapping = characterMapping;
  220. this.containsRTL = containsRTL;
  221. this.containsForeignElements = containsForeignElements;
  222. }
  223. }
  224. export function renderViewLine(input, sb) {
  225. if (input.lineContent.length === 0) {
  226. if (input.lineDecorations.length > 0) {
  227. // This line is empty, but it contains inline decorations
  228. sb.appendASCIIString(`<span>`);
  229. let beforeCount = 0;
  230. let afterCount = 0;
  231. let containsForeignElements = 0 /* None */;
  232. for (const lineDecoration of input.lineDecorations) {
  233. if (lineDecoration.type === 1 /* Before */ || lineDecoration.type === 2 /* After */) {
  234. sb.appendASCIIString(`<span class="`);
  235. sb.appendASCIIString(lineDecoration.className);
  236. sb.appendASCIIString(`"></span>`);
  237. if (lineDecoration.type === 1 /* Before */) {
  238. containsForeignElements |= 1 /* Before */;
  239. beforeCount++;
  240. }
  241. if (lineDecoration.type === 2 /* After */) {
  242. containsForeignElements |= 2 /* After */;
  243. afterCount++;
  244. }
  245. }
  246. }
  247. sb.appendASCIIString(`</span>`);
  248. const characterMapping = new CharacterMapping(1, beforeCount + afterCount);
  249. characterMapping.setColumnInfo(1, beforeCount, 0, 0);
  250. return new RenderLineOutput(characterMapping, false, containsForeignElements);
  251. }
  252. // completely empty line
  253. sb.appendASCIIString('<span><span></span></span>');
  254. return new RenderLineOutput(new CharacterMapping(0, 0), false, 0 /* None */);
  255. }
  256. return _renderLine(resolveRenderLineInput(input), sb);
  257. }
  258. export class RenderLineOutput2 {
  259. constructor(characterMapping, html, containsRTL, containsForeignElements) {
  260. this.characterMapping = characterMapping;
  261. this.html = html;
  262. this.containsRTL = containsRTL;
  263. this.containsForeignElements = containsForeignElements;
  264. }
  265. }
  266. export function renderViewLine2(input) {
  267. let sb = createStringBuilder(10000);
  268. let out = renderViewLine(input, sb);
  269. return new RenderLineOutput2(out.characterMapping, sb.build(), out.containsRTL, out.containsForeignElements);
  270. }
  271. class ResolvedRenderLineInput {
  272. constructor(fontIsMonospace, canUseHalfwidthRightwardsArrow, lineContent, len, isOverflowing, parts, containsForeignElements, fauxIndentLength, tabSize, startVisibleColumn, containsRTL, spaceWidth, renderSpaceCharCode, renderWhitespace, renderControlCharacters) {
  273. this.fontIsMonospace = fontIsMonospace;
  274. this.canUseHalfwidthRightwardsArrow = canUseHalfwidthRightwardsArrow;
  275. this.lineContent = lineContent;
  276. this.len = len;
  277. this.isOverflowing = isOverflowing;
  278. this.parts = parts;
  279. this.containsForeignElements = containsForeignElements;
  280. this.fauxIndentLength = fauxIndentLength;
  281. this.tabSize = tabSize;
  282. this.startVisibleColumn = startVisibleColumn;
  283. this.containsRTL = containsRTL;
  284. this.spaceWidth = spaceWidth;
  285. this.renderSpaceCharCode = renderSpaceCharCode;
  286. this.renderWhitespace = renderWhitespace;
  287. this.renderControlCharacters = renderControlCharacters;
  288. //
  289. }
  290. }
  291. function resolveRenderLineInput(input) {
  292. const lineContent = input.lineContent;
  293. let isOverflowing;
  294. let len;
  295. if (input.stopRenderingLineAfter !== -1 && input.stopRenderingLineAfter < lineContent.length) {
  296. isOverflowing = true;
  297. len = input.stopRenderingLineAfter;
  298. }
  299. else {
  300. isOverflowing = false;
  301. len = lineContent.length;
  302. }
  303. let tokens = transformAndRemoveOverflowing(input.lineTokens, input.fauxIndentLength, len);
  304. if (input.renderControlCharacters && !input.isBasicASCII) {
  305. // Calling `extractControlCharacters` before adding (possibly empty) line parts
  306. // for inline decorations. `extractControlCharacters` removes empty line parts.
  307. tokens = extractControlCharacters(lineContent, tokens);
  308. }
  309. if (input.renderWhitespace === 4 /* All */ ||
  310. input.renderWhitespace === 1 /* Boundary */ ||
  311. (input.renderWhitespace === 2 /* Selection */ && !!input.selectionsOnLine) ||
  312. input.renderWhitespace === 3 /* Trailing */) {
  313. tokens = _applyRenderWhitespace(input, lineContent, len, tokens);
  314. }
  315. let containsForeignElements = 0 /* None */;
  316. if (input.lineDecorations.length > 0) {
  317. for (let i = 0, len = input.lineDecorations.length; i < len; i++) {
  318. const lineDecoration = input.lineDecorations[i];
  319. if (lineDecoration.type === 3 /* RegularAffectingLetterSpacing */) {
  320. // Pretend there are foreign elements... although not 100% accurate.
  321. containsForeignElements |= 1 /* Before */;
  322. }
  323. else if (lineDecoration.type === 1 /* Before */) {
  324. containsForeignElements |= 1 /* Before */;
  325. }
  326. else if (lineDecoration.type === 2 /* After */) {
  327. containsForeignElements |= 2 /* After */;
  328. }
  329. }
  330. tokens = _applyInlineDecorations(lineContent, len, tokens, input.lineDecorations);
  331. }
  332. if (!input.containsRTL) {
  333. // We can never split RTL text, as it ruins the rendering
  334. tokens = splitLargeTokens(lineContent, tokens, !input.isBasicASCII || input.fontLigatures);
  335. }
  336. return new ResolvedRenderLineInput(input.useMonospaceOptimizations, input.canUseHalfwidthRightwardsArrow, lineContent, len, isOverflowing, tokens, containsForeignElements, input.fauxIndentLength, input.tabSize, input.startVisibleColumn, input.containsRTL, input.spaceWidth, input.renderSpaceCharCode, input.renderWhitespace, input.renderControlCharacters);
  337. }
  338. /**
  339. * In the rendering phase, characters are always looped until token.endIndex.
  340. * Ensure that all tokens end before `len` and the last one ends precisely at `len`.
  341. */
  342. function transformAndRemoveOverflowing(tokens, fauxIndentLength, len) {
  343. let result = [], resultLen = 0;
  344. // The faux indent part of the line should have no token type
  345. if (fauxIndentLength > 0) {
  346. result[resultLen++] = new LinePart(fauxIndentLength, '', 0);
  347. }
  348. for (let tokenIndex = 0, tokensLen = tokens.getCount(); tokenIndex < tokensLen; tokenIndex++) {
  349. const endIndex = tokens.getEndOffset(tokenIndex);
  350. if (endIndex <= fauxIndentLength) {
  351. // The faux indent part of the line should have no token type
  352. continue;
  353. }
  354. const type = tokens.getClassName(tokenIndex);
  355. if (endIndex >= len) {
  356. result[resultLen++] = new LinePart(len, type, 0);
  357. break;
  358. }
  359. result[resultLen++] = new LinePart(endIndex, type, 0);
  360. }
  361. return result;
  362. }
  363. /**
  364. * See https://github.com/microsoft/vscode/issues/6885.
  365. * It appears that having very large spans causes very slow reading of character positions.
  366. * So here we try to avoid that.
  367. */
  368. function splitLargeTokens(lineContent, tokens, onlyAtSpaces) {
  369. let lastTokenEndIndex = 0;
  370. let result = [], resultLen = 0;
  371. if (onlyAtSpaces) {
  372. // Split only at spaces => we need to walk each character
  373. for (let i = 0, len = tokens.length; i < len; i++) {
  374. const token = tokens[i];
  375. const tokenEndIndex = token.endIndex;
  376. if (lastTokenEndIndex + 50 /* LongToken */ < tokenEndIndex) {
  377. const tokenType = token.type;
  378. const tokenMetadata = token.metadata;
  379. let lastSpaceOffset = -1;
  380. let currTokenStart = lastTokenEndIndex;
  381. for (let j = lastTokenEndIndex; j < tokenEndIndex; j++) {
  382. if (lineContent.charCodeAt(j) === 32 /* Space */) {
  383. lastSpaceOffset = j;
  384. }
  385. if (lastSpaceOffset !== -1 && j - currTokenStart >= 50 /* LongToken */) {
  386. // Split at `lastSpaceOffset` + 1
  387. result[resultLen++] = new LinePart(lastSpaceOffset + 1, tokenType, tokenMetadata);
  388. currTokenStart = lastSpaceOffset + 1;
  389. lastSpaceOffset = -1;
  390. }
  391. }
  392. if (currTokenStart !== tokenEndIndex) {
  393. result[resultLen++] = new LinePart(tokenEndIndex, tokenType, tokenMetadata);
  394. }
  395. }
  396. else {
  397. result[resultLen++] = token;
  398. }
  399. lastTokenEndIndex = tokenEndIndex;
  400. }
  401. }
  402. else {
  403. // Split anywhere => we don't need to walk each character
  404. for (let i = 0, len = tokens.length; i < len; i++) {
  405. const token = tokens[i];
  406. const tokenEndIndex = token.endIndex;
  407. let diff = (tokenEndIndex - lastTokenEndIndex);
  408. if (diff > 50 /* LongToken */) {
  409. const tokenType = token.type;
  410. const tokenMetadata = token.metadata;
  411. const piecesCount = Math.ceil(diff / 50 /* LongToken */);
  412. for (let j = 1; j < piecesCount; j++) {
  413. let pieceEndIndex = lastTokenEndIndex + (j * 50 /* LongToken */);
  414. result[resultLen++] = new LinePart(pieceEndIndex, tokenType, tokenMetadata);
  415. }
  416. result[resultLen++] = new LinePart(tokenEndIndex, tokenType, tokenMetadata);
  417. }
  418. else {
  419. result[resultLen++] = token;
  420. }
  421. lastTokenEndIndex = tokenEndIndex;
  422. }
  423. }
  424. return result;
  425. }
  426. function isControlCharacter(charCode) {
  427. if (charCode < 32) {
  428. return (charCode !== 9 /* Tab */);
  429. }
  430. if (charCode === 127) {
  431. // DEL
  432. return true;
  433. }
  434. if ((charCode >= 0x202A && charCode <= 0x202E)
  435. || (charCode >= 0x2066 && charCode <= 0x2069)
  436. || (charCode >= 0x200E && charCode <= 0x200F)
  437. || charCode === 0x061C) {
  438. // Unicode Directional Formatting Characters
  439. // LRE U+202A LEFT-TO-RIGHT EMBEDDING
  440. // RLE U+202B RIGHT-TO-LEFT EMBEDDING
  441. // PDF U+202C POP DIRECTIONAL FORMATTING
  442. // LRO U+202D LEFT-TO-RIGHT OVERRIDE
  443. // RLO U+202E RIGHT-TO-LEFT OVERRIDE
  444. // LRI U+2066 LEFT-TO-RIGHT ISOLATE
  445. // RLI U+2067 RIGHT-TO-LEFT ISOLATE
  446. // FSI U+2068 FIRST STRONG ISOLATE
  447. // PDI U+2069 POP DIRECTIONAL ISOLATE
  448. // LRM U+200E LEFT-TO-RIGHT MARK
  449. // RLM U+200F RIGHT-TO-LEFT MARK
  450. // ALM U+061C ARABIC LETTER MARK
  451. return true;
  452. }
  453. return false;
  454. }
  455. function extractControlCharacters(lineContent, tokens) {
  456. let result = [];
  457. let lastLinePart = new LinePart(0, '', 0);
  458. let charOffset = 0;
  459. for (const token of tokens) {
  460. const tokenEndIndex = token.endIndex;
  461. for (; charOffset < tokenEndIndex; charOffset++) {
  462. const charCode = lineContent.charCodeAt(charOffset);
  463. if (isControlCharacter(charCode)) {
  464. if (charOffset > lastLinePart.endIndex) {
  465. // emit previous part if it has text
  466. lastLinePart = new LinePart(charOffset, token.type, token.metadata);
  467. result.push(lastLinePart);
  468. }
  469. lastLinePart = new LinePart(charOffset + 1, 'mtkcontrol', token.metadata);
  470. result.push(lastLinePart);
  471. }
  472. }
  473. if (charOffset > lastLinePart.endIndex) {
  474. // emit previous part if it has text
  475. lastLinePart = new LinePart(tokenEndIndex, token.type, token.metadata);
  476. result.push(lastLinePart);
  477. }
  478. }
  479. return result;
  480. }
  481. /**
  482. * Whitespace is rendered by "replacing" tokens with a special-purpose `mtkw` type that is later recognized in the rendering phase.
  483. * Moreover, a token is created for every visual indent because on some fonts the glyphs used for rendering whitespace (&rarr; or &middot;) do not have the same width as &nbsp;.
  484. * The rendering phase will generate `style="width:..."` for these tokens.
  485. */
  486. function _applyRenderWhitespace(input, lineContent, len, tokens) {
  487. const continuesWithWrappedLine = input.continuesWithWrappedLine;
  488. const fauxIndentLength = input.fauxIndentLength;
  489. const tabSize = input.tabSize;
  490. const startVisibleColumn = input.startVisibleColumn;
  491. const useMonospaceOptimizations = input.useMonospaceOptimizations;
  492. const selections = input.selectionsOnLine;
  493. const onlyBoundary = (input.renderWhitespace === 1 /* Boundary */);
  494. const onlyTrailing = (input.renderWhitespace === 3 /* Trailing */);
  495. const generateLinePartForEachWhitespace = (input.renderSpaceWidth !== input.spaceWidth);
  496. let result = [], resultLen = 0;
  497. let tokenIndex = 0;
  498. let tokenType = tokens[tokenIndex].type;
  499. let tokenEndIndex = tokens[tokenIndex].endIndex;
  500. const tokensLength = tokens.length;
  501. let lineIsEmptyOrWhitespace = false;
  502. let firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineContent);
  503. let lastNonWhitespaceIndex;
  504. if (firstNonWhitespaceIndex === -1) {
  505. lineIsEmptyOrWhitespace = true;
  506. firstNonWhitespaceIndex = len;
  507. lastNonWhitespaceIndex = len;
  508. }
  509. else {
  510. lastNonWhitespaceIndex = strings.lastNonWhitespaceIndex(lineContent);
  511. }
  512. let wasInWhitespace = false;
  513. let currentSelectionIndex = 0;
  514. let currentSelection = selections && selections[currentSelectionIndex];
  515. let tmpIndent = startVisibleColumn % tabSize;
  516. for (let charIndex = fauxIndentLength; charIndex < len; charIndex++) {
  517. const chCode = lineContent.charCodeAt(charIndex);
  518. if (currentSelection && charIndex >= currentSelection.endOffset) {
  519. currentSelectionIndex++;
  520. currentSelection = selections && selections[currentSelectionIndex];
  521. }
  522. let isInWhitespace;
  523. if (charIndex < firstNonWhitespaceIndex || charIndex > lastNonWhitespaceIndex) {
  524. // in leading or trailing whitespace
  525. isInWhitespace = true;
  526. }
  527. else if (chCode === 9 /* Tab */) {
  528. // a tab character is rendered both in all and boundary cases
  529. isInWhitespace = true;
  530. }
  531. else if (chCode === 32 /* Space */) {
  532. // hit a space character
  533. if (onlyBoundary) {
  534. // rendering only boundary whitespace
  535. if (wasInWhitespace) {
  536. isInWhitespace = true;
  537. }
  538. else {
  539. const nextChCode = (charIndex + 1 < len ? lineContent.charCodeAt(charIndex + 1) : 0 /* Null */);
  540. isInWhitespace = (nextChCode === 32 /* Space */ || nextChCode === 9 /* Tab */);
  541. }
  542. }
  543. else {
  544. isInWhitespace = true;
  545. }
  546. }
  547. else {
  548. isInWhitespace = false;
  549. }
  550. // If rendering whitespace on selection, check that the charIndex falls within a selection
  551. if (isInWhitespace && selections) {
  552. isInWhitespace = !!currentSelection && currentSelection.startOffset <= charIndex && currentSelection.endOffset > charIndex;
  553. }
  554. // If rendering only trailing whitespace, check that the charIndex points to trailing whitespace.
  555. if (isInWhitespace && onlyTrailing) {
  556. isInWhitespace = lineIsEmptyOrWhitespace || charIndex > lastNonWhitespaceIndex;
  557. }
  558. if (wasInWhitespace) {
  559. // was in whitespace token
  560. if (!isInWhitespace || (!useMonospaceOptimizations && tmpIndent >= tabSize)) {
  561. // leaving whitespace token or entering a new indent
  562. if (generateLinePartForEachWhitespace) {
  563. const lastEndIndex = (resultLen > 0 ? result[resultLen - 1].endIndex : fauxIndentLength);
  564. for (let i = lastEndIndex + 1; i <= charIndex; i++) {
  565. result[resultLen++] = new LinePart(i, 'mtkw', 1 /* IS_WHITESPACE */);
  566. }
  567. }
  568. else {
  569. result[resultLen++] = new LinePart(charIndex, 'mtkw', 1 /* IS_WHITESPACE */);
  570. }
  571. tmpIndent = tmpIndent % tabSize;
  572. }
  573. }
  574. else {
  575. // was in regular token
  576. if (charIndex === tokenEndIndex || (isInWhitespace && charIndex > fauxIndentLength)) {
  577. result[resultLen++] = new LinePart(charIndex, tokenType, 0);
  578. tmpIndent = tmpIndent % tabSize;
  579. }
  580. }
  581. if (chCode === 9 /* Tab */) {
  582. tmpIndent = tabSize;
  583. }
  584. else if (strings.isFullWidthCharacter(chCode)) {
  585. tmpIndent += 2;
  586. }
  587. else {
  588. tmpIndent++;
  589. }
  590. wasInWhitespace = isInWhitespace;
  591. while (charIndex === tokenEndIndex) {
  592. tokenIndex++;
  593. if (tokenIndex < tokensLength) {
  594. tokenType = tokens[tokenIndex].type;
  595. tokenEndIndex = tokens[tokenIndex].endIndex;
  596. }
  597. else {
  598. break;
  599. }
  600. }
  601. }
  602. let generateWhitespace = false;
  603. if (wasInWhitespace) {
  604. // was in whitespace token
  605. if (continuesWithWrappedLine && onlyBoundary) {
  606. let lastCharCode = (len > 0 ? lineContent.charCodeAt(len - 1) : 0 /* Null */);
  607. let prevCharCode = (len > 1 ? lineContent.charCodeAt(len - 2) : 0 /* Null */);
  608. let isSingleTrailingSpace = (lastCharCode === 32 /* Space */ && (prevCharCode !== 32 /* Space */ && prevCharCode !== 9 /* Tab */));
  609. if (!isSingleTrailingSpace) {
  610. generateWhitespace = true;
  611. }
  612. }
  613. else {
  614. generateWhitespace = true;
  615. }
  616. }
  617. if (generateWhitespace) {
  618. if (generateLinePartForEachWhitespace) {
  619. const lastEndIndex = (resultLen > 0 ? result[resultLen - 1].endIndex : fauxIndentLength);
  620. for (let i = lastEndIndex + 1; i <= len; i++) {
  621. result[resultLen++] = new LinePart(i, 'mtkw', 1 /* IS_WHITESPACE */);
  622. }
  623. }
  624. else {
  625. result[resultLen++] = new LinePart(len, 'mtkw', 1 /* IS_WHITESPACE */);
  626. }
  627. }
  628. else {
  629. result[resultLen++] = new LinePart(len, tokenType, 0);
  630. }
  631. return result;
  632. }
  633. /**
  634. * Inline decorations are "merged" on top of tokens.
  635. * Special care must be taken when multiple inline decorations are at play and they overlap.
  636. */
  637. function _applyInlineDecorations(lineContent, len, tokens, _lineDecorations) {
  638. _lineDecorations.sort(LineDecoration.compare);
  639. const lineDecorations = LineDecorationsNormalizer.normalize(lineContent, _lineDecorations);
  640. const lineDecorationsLen = lineDecorations.length;
  641. let lineDecorationIndex = 0;
  642. let result = [], resultLen = 0, lastResultEndIndex = 0;
  643. for (let tokenIndex = 0, len = tokens.length; tokenIndex < len; tokenIndex++) {
  644. const token = tokens[tokenIndex];
  645. const tokenEndIndex = token.endIndex;
  646. const tokenType = token.type;
  647. const tokenMetadata = token.metadata;
  648. while (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset < tokenEndIndex) {
  649. const lineDecoration = lineDecorations[lineDecorationIndex];
  650. if (lineDecoration.startOffset > lastResultEndIndex) {
  651. lastResultEndIndex = lineDecoration.startOffset;
  652. result[resultLen++] = new LinePart(lastResultEndIndex, tokenType, tokenMetadata);
  653. }
  654. if (lineDecoration.endOffset + 1 <= tokenEndIndex) {
  655. // This line decoration ends before this token ends
  656. lastResultEndIndex = lineDecoration.endOffset + 1;
  657. result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className, tokenMetadata | lineDecoration.metadata);
  658. lineDecorationIndex++;
  659. }
  660. else {
  661. // This line decoration continues on to the next token
  662. lastResultEndIndex = tokenEndIndex;
  663. result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className, tokenMetadata | lineDecoration.metadata);
  664. break;
  665. }
  666. }
  667. if (tokenEndIndex > lastResultEndIndex) {
  668. lastResultEndIndex = tokenEndIndex;
  669. result[resultLen++] = new LinePart(lastResultEndIndex, tokenType, tokenMetadata);
  670. }
  671. }
  672. const lastTokenEndIndex = tokens[tokens.length - 1].endIndex;
  673. if (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset === lastTokenEndIndex) {
  674. while (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset === lastTokenEndIndex) {
  675. const lineDecoration = lineDecorations[lineDecorationIndex];
  676. result[resultLen++] = new LinePart(lastResultEndIndex, lineDecoration.className, lineDecoration.metadata);
  677. lineDecorationIndex++;
  678. }
  679. }
  680. return result;
  681. }
  682. /**
  683. * This function is on purpose not split up into multiple functions to allow runtime type inference (i.e. performance reasons).
  684. * Notice how all the needed data is fully resolved and passed in (i.e. no other calls).
  685. */
  686. function _renderLine(input, sb) {
  687. const fontIsMonospace = input.fontIsMonospace;
  688. const canUseHalfwidthRightwardsArrow = input.canUseHalfwidthRightwardsArrow;
  689. const containsForeignElements = input.containsForeignElements;
  690. const lineContent = input.lineContent;
  691. const len = input.len;
  692. const isOverflowing = input.isOverflowing;
  693. const parts = input.parts;
  694. const fauxIndentLength = input.fauxIndentLength;
  695. const tabSize = input.tabSize;
  696. const startVisibleColumn = input.startVisibleColumn;
  697. const containsRTL = input.containsRTL;
  698. const spaceWidth = input.spaceWidth;
  699. const renderSpaceCharCode = input.renderSpaceCharCode;
  700. const renderWhitespace = input.renderWhitespace;
  701. const renderControlCharacters = input.renderControlCharacters;
  702. const characterMapping = new CharacterMapping(len + 1, parts.length);
  703. let lastCharacterMappingDefined = false;
  704. let charIndex = 0;
  705. let visibleColumn = startVisibleColumn;
  706. let charOffsetInPart = 0;
  707. let partDisplacement = 0;
  708. let prevPartContentCnt = 0;
  709. let partAbsoluteOffset = 0;
  710. if (containsRTL) {
  711. sb.appendASCIIString('<span dir="ltr">');
  712. }
  713. else {
  714. sb.appendASCIIString('<span>');
  715. }
  716. for (let partIndex = 0, tokensLen = parts.length; partIndex < tokensLen; partIndex++) {
  717. partAbsoluteOffset += prevPartContentCnt;
  718. const part = parts[partIndex];
  719. const partEndIndex = part.endIndex;
  720. const partType = part.type;
  721. const partRendersWhitespace = (renderWhitespace !== 0 /* None */ && part.isWhitespace());
  722. const partRendersWhitespaceWithWidth = partRendersWhitespace && !fontIsMonospace && (partType === 'mtkw' /*only whitespace*/ || !containsForeignElements);
  723. const partIsEmptyAndHasPseudoAfter = (charIndex === partEndIndex && part.isPseudoAfter());
  724. charOffsetInPart = 0;
  725. sb.appendASCIIString('<span class="');
  726. sb.appendASCIIString(partRendersWhitespaceWithWidth ? 'mtkz' : partType);
  727. sb.appendASCII(34 /* DoubleQuote */);
  728. if (partRendersWhitespace) {
  729. let partContentCnt = 0;
  730. {
  731. let _charIndex = charIndex;
  732. let _visibleColumn = visibleColumn;
  733. for (; _charIndex < partEndIndex; _charIndex++) {
  734. const charCode = lineContent.charCodeAt(_charIndex);
  735. const charWidth = (charCode === 9 /* Tab */ ? (tabSize - (_visibleColumn % tabSize)) : 1) | 0;
  736. partContentCnt += charWidth;
  737. if (_charIndex >= fauxIndentLength) {
  738. _visibleColumn += charWidth;
  739. }
  740. }
  741. }
  742. if (partRendersWhitespaceWithWidth) {
  743. sb.appendASCIIString(' style="width:');
  744. sb.appendASCIIString(String(spaceWidth * partContentCnt));
  745. sb.appendASCIIString('px"');
  746. }
  747. sb.appendASCII(62 /* GreaterThan */);
  748. for (; charIndex < partEndIndex; charIndex++) {
  749. characterMapping.setColumnInfo(charIndex + 1, partIndex - partDisplacement, charOffsetInPart, partAbsoluteOffset);
  750. partDisplacement = 0;
  751. const charCode = lineContent.charCodeAt(charIndex);
  752. let charWidth;
  753. if (charCode === 9 /* Tab */) {
  754. charWidth = (tabSize - (visibleColumn % tabSize)) | 0;
  755. if (!canUseHalfwidthRightwardsArrow || charWidth > 1) {
  756. sb.write1(0x2192); // RIGHTWARDS ARROW
  757. }
  758. else {
  759. sb.write1(0xFFEB); // HALFWIDTH RIGHTWARDS ARROW
  760. }
  761. for (let space = 2; space <= charWidth; space++) {
  762. sb.write1(0xA0); // &nbsp;
  763. }
  764. }
  765. else { // must be CharCode.Space
  766. charWidth = 1;
  767. sb.write1(renderSpaceCharCode); // &middot; or word separator middle dot
  768. }
  769. charOffsetInPart += charWidth;
  770. if (charIndex >= fauxIndentLength) {
  771. visibleColumn += charWidth;
  772. }
  773. }
  774. prevPartContentCnt = partContentCnt;
  775. }
  776. else {
  777. let partContentCnt = 0;
  778. sb.appendASCII(62 /* GreaterThan */);
  779. for (; charIndex < partEndIndex; charIndex++) {
  780. characterMapping.setColumnInfo(charIndex + 1, partIndex - partDisplacement, charOffsetInPart, partAbsoluteOffset);
  781. partDisplacement = 0;
  782. const charCode = lineContent.charCodeAt(charIndex);
  783. let producedCharacters = 1;
  784. let charWidth = 1;
  785. switch (charCode) {
  786. case 9 /* Tab */:
  787. producedCharacters = (tabSize - (visibleColumn % tabSize));
  788. charWidth = producedCharacters;
  789. for (let space = 1; space <= producedCharacters; space++) {
  790. sb.write1(0xA0); // &nbsp;
  791. }
  792. break;
  793. case 32 /* Space */:
  794. sb.write1(0xA0); // &nbsp;
  795. break;
  796. case 60 /* LessThan */:
  797. sb.appendASCIIString('&lt;');
  798. break;
  799. case 62 /* GreaterThan */:
  800. sb.appendASCIIString('&gt;');
  801. break;
  802. case 38 /* Ampersand */:
  803. sb.appendASCIIString('&amp;');
  804. break;
  805. case 0 /* Null */:
  806. if (renderControlCharacters) {
  807. // See https://unicode-table.com/en/blocks/control-pictures/
  808. sb.write1(9216);
  809. }
  810. else {
  811. sb.appendASCIIString('&#00;');
  812. }
  813. break;
  814. case 65279 /* UTF8_BOM */:
  815. case 8232 /* LINE_SEPARATOR */:
  816. case 8233 /* PARAGRAPH_SEPARATOR */:
  817. case 133 /* NEXT_LINE */:
  818. sb.write1(0xFFFD);
  819. break;
  820. default:
  821. if (strings.isFullWidthCharacter(charCode)) {
  822. charWidth++;
  823. }
  824. // See https://unicode-table.com/en/blocks/control-pictures/
  825. if (renderControlCharacters && charCode < 32) {
  826. sb.write1(9216 + charCode);
  827. }
  828. else if (renderControlCharacters && charCode === 127) {
  829. // DEL
  830. sb.write1(9249);
  831. }
  832. else if (renderControlCharacters && isControlCharacter(charCode)) {
  833. sb.appendASCIIString('[U+');
  834. sb.appendASCIIString(to4CharHex(charCode));
  835. sb.appendASCIIString(']');
  836. producedCharacters = 8;
  837. }
  838. else {
  839. sb.write1(charCode);
  840. }
  841. }
  842. charOffsetInPart += producedCharacters;
  843. partContentCnt += producedCharacters;
  844. if (charIndex >= fauxIndentLength) {
  845. visibleColumn += charWidth;
  846. }
  847. }
  848. prevPartContentCnt = partContentCnt;
  849. }
  850. if (partIsEmptyAndHasPseudoAfter) {
  851. partDisplacement++;
  852. }
  853. else {
  854. partDisplacement = 0;
  855. }
  856. if (charIndex >= len && !lastCharacterMappingDefined && part.isPseudoAfter()) {
  857. lastCharacterMappingDefined = true;
  858. characterMapping.setColumnInfo(charIndex + 1, partIndex, charOffsetInPart, partAbsoluteOffset);
  859. }
  860. sb.appendASCIIString('</span>');
  861. }
  862. if (!lastCharacterMappingDefined) {
  863. // When getting client rects for the last character, we will position the
  864. // text range at the end of the span, insteaf of at the beginning of next span
  865. characterMapping.setColumnInfo(len + 1, parts.length - 1, charOffsetInPart, partAbsoluteOffset);
  866. }
  867. if (isOverflowing) {
  868. sb.appendASCIIString('<span>&hellip;</span>');
  869. }
  870. sb.appendASCIIString('</span>');
  871. return new RenderLineOutput(characterMapping, containsRTL, containsForeignElements);
  872. }
  873. function to4CharHex(n) {
  874. return n.toString(16).toUpperCase().padStart(4, '0');
  875. }