174 lines
8.2 KiB
Go
174 lines
8.2 KiB
Go
package main
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/charmbracelet/lipgloss"
|
|
)
|
|
|
|
// === Цвета подсветки синтаксиса ===
|
|
var (
|
|
colorKeyword = lipgloss.NewStyle().Foreground(lipgloss.Color("207")).Bold(true)
|
|
colorString = lipgloss.NewStyle().Foreground(lipgloss.Color("106"))
|
|
colorComment = lipgloss.NewStyle().Foreground(lipgloss.Color("59")).Italic(true)
|
|
colorNumber = lipgloss.NewStyle().Foreground(lipgloss.Color("215"))
|
|
colorFunc = lipgloss.NewStyle().Foreground(lipgloss.Color("81")).Bold(true)
|
|
)
|
|
|
|
// LanguageConfig описывает правила подсветки для одного языка
|
|
type LanguageConfig struct {
|
|
Keywords []string
|
|
LineCommentPrefix string // "//" или "#" или "--"
|
|
StringDelimiters []string // `"`, `'`, `` ` ``
|
|
HasNumbers bool
|
|
CaseInsensitive bool // для SQL
|
|
VarPrefix string // для Bash: "$"
|
|
}
|
|
|
|
// languageConfigs — конфиг для каждого языка
|
|
var languageConfigs = map[string]LanguageConfig{
|
|
"go": {
|
|
Keywords: []string{"func", "package", "import", "var", "const", "type", "struct", "interface", "if", "else", "for", "switch", "case", "default", "return", "break", "continue", "defer", "go", "chan", "select", "map", "range", "nil", "true", "false"},
|
|
LineCommentPrefix: "//",
|
|
StringDelimiters: []string{`"`, "`"},
|
|
HasNumbers: true,
|
|
},
|
|
"py": {
|
|
Keywords: []string{"def", "class", "if", "else", "elif", "for", "while", "import", "from", "return", "pass", "break", "continue", "lambda", "try", "except", "finally", "with", "as", "True", "False", "None", "and", "or", "not", "in", "is", "raise", "yield", "global", "nonlocal", "del", "assert"},
|
|
LineCommentPrefix: "#",
|
|
StringDelimiters: []string{`"`, `'`},
|
|
HasNumbers: true,
|
|
},
|
|
"js": {
|
|
Keywords: []string{"function", "const", "let", "var", "if", "else", "for", "while", "switch", "case", "default", "return", "break", "continue", "async", "await", "import", "export", "class", "extends", "new", "this", "super", "true", "false", "null", "undefined", "typeof", "instanceof", "void", "delete", "throw", "try", "catch", "finally"},
|
|
LineCommentPrefix: "//",
|
|
StringDelimiters: []string{`"`, `'`, "`"},
|
|
HasNumbers: true,
|
|
},
|
|
"ts": {
|
|
Keywords: []string{"function", "const", "let", "var", "if", "else", "for", "while", "switch", "case", "default", "return", "break", "continue", "async", "await", "import", "export", "class", "extends", "new", "this", "super", "true", "false", "null", "undefined", "type", "interface", "enum", "implements", "namespace", "abstract", "readonly", "public", "private", "protected"},
|
|
LineCommentPrefix: "//",
|
|
StringDelimiters: []string{`"`, `'`, "`"},
|
|
HasNumbers: true,
|
|
},
|
|
"rs": {
|
|
Keywords: []string{"fn", "let", "mut", "const", "static", "if", "else", "match", "for", "while", "loop", "break", "continue", "return", "pub", "mod", "use", "struct", "enum", "trait", "impl", "unsafe", "async", "await", "move", "ref", "self", "Self", "super", "crate", "true", "false"},
|
|
LineCommentPrefix: "//",
|
|
StringDelimiters: []string{`"`},
|
|
HasNumbers: true,
|
|
},
|
|
"c": {
|
|
Keywords: []string{"int", "char", "void", "float", "double", "long", "short", "unsigned", "signed", "if", "else", "for", "while", "do", "switch", "case", "default", "return", "break", "continue", "struct", "union", "enum", "typedef", "static", "const", "extern", "volatile", "sizeof", "NULL"},
|
|
LineCommentPrefix: "//",
|
|
StringDelimiters: []string{`"`},
|
|
HasNumbers: true,
|
|
},
|
|
"cpp": {
|
|
Keywords: []string{"int", "char", "void", "float", "double", "long", "short", "unsigned", "signed", "if", "else", "for", "while", "do", "switch", "case", "default", "return", "break", "continue", "struct", "union", "enum", "typedef", "static", "const", "extern", "volatile", "class", "public", "private", "protected", "virtual", "override", "new", "delete", "nullptr", "true", "false", "auto", "template", "typename", "namespace", "using", "this"},
|
|
LineCommentPrefix: "//",
|
|
StringDelimiters: []string{`"`},
|
|
HasNumbers: true,
|
|
},
|
|
"java": {
|
|
Keywords: []string{"class", "interface", "extends", "implements", "public", "private", "protected", "static", "final", "void", "int", "boolean", "char", "double", "float", "long", "short", "byte", "if", "else", "for", "while", "do", "switch", "case", "default", "return", "break", "continue", "new", "this", "super", "null", "true", "false", "try", "catch", "finally", "throw", "throws", "import", "package", "abstract", "synchronized"},
|
|
LineCommentPrefix: "//",
|
|
StringDelimiters: []string{`"`},
|
|
HasNumbers: true,
|
|
},
|
|
"sql": {
|
|
Keywords: []string{"SELECT", "FROM", "WHERE", "INSERT", "INTO", "VALUES", "UPDATE", "SET", "DELETE", "CREATE", "TABLE", "DROP", "ALTER", "ADD", "COLUMN", "INDEX", "JOIN", "LEFT", "RIGHT", "INNER", "OUTER", "ON", "AND", "OR", "NOT", "IN", "LIKE", "BETWEEN", "ORDER", "BY", "GROUP", "HAVING", "LIMIT", "OFFSET", "AS", "DISTINCT", "COUNT", "SUM", "AVG", "MAX", "MIN", "NULL", "IS", "PRIMARY", "KEY", "FOREIGN", "REFERENCES", "CONSTRAINT", "UNIQUE", "DEFAULT"},
|
|
LineCommentPrefix: "--",
|
|
StringDelimiters: []string{`'`},
|
|
HasNumbers: true,
|
|
CaseInsensitive: true,
|
|
},
|
|
"sh": {
|
|
Keywords: []string{"if", "then", "else", "elif", "fi", "for", "do", "done", "while", "case", "esac", "function", "return", "export", "local", "readonly", "unset", "shift", "echo", "exit", "break", "continue", "true", "false"},
|
|
LineCommentPrefix: "#",
|
|
StringDelimiters: []string{`"`, `'`},
|
|
HasNumbers: false,
|
|
VarPrefix: "$",
|
|
},
|
|
}
|
|
|
|
// HighlightLine подсвечивает одну строку кода для заданного типа файла
|
|
func HighlightLine(line, fileType string) string {
|
|
cfg, ok := languageConfigs[fileType]
|
|
if !ok {
|
|
return line
|
|
}
|
|
return highlightWithConfig(line, cfg)
|
|
}
|
|
|
|
// highlightWithConfig применяет правила подсветки из LanguageConfig
|
|
func highlightWithConfig(line string, cfg LanguageConfig) string {
|
|
result := line
|
|
|
|
// 1. Комментарии — применяем первыми, остальное внутри комментария не трогаем
|
|
if cfg.LineCommentPrefix != "" {
|
|
if idx := strings.Index(result, cfg.LineCommentPrefix); idx != -1 {
|
|
before := result[:idx]
|
|
comment := result[idx:]
|
|
result = highlightTokens(before, cfg) + colorComment.Render(comment)
|
|
return result
|
|
}
|
|
}
|
|
|
|
result = highlightTokens(result, cfg)
|
|
return result
|
|
}
|
|
|
|
// highlightTokens применяет подсветку строк, ключевых слов, чисел, переменных
|
|
func highlightTokens(text string, cfg LanguageConfig) string {
|
|
result := text
|
|
|
|
// Строки — выделяем до ключевых слов, чтобы не подсвечивать kw внутри строк
|
|
for _, delim := range cfg.StringDelimiters {
|
|
if delim == "`" {
|
|
result = regexp.MustCompile("`[^`]*`").ReplaceAllStringFunc(result, func(s string) string {
|
|
return colorString.Render(s)
|
|
})
|
|
} else if delim == `"` {
|
|
result = regexp.MustCompile(`"[^"]*"`).ReplaceAllStringFunc(result, func(s string) string {
|
|
return colorString.Render(s)
|
|
})
|
|
} else if delim == `'` {
|
|
result = regexp.MustCompile(`'[^']*'`).ReplaceAllStringFunc(result, func(s string) string {
|
|
return colorString.Render(s)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Ключевые слова
|
|
for _, kw := range cfg.Keywords {
|
|
var pattern *regexp.Regexp
|
|
if cfg.CaseInsensitive {
|
|
pattern = regexp.MustCompile(`(?i)\b` + regexp.QuoteMeta(kw) + `\b`)
|
|
} else {
|
|
pattern = regexp.MustCompile(`\b` + regexp.QuoteMeta(kw) + `\b`)
|
|
}
|
|
rendered := colorKeyword.Render(kw)
|
|
if cfg.CaseInsensitive {
|
|
rendered = colorKeyword.Render(strings.ToUpper(kw))
|
|
}
|
|
result = pattern.ReplaceAllString(result, rendered)
|
|
}
|
|
|
|
// Числа
|
|
if cfg.HasNumbers {
|
|
result = regexp.MustCompile(`\b\d+(\.\d+)?\b`).ReplaceAllStringFunc(result, func(s string) string {
|
|
return colorNumber.Render(s)
|
|
})
|
|
}
|
|
|
|
// Переменные (Bash: $VAR)
|
|
if cfg.VarPrefix != "" {
|
|
escaped := regexp.QuoteMeta(cfg.VarPrefix)
|
|
result = regexp.MustCompile(escaped+`\w+`).ReplaceAllStringFunc(result, func(s string) string {
|
|
return colorFunc.Render(s)
|
|
})
|
|
}
|
|
|
|
return result
|
|
}
|