mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 13:32:50 -05:00
301 lines
7.6 KiB
Go
301 lines
7.6 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/alecthomas/chroma/v2"
|
|
"github.com/alecthomas/chroma/v2/formatters/html"
|
|
"github.com/alecthomas/chroma/v2/lexers"
|
|
"github.com/alecthomas/chroma/v2/styles"
|
|
"github.com/spf13/cobra"
|
|
"github.com/yuin/goldmark"
|
|
highlighting "github.com/yuin/goldmark-highlighting/v2"
|
|
"github.com/yuin/goldmark/extension"
|
|
"github.com/yuin/goldmark/parser"
|
|
ghtml "github.com/yuin/goldmark/renderer/html"
|
|
)
|
|
|
|
var (
|
|
chromaLanguage string
|
|
chromaStyle string
|
|
chromaInline bool
|
|
chromaMarkdown bool
|
|
chromaLineNumbers bool
|
|
|
|
// Caching layer for performance
|
|
lexerCache = make(map[string]chroma.Lexer)
|
|
styleCache = make(map[string]*chroma.Style)
|
|
formatterCache = make(map[string]*html.Formatter)
|
|
cacheMutex sync.RWMutex
|
|
maxFileSize = int64(5 * 1024 * 1024) // 5MB default
|
|
)
|
|
|
|
var chromaCmd = &cobra.Command{
|
|
Use: "chroma [file]",
|
|
Short: "Syntax highlight source code",
|
|
Long: `Generate syntax-highlighted HTML from source code.
|
|
|
|
Reads from file or stdin, outputs HTML with syntax highlighting.
|
|
Language is auto-detected from filename or can be specified with --language.
|
|
|
|
Examples:
|
|
dms chroma main.go
|
|
dms chroma --language python script.py
|
|
echo "def foo(): pass" | dms chroma -l python
|
|
cat code.rs | dms chroma -l rust --style dracula
|
|
dms chroma --markdown README.md
|
|
dms chroma --markdown --style github-dark notes.md
|
|
dms chroma list-languages
|
|
dms chroma list-styles`,
|
|
Args: cobra.MaximumNArgs(1),
|
|
Run: runChroma,
|
|
}
|
|
|
|
var chromaListLanguagesCmd = &cobra.Command{
|
|
Use: "list-languages",
|
|
Short: "List all supported languages",
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
for _, name := range lexers.Names(true) {
|
|
fmt.Println(name)
|
|
}
|
|
},
|
|
}
|
|
|
|
var chromaListStylesCmd = &cobra.Command{
|
|
Use: "list-styles",
|
|
Short: "List all available color styles",
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
for _, name := range styles.Names() {
|
|
fmt.Println(name)
|
|
}
|
|
},
|
|
}
|
|
|
|
func init() {
|
|
chromaCmd.Flags().StringVarP(&chromaLanguage, "language", "l", "", "Language for highlighting (auto-detect if not specified)")
|
|
chromaCmd.Flags().StringVarP(&chromaStyle, "style", "s", "monokai", "Color style (monokai, dracula, github, etc.)")
|
|
chromaCmd.Flags().BoolVar(&chromaInline, "inline", false, "Output inline styles instead of CSS classes")
|
|
chromaCmd.Flags().BoolVar(&chromaLineNumbers, "line-numbers", false, "Show line numbers in output")
|
|
chromaCmd.Flags().BoolVarP(&chromaMarkdown, "markdown", "m", false, "Render markdown with syntax-highlighted code blocks")
|
|
chromaCmd.Flags().Int64Var(&maxFileSize, "max-size", 5*1024*1024, "Maximum file size to process without warning (bytes)")
|
|
|
|
chromaCmd.AddCommand(chromaListLanguagesCmd)
|
|
chromaCmd.AddCommand(chromaListStylesCmd)
|
|
}
|
|
|
|
func getCachedLexer(key string, fallbackFunc func() chroma.Lexer) chroma.Lexer {
|
|
cacheMutex.RLock()
|
|
if lexer, ok := lexerCache[key]; ok {
|
|
cacheMutex.RUnlock()
|
|
return lexer
|
|
}
|
|
cacheMutex.RUnlock()
|
|
|
|
lexer := fallbackFunc()
|
|
if lexer != nil {
|
|
cacheMutex.Lock()
|
|
lexerCache[key] = lexer
|
|
cacheMutex.Unlock()
|
|
}
|
|
return lexer
|
|
}
|
|
|
|
func getCachedStyle(name string) *chroma.Style {
|
|
cacheMutex.RLock()
|
|
if style, ok := styleCache[name]; ok {
|
|
cacheMutex.RUnlock()
|
|
return style
|
|
}
|
|
cacheMutex.RUnlock()
|
|
|
|
style := styles.Get(name)
|
|
if style == nil {
|
|
fmt.Fprintf(os.Stderr, "Warning: Style '%s' not found, using fallback\n", name)
|
|
style = styles.Fallback
|
|
}
|
|
|
|
cacheMutex.Lock()
|
|
styleCache[name] = style
|
|
cacheMutex.Unlock()
|
|
return style
|
|
}
|
|
|
|
func getCachedFormatter(inline bool, lineNumbers bool) *html.Formatter {
|
|
key := fmt.Sprintf("inline=%t,lineNumbers=%t", inline, lineNumbers)
|
|
|
|
cacheMutex.RLock()
|
|
if formatter, ok := formatterCache[key]; ok {
|
|
cacheMutex.RUnlock()
|
|
return formatter
|
|
}
|
|
cacheMutex.RUnlock()
|
|
|
|
var opts []html.Option
|
|
if inline {
|
|
opts = append(opts, html.WithClasses(false))
|
|
} else {
|
|
opts = append(opts, html.WithClasses(true))
|
|
}
|
|
opts = append(opts, html.TabWidth(4))
|
|
|
|
if lineNumbers {
|
|
opts = append(opts, html.WithLineNumbers(true))
|
|
opts = append(opts, html.LineNumbersInTable(false))
|
|
opts = append(opts, html.WithLinkableLineNumbers(false, ""))
|
|
}
|
|
|
|
formatter := html.New(opts...)
|
|
|
|
cacheMutex.Lock()
|
|
formatterCache[key] = formatter
|
|
cacheMutex.Unlock()
|
|
return formatter
|
|
}
|
|
|
|
func runChroma(cmd *cobra.Command, args []string) {
|
|
var source string
|
|
var filename string
|
|
|
|
// Read from file or stdin
|
|
if len(args) > 0 {
|
|
filename = args[0]
|
|
|
|
// Check file size before reading
|
|
fileInfo, err := os.Stat(filename)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error reading file info: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if fileInfo.Size() > maxFileSize {
|
|
fmt.Fprintf(os.Stderr, "Warning: File size (%d bytes) exceeds recommended limit (%d bytes)\n",
|
|
fileInfo.Size(), maxFileSize)
|
|
fmt.Fprintf(os.Stderr, "Processing may be slow. Consider using smaller files.\n")
|
|
}
|
|
|
|
content, err := os.ReadFile(filename)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error reading file: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
source = string(content)
|
|
} else {
|
|
stat, _ := os.Stdin.Stat()
|
|
if (stat.Mode() & os.ModeCharDevice) != 0 {
|
|
_ = cmd.Help()
|
|
os.Exit(0)
|
|
}
|
|
|
|
content, err := io.ReadAll(os.Stdin)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error reading stdin: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
source = string(content)
|
|
}
|
|
|
|
// Handle empty input
|
|
if strings.TrimSpace(source) == "" {
|
|
return
|
|
}
|
|
|
|
// Handle Markdown rendering
|
|
if chromaMarkdown {
|
|
md := goldmark.New(
|
|
goldmark.WithExtensions(
|
|
extension.GFM,
|
|
highlighting.NewHighlighting(
|
|
highlighting.WithStyle(chromaStyle),
|
|
highlighting.WithFormatOptions(
|
|
html.WithClasses(!chromaInline),
|
|
),
|
|
),
|
|
),
|
|
goldmark.WithParserOptions(
|
|
parser.WithAutoHeadingID(),
|
|
),
|
|
goldmark.WithRendererOptions(
|
|
ghtml.WithHardWraps(),
|
|
ghtml.WithXHTML(),
|
|
),
|
|
)
|
|
|
|
var buf bytes.Buffer
|
|
if err := md.Convert([]byte(source), &buf); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Markdown rendering error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
fmt.Print(buf.String())
|
|
return
|
|
}
|
|
|
|
// Detect or use specified lexer
|
|
var lexer chroma.Lexer
|
|
if chromaLanguage != "" {
|
|
lexer = getCachedLexer(chromaLanguage, func() chroma.Lexer {
|
|
l := lexers.Get(chromaLanguage)
|
|
if l == nil {
|
|
fmt.Fprintf(os.Stderr, "Unknown language: %s\n", chromaLanguage)
|
|
os.Exit(1)
|
|
}
|
|
return l
|
|
})
|
|
} else if filename != "" {
|
|
lexer = getCachedLexer("file:"+filename, func() chroma.Lexer {
|
|
return lexers.Match(filename)
|
|
})
|
|
}
|
|
|
|
// Try content analysis if no lexer found (limit to first 1KB for performance)
|
|
if lexer == nil {
|
|
analyzeContent := source
|
|
if len(source) > 1024 {
|
|
analyzeContent = source[:1024]
|
|
}
|
|
lexer = lexers.Analyse(analyzeContent)
|
|
}
|
|
|
|
// Fallback to plaintext
|
|
if lexer == nil {
|
|
lexer = lexers.Fallback
|
|
}
|
|
|
|
lexer = chroma.Coalesce(lexer)
|
|
|
|
// Get cached style
|
|
style := getCachedStyle(chromaStyle)
|
|
|
|
// Get cached formatter
|
|
formatter := getCachedFormatter(chromaInline, chromaLineNumbers)
|
|
|
|
// Tokenize
|
|
iterator, err := lexer.Tokenise(nil, source)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Tokenization error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Format and output
|
|
if chromaLineNumbers {
|
|
var buf bytes.Buffer
|
|
if err := formatter.Format(&buf, style, iterator); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Formatting error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
// Add spacing between line numbers
|
|
output := buf.String()
|
|
output = strings.ReplaceAll(output, "</span><span>", "</span>\u00A0\u00A0<span>")
|
|
fmt.Print(output)
|
|
} else {
|
|
if err := formatter.Format(os.Stdout, style, iterator); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Formatting error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
}
|