How Superfile Renders File Previews with Syntax Highlighting Using Chroma and Bat
Superfile determines file type at runtime and routes content through either a pure‑Go Chroma highlighter or an external bat process, falling back to plain text when no lexer matches.
Superfile’s preview pane supports syntax highlighting for source code through a pluggable architecture that balances portability with performance. The implementation lives primarily in the src/internal/ui/preview package and uses either the Go-native Chroma library or the external bat CLI tool, depending on user configuration.
The Rendering Pipeline
The entry point for all preview generation is RenderWithPath inside src/internal/ui/preview/render.go. This function acts as a router that first inspects the file’s mode, extension, and MIME type before deciding whether to render a directory listing, an image, or text with optional syntax highlighting.
The pipeline follows four distinct phases: detection, content acquisition, highlighting engine selection, and final rendering. This separation allows the UI code to remain agnostic of the underlying highlighter while supporting both offline-first (Chroma) and external tool (Bat) workflows.
File Type Detection and Routing
Before any content is read, Superfile categorizes the path:
- Unsupported formats are filtered early via
common.UnsupportedPreviewFormats - Directories are handed to
renderDirectoryPreview, which returns a file listing with icons - Images are detected by
isImageFile(defined inrender_utils.go) and processed by animagePreviewerthat converts them to ANSI or Kitty graphics protocol - Regular text files proceed to
renderTextPreview
For text files, the system first attempts lexer detection using Chroma’s lexers.Match(filepath.Base(itemPath)). If Chroma returns no lexer, Superfile runs common.IsTextFile to perform a binary‑text heuristic before deciding whether to render the content as plain text.
Syntax Highlighting Engines
Once a file is confirmed as text with a valid lexer, Superfile checks common.Config.CodePreviewer to decide which highlighting engine to invoke.
The Built‑in Chroma Engine (ansichroma)
When CodePreviewer is set to "ansichroma" (or when bat is unavailable), Superfile uses its own wrapper around the Chroma library.
The file content is first loaded via utils.ReadFileContent, then passed to:
fileContent, err = ansichroma.HightlightString(
fileContent, format.Config().Name,
common.Theme.CodeSyntaxHighlightTheme, background)
This call tokenizes the content using the detected lexer and renders it to ANSI escape codes using the theme defined in common.Theme.CodeSyntaxHighlightTheme. The ansichroma package (internal to Superfile) adapts Chroma’s token stream into terminal‑ready strings, respecting the configured background color when transparentBackground is disabled.
The External Bat Integration
If common.Config.CodePreviewer equals "bat" and the executable exists (detected via CheckBatCmd which resolves to bat or batcat), Superfile delegates highlighting to the external tool.
The implementation in src/internal/ui/preview/render_utils.go constructs the command via getBatSyntaxHighlightedContent:
batArgs := []string{itemPath, "--plain", "--force-colorization",
"--line-range", fmt.Sprintf(":%d", previewLine)}
cmd := exec.CommandContext(ctx, batCmd, batArgs...)
The --plain flag disables Bat’s default pagination and headers, while --force-colorization ensures ANSI codes are emitted even when piped. The --line-range argument limits output to the available preview height.
After execution, the output optionally passes through setBatBackground to inject solid‑color background ANSI sequences when the terminal theme requires it. This matches the behavior of the Chroma path for consistent UI appearance.
Rendering the Final Output
Regardless of which engine produces the highlighted string, completion follows the same path. The content is added to the renderer instance via r.AddLines(fileContent), which buffers the lines for display. Finally, r.Render() emits the complete ANSI string (or Kitty image data) that the Bubble Tea framework draws into the preview pane.
RenderWithPath also manages dimension calculations for optional borders and clears previous Kitty images using m.imagePreviewer.GetKittyClearRaw() to prevent ghosting when navigating between files.
Code Examples
Render a Source File with Default Chroma
// Inside the UI model:
previewStr, _ := model.RenderWithPath(
"/path/to/main.go", // itemPath
80, // previewWidth (columns)
20, // previewHeight (rows)
120, // fullModelWidth
)
fmt.Println(previewStr) // ANSI‑colored preview shown in the panel
Force Bat for Syntax Highlighting
common.Config.CodePreviewer = "bat"
common.Config.BatCmd = preview.CheckBatCmd() // resolves to "bat" or "batcat"
previewStr, _ := model.RenderWithPath(
"/path/to/main.go", 80, 20, 120,
)
fmt.Println(previewStr) // output produced by the external `bat` command
Preview a Directory
previewStr, _ := model.RenderWithPath(
"/path/to/dir", 30, 10, 80,
)
fmt.Println(previewStr) // lists files with icons
Preview an Image (Kitty Protocol)
previewStr, raw := model.RenderWithPath(
"/path/to/image.png", 40, 15, 100,
)
// `raw` contains the Kitty image data that must be sent via tea.Raw()
Summary
- Routing logic in
src/internal/ui/preview/render.go(RenderWithPath) separates file type detection from rendering logic. - Chroma integration uses
lexers.Matchfor detection andansichroma.HightlightStringfor pure‑Go highlighting without external dependencies. - Bat integration shells out to
batorbatcatwith--plainand--force-colorizationflags for fast, high‑fidelity highlighting. - Configuration is controlled by
common.Config.CodePreviewerand theme settings incommon.Theme.CodeSyntaxHighlightTheme. - Extensibility is built‑in: new highlighters can be added by implementing the content acquisition → highlight →
r.AddLinespattern.
Frequently Asked Questions
What happens if a file has no recognized syntax?
If Chroma’s lexers.Match returns nil and common.IsTextFile confirms the content is text, Superfile renders the file as plain text without color codes. Binary files are rejected from the preview pane entirely.
Can I use bat on systems where it is installed as batcat?
Yes. Superfile’s CheckBatCmd function (in render_utils.go) probes the system path and automatically resolves the correct executable name, storing it in common.Config.BatCmd for subsequent calls.
How does Superfile limit the amount of file content read for large files?
Both the Chroma and Bat paths respect the previewLine limit. The Bat integration passes --line-range to restrict output, while the Chroma path reads content via utils.ReadFileContent which internally handles buffering and truncation to avoid loading multi‑gigabyte files into memory.
Where is the color theme for syntax highlighting defined?
The theme name is stored in common.Theme.CodeSyntaxHighlightTheme (defined in the style configuration) and is passed directly to ansichroma.HightlightString. When using Bat, the theme is controlled by Bat’s own configuration, though Superfile still manages the background color via setBatBackground to ensure consistency with the terminal UI.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →