mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-27 23:06:54 +05:00
- 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
168 lines
3.9 KiB
TypeScript
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
|
|
}
|