Files
puaros/packages/ipuaro/src/tui/utils/syntax-highlighter.ts
imfozilbek a589b0dfc4 feat(ipuaro): add multiline input and syntax highlighting
- Multiline input support with Shift+Enter for new lines
- Auto-height adjustment and line navigation
- Syntax highlighting in DiffView for added lines
- Language detection from file extensions
- Config options for multiline and syntaxHighlight
2025-12-02 00:31:21 +05:00

168 lines
3.9 KiB
TypeScript

/**
* Simple syntax highlighter for terminal UI.
* Highlights keywords, strings, comments, numbers, and operators.
*/
export type Language = "typescript" | "javascript" | "tsx" | "jsx" | "json" | "yaml" | "unknown"
export interface HighlightedToken {
text: string
color: string
}
const KEYWORDS = new Set([
"abstract",
"any",
"as",
"async",
"await",
"boolean",
"break",
"case",
"catch",
"class",
"const",
"constructor",
"continue",
"debugger",
"declare",
"default",
"delete",
"do",
"else",
"enum",
"export",
"extends",
"false",
"finally",
"for",
"from",
"function",
"get",
"if",
"implements",
"import",
"in",
"instanceof",
"interface",
"let",
"module",
"namespace",
"new",
"null",
"number",
"of",
"package",
"private",
"protected",
"public",
"readonly",
"require",
"return",
"set",
"static",
"string",
"super",
"switch",
"this",
"throw",
"true",
"try",
"type",
"typeof",
"undefined",
"var",
"void",
"while",
"with",
"yield",
])
export function detectLanguage(filePath: string): Language {
const ext = filePath.split(".").pop()?.toLowerCase()
switch (ext) {
case "ts":
return "typescript"
case "tsx":
return "tsx"
case "js":
return "javascript"
case "jsx":
return "jsx"
case "json":
return "json"
case "yaml":
case "yml":
return "yaml"
default:
return "unknown"
}
}
const COMMENT_REGEX = /^(\/\/.*|\/\*[\s\S]*?\*\/)/
const STRING_REGEX = /^("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)/
const NUMBER_REGEX = /^(\b\d+\.?\d*\b)/
const WORD_REGEX = /^([a-zA-Z_$][a-zA-Z0-9_$]*)/
const OPERATOR_REGEX = /^([+\-*/%=<>!&|^~?:;,.()[\]{}])/
const WHITESPACE_REGEX = /^(\s+)/
export function highlightLine(line: string, language: Language): HighlightedToken[] {
if (language === "unknown" || language === "json" || language === "yaml") {
return [{ text: line, color: "white" }]
}
const tokens: HighlightedToken[] = []
let remaining = line
while (remaining.length > 0) {
const commentMatch = COMMENT_REGEX.exec(remaining)
if (commentMatch) {
tokens.push({ text: commentMatch[0], color: "gray" })
remaining = remaining.slice(commentMatch[0].length)
continue
}
const stringMatch = STRING_REGEX.exec(remaining)
if (stringMatch) {
tokens.push({ text: stringMatch[0], color: "green" })
remaining = remaining.slice(stringMatch[0].length)
continue
}
const numberMatch = NUMBER_REGEX.exec(remaining)
if (numberMatch) {
tokens.push({ text: numberMatch[0], color: "cyan" })
remaining = remaining.slice(numberMatch[0].length)
continue
}
const wordMatch = WORD_REGEX.exec(remaining)
if (wordMatch) {
const word = wordMatch[0]
const color = KEYWORDS.has(word) ? "magenta" : "white"
tokens.push({ text: word, color })
remaining = remaining.slice(word.length)
continue
}
const operatorMatch = OPERATOR_REGEX.exec(remaining)
if (operatorMatch) {
tokens.push({ text: operatorMatch[0], color: "yellow" })
remaining = remaining.slice(operatorMatch[0].length)
continue
}
const whitespaceMatch = WHITESPACE_REGEX.exec(remaining)
if (whitespaceMatch) {
tokens.push({ text: whitespaceMatch[0], color: "white" })
remaining = remaining.slice(whitespaceMatch[0].length)
continue
}
tokens.push({ text: remaining[0] ?? "", color: "white" })
remaining = remaining.slice(1)
}
return tokens
}