Files
paseo/packages/highlight/src/highlighter.ts
T
Mohamed BoudraandGitHub 4bda2dfea9 Edit workspace files directly on web (#2270)
* feat(files): edit workspace files on web

Keep source buffers synchronized with host file changes and require an explicit overwrite or reload when revisions diverge.

* feat(panels): surface and protect modified tabs

Expose tooltip and modification state through the generic panel boundary so tabs can show stable metadata and guard every close route consistently.

* fix(tests): use portable fake timeout handle

* fix(files): harden editor conflict handling

Preserve modified panel state across tab eviction, use precise revisions for optimistic writes, coalesce concurrent file watchers, and localize the editor interface.

* fix(files): close editor concurrency gaps

Coalesce clean reloads, preserve subscriber identities and file permissions, suspend pending saves during close confirmation, and carry precise revisions through file reads.

* test(files): expect read revision metadata
2026-07-21 04:41:19 +08:00

70 lines
2.0 KiB
TypeScript

import { highlightTree } from "@lezer/highlight";
import type { HighlightStyle, HighlightToken } from "./types.js";
import { getParserForFile } from "./parsers.js";
import { staticSyntaxHighlighter } from "./syntax-roles.js";
export function highlightCode(code: string, filename: string): HighlightToken[][] {
const parser = getParserForFile(filename);
if (!parser) {
return code.split("\n").map((line) => [{ text: line, style: null }]);
}
const tree = parser.parse(code);
const lines = code.split("\n");
const result: HighlightToken[][] = [];
for (let i = 0; i < lines.length; i++) {
result.push([]);
}
// Build a map of character positions to styles
const styleMap: Array<HighlightStyle | null> = Array.from({ length: code.length }, () => null);
highlightTree(tree, staticSyntaxHighlighter, (from, to, classes) => {
for (let i = from; i < to && i < styleMap.length; i++) {
styleMap[i] = classes as HighlightStyle;
}
});
// Convert style map to tokens per line
let pos = 0;
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
const line = lines[lineIndex];
if (line.length === 0) {
result[lineIndex].push({ text: "", style: null });
pos++; // skip newline
continue;
}
let currentToken: HighlightToken = { text: "", style: styleMap[pos] };
for (let i = 0; i < line.length; i++) {
const charStyle = styleMap[pos + i];
if (charStyle === currentToken.style) {
currentToken.text += line[i];
} else {
if (currentToken.text) {
result[lineIndex].push(currentToken);
}
currentToken = { text: line[i], style: charStyle };
}
}
if (currentToken.text) {
result[lineIndex].push(currentToken);
}
pos += line.length + 1; // +1 for newline
}
return result;
}
export function highlightLine(line: string, filename: string): HighlightToken[] {
const result = highlightCode(line, filename);
return result[0] ?? [{ text: line, style: null }];
}