Chroma is a Swift package for syntax highlighting code in terminal output. It takes a code string plus a language identifier, and returns an ANSI-colored string ready for printing in your TUI or CLI application.
ANSI styling is powered by Rainbow.
-
30+ Built-in Languages — Includes almost all popular languages. See all languages.
-
High Performance — Minimal memory footprint with fast tokenization using optimized regex-based scanning and keyword fast-path lookups.
-
Flexible Highlighting — Built-in support for line highlighting with background colors, line numbers, and output indentation.
-
Diff Highlighting — Automatic detection and rendering of unified patches with configurable styles (foreground/background) and presentation modes (compact/verbose).
-
Customizable — Register custom languages via
LanguageRegistryand define custom themes with full control over token styles and colors.
import Chroma
let code = """
struct User {
let id: Int
let name: String
}
"""
let output = try Chroma.highlight(code, language: .swift)
print(output)Clone the repository and run the built-in demo to see Chroma in action:
git clone https://github.com/onevcat/Chroma.git
cd Chroma
swift run ChromaDemo --lang swift
# Or use `--light` to apply the default light theme
# swift run ChromaDemo --lang swift --lightTo list all supported languages:
swift run ChromaDemo --list-languagesAdd Chroma as a dependency in your Package.swift:
dependencies: [
.package(url: "https://github.com/onevcat/Chroma.git", from: "0.1.0")
]Then add it to your target:
targets: [
.target(
name: "MyApp",
dependencies: ["Chroma"]
)
]The simplest way to highlight code:
import Chroma
let code = "let x = 42"
let output = try Chroma.highlight(code, language: .swift)
print(output)Infer a language from file names, paths, or URLs:
let language = LanguageID.fromFileName("MyFile.swift")
let output = try Chroma.highlight(code, language: language)language is optional; passing nil skips syntax highlighting and returns the original text.
Fallback to plain text when the language is unavailable:
let options = HighlightOptions(missingLanguageHandling: .fallbackToPlainText)
let output = try Chroma.highlight(code, language: "unknown", options: options)Chroma includes two built-in themes:
// Use the dark theme (default)
let output1 = try Chroma.highlight(code, language: .swift)
// Use the light theme
let output2 = try Chroma.highlight(
code,
language: .swift,
options: .init(theme: .light)
)Highlight specific lines with a background color:
let output = try Chroma.highlight(
code,
language: .swift,
options: .init(highlightLines: [2...5, 10...12])
)Add line numbers to the output:
let output = try Chroma.highlight(
code,
language: .swift,
options: .init(lineNumbers: .init(start: 1))
)Chroma defaults to auto-detecting ANSI output based on TTY, TERM=dumb, and common environment variables (NO_COLOR, CHROMA_NO_COLOR, FORCE_COLOR).
// Force ANSI output
let output = try Chroma.highlight(
code,
language: .swift,
options: .init(colorMode: .always)
)
// Auto-detect for stderr output
let output = try Chroma.highlight(
code,
language: .swift,
options: .init(colorMode: .auto(output: .stderr))
)Indent the entire output by a specified number of spaces:
let output = try Chroma.highlight(
code,
language: .swift,
options: .init(indent: 4)
)Chroma can highlight unified patches (like git diff output) automatically. It detects patch format and renders additions/deletions with appropriate styling.
let patch = """
diff --git a/File.swift b/File.swift
index 1111111..2222222 100644
--- a/File.swift
+++ b/File.swift
@@ -1,3 +1,3 @@
-let a = 1
+let a = 2
"""
// Automatically detected and highlighted
let output = try Chroma.highlight(patch, language: .swift)Configure how diffs are rendered:
// Background highlighting with syntax-colored code (default)
let output1 = try Chroma.highlight(
patch,
language: .swift,
options: .init(diff: .patch(style: .background()))
)
// Foreground highlighting (red/green text only)
let output2 = try Chroma.highlight(
patch,
language: .swift,
options: .init(diff: .patch(style: .foreground()))
)
// Foreground diff with syntax-highlighted context
let output3 = try Chroma.highlight(
patch,
language: .swift,
options: .init(diff: .patch(style: .foreground(contextCode: .syntax)))
)let patch = """
diff --git a/UserService.swift b/UserService.swift
index 1111111..2222222 100644
--- a/UserService.swift
+++ b/UserService.swift
@@ -5,7 +5,7 @@
let id: Int
let name: String
let email: String
- let isActive: Bool
+ var isActive: Bool
}
struct UserService {
@@ -14,8 +14,8 @@ struct UserService {
return users.filter { $0.isActive }
}
- func findUser(id: Int) -> User? {
+ func find(id: Int) -> User? {
users.first { $0.id == id }
}
}
"""
// Compact mode (default) - uses "⋮" separators between hunks
let output1 = try Chroma.highlight(
patch,
language: .swift,
options: .init(diff: .patch(presentation: .compact))
)
// Verbose mode - shows full patch headers
let output2 = try Chroma.highlight(
patch,
language: .swift,
options: .init(diff: .patch(presentation: .verbose))
)For more control, create a Highlighter instance:
import Chroma
let highlighter = Highlighter(theme: .dark)
let output1 = try highlighter.highlight(code1, language: .swift)
let output2 = try highlighter.highlight(code2, language: .python)let output = try Chroma.highlight(
code,
language: .swift,
options: .init(
theme: .light,
highlightLines: [5...10],
lineNumbers: .init(start: 1),
indent: 2
)
)Register custom language definitions using regex-based token rules:
import Chroma
var myLang = LanguageDefinition(
id: "my-lang",
displayName: "MyLang",
rules: [
try TokenRule(kind: .comment, pattern: "#[^\\n\\r]*"),
try TokenRule.words(["let", "fn", "return", "if", "else", "while"], kind: .keyword),
try TokenRule(kind: .string, pattern: "\"(?:\\\\.|[^\"\\\\])*\""),
try TokenRule(kind: .number, pattern: "\\b\\d+\\b"),
]
)
let registry = LanguageRegistry.builtIn()
registry.register(myLang)
let highlighter = Highlighter(registry: registry)
let code = """
# Calculate factorial
fn factorial(n) {
if n <= 1 {
return 1
}
return n * factorial(n - 1)
}
let result = factorial(5)
"""
let output = try highlighter.highlight(code, language: "my-lang")Define your own theme with full control over token styles:
import Chroma
import Rainbow
let customTheme = Theme(
name: "custom",
tokenStyles: [
.plain: .init(foreground: .named(.white)),
.keyword: .init(foreground: .named(.yellow), styles: [.bold]),
.string: .init(foreground: .named(.red)),
.comment: .init(foreground: .named(.lightGreen), styles: [.dim]),
],
lineHighlightBackground: .named(.lightBlack),
diffAddedBackground: .named(.green),
diffRemovedBackground: .named(.red),
diffAddedForeground: .named(.lightGreen),
diffRemovedForeground: .named(.lightRed),
lineNumberForeground: .named(.white)
)
let highlighter = Highlighter(theme: customTheme)
let output = try highlighter.highlight(code, language: .swift)You can also use Rainbow's extended color modes for more color options:
.tokenStyles: [
.keyword: .init(foreground: .bit8(226)), // 256-color mode
.string: .init(foreground: .bit24((255, 107, 107))), // Truecolor RGB tuple
]To get tokens without rendering:
let tokens = try Chroma.tokenize(code, language: .swift)For large files, process tokens as they are generated:
try Chroma.tokenize(code, language: .swift) { token in
print(token.kind, token.range)
}Render pre-tokenized code with custom options:
let tokens = try Chroma.tokenize(code, language: .swift)
let output = Chroma.render(
code,
tokens: tokens,
options: .init(theme: .light, lineNumbers: .init(start: 1))
)To run performance benchmarks:
# Install jemalloc for memory tracking
brew install jemalloc
# Run benchmarks
swift package benchmark --target ChromaBenchmarksOn Ubuntu:
sudo apt-get update
sudo apt-get install -y libjemalloc-dev
swift package benchmark --target ChromaBenchmarksIf jemalloc is not available, memory stats will be skipped:
BENCHMARK_DISABLE_JEMALLOC=1 swift package benchmark --target ChromaBenchmarksMIT License (c) 2025 Wei Wang, [email protected]
Rainbow— String coloring for Swift that powers Chroma's ANSI output
