b53bf92296e2564eced4d1b82d34dcc847a32e1cf4ce73750947e8f20b7184afdacc17a8184f76a1282d62f182c89850b8ed0f1bceaf32b030b38e9d464aa9 41 KB

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