Path: blob/main/src/vs/editor/common/languages/textToHtmlTokenizer.ts
5252 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { CharCode } from '../../../base/common/charCode.js';6import * as strings from '../../../base/common/strings.js';7import { IViewLineTokens, LineTokens } from '../tokens/lineTokens.js';8import { ILanguageIdCodec, IState, ITokenizationSupport, TokenizationRegistry } from '../languages.js';9import { LanguageId } from '../encodedTokenAttributes.js';10import { NullState, nullTokenizeEncoded } from './nullTokenize.js';11import { ILanguageService } from './language.js';1213export type IReducedTokenizationSupport = Omit<ITokenizationSupport, 'tokenize'>;1415const fallback: IReducedTokenizationSupport = {16getInitialState: () => NullState,17tokenizeEncoded: (buffer: string, hasEOL: boolean, state: IState) => nullTokenizeEncoded(LanguageId.Null, state)18};1920export function tokenizeToStringSync(languageService: ILanguageService, text: string, languageId: string): string {21return _tokenizeToString(text, languageService.languageIdCodec, TokenizationRegistry.get(languageId) || fallback);22}2324export async function tokenizeToString(languageService: ILanguageService, text: string, languageId: string | null): Promise<string> {25if (!languageId) {26return _tokenizeToString(text, languageService.languageIdCodec, fallback);27}28const tokenizationSupport = await TokenizationRegistry.getOrCreate(languageId);29return _tokenizeToString(text, languageService.languageIdCodec, tokenizationSupport || fallback);30}3132export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens, colorMap: string[], startOffset: number, endOffset: number, tabSize: number, useNbsp: boolean): string {33let result = `<div>`;34let charIndex = 0;35let width = 0;3637let prevIsSpace = true;3839for (let tokenIndex = 0, tokenCount = viewLineTokens.getCount(); tokenIndex < tokenCount; tokenIndex++) {40const tokenEndIndex = viewLineTokens.getEndOffset(tokenIndex);41let partContent = '';4243for (; charIndex < tokenEndIndex && charIndex < endOffset; charIndex++) {44const charCode = text.charCodeAt(charIndex);45const isTab = charCode === CharCode.Tab;4647width += strings.isFullWidthCharacter(charCode) ? 2 : (isTab ? 0 : 1);4849if (charIndex < startOffset) {50if (isTab) {51const remainder = width % tabSize;52width += remainder === 0 ? tabSize : tabSize - remainder;53}54continue;55}5657switch (charCode) {58case CharCode.Tab: {59const remainder = width % tabSize;60const insertSpacesCount = remainder === 0 ? tabSize : tabSize - remainder;61width += insertSpacesCount;62let spacesRemaining = insertSpacesCount;63while (spacesRemaining > 0) {64if (useNbsp && prevIsSpace) {65partContent += ' ';66prevIsSpace = false;67} else {68partContent += ' ';69prevIsSpace = true;70}71spacesRemaining--;72}73break;74}75case CharCode.LessThan:76partContent += '<';77prevIsSpace = false;78break;7980case CharCode.GreaterThan:81partContent += '>';82prevIsSpace = false;83break;8485case CharCode.Ampersand:86partContent += '&';87prevIsSpace = false;88break;8990case CharCode.Null:91partContent += '�';92prevIsSpace = false;93break;9495case CharCode.UTF8_BOM:96case CharCode.LINE_SEPARATOR:97case CharCode.PARAGRAPH_SEPARATOR:98case CharCode.NEXT_LINE:99partContent += '\ufffd';100prevIsSpace = false;101break;102103case CharCode.CarriageReturn:104// zero width space, because carriage return would introduce a line break105partContent += '​';106prevIsSpace = false;107break;108109case CharCode.Space:110if (useNbsp && prevIsSpace) {111partContent += ' ';112prevIsSpace = false;113} else {114partContent += ' ';115prevIsSpace = true;116}117break;118119default:120partContent += String.fromCharCode(charCode);121prevIsSpace = false;122}123}124125if (tokenEndIndex <= startOffset) {126continue;127}128129result += `<span style="${viewLineTokens.getInlineStyle(tokenIndex, colorMap)}">${partContent}</span>`;130131if (tokenEndIndex > endOffset || charIndex >= endOffset || startOffset >= endOffset) {132break;133}134}135136result += `</div>`;137return result;138}139140export function _tokenizeToString(text: string, languageIdCodec: ILanguageIdCodec, tokenizationSupport: IReducedTokenizationSupport): string {141let result = `<div class="monaco-tokenized-source">`;142const lines = strings.splitLines(text);143let currentState = tokenizationSupport.getInitialState();144for (let i = 0, len = lines.length; i < len; i++) {145const line = lines[i];146147if (i > 0) {148result += `<br/>`;149}150151const tokenizationResult = tokenizationSupport.tokenizeEncoded(line, true, currentState);152LineTokens.convertToEndOffset(tokenizationResult.tokens, line.length);153const lineTokens = new LineTokens(tokenizationResult.tokens, line, languageIdCodec);154const viewLineTokens = lineTokens.inflate();155156let startOffset = 0;157for (let j = 0, lenJ = viewLineTokens.getCount(); j < lenJ; j++) {158const type = viewLineTokens.getClassName(j);159const endIndex = viewLineTokens.getEndOffset(j);160result += `<span class="${type}">${strings.escape(line.substring(startOffset, endIndex))}</span>`;161startOffset = endIndex;162}163164currentState = tokenizationResult.endState;165}166167result += `</div>`;168return result;169}170171172