#[cfg(any(
feature = "syntax-rust",
feature = "syntax-python",
feature = "syntax-javascript",
feature = "syntax-typescript",
feature = "syntax-go",
feature = "syntax-bash",
feature = "syntax-json",
feature = "syntax-toml",
feature = "syntax-c",
feature = "syntax-cpp",
feature = "syntax-java",
feature = "syntax-ruby",
feature = "syntax-css",
feature = "syntax-html",
feature = "syntax-yaml",
))]
use crate::style::Color;
use crate::style::{Style, Theme};
#[cfg(any(
feature = "syntax-rust",
feature = "syntax-python",
feature = "syntax-javascript",
feature = "syntax-typescript",
feature = "syntax-go",
feature = "syntax-bash",
feature = "syntax-json",
feature = "syntax-toml",
feature = "syntax-c",
feature = "syntax-cpp",
feature = "syntax-java",
feature = "syntax-ruby",
feature = "syntax-css",
feature = "syntax-html",
feature = "syntax-yaml",
))]
const HIGHLIGHT_NAMES: &[&str] = &[
"attribute",
"comment",
"constant",
"constant.builtin",
"constructor",
"embedded",
"function",
"function.builtin",
"function.macro",
"keyword",
"module",
"number",
"operator",
"property",
"property.builtin",
"punctuation",
"punctuation.bracket",
"punctuation.delimiter",
"punctuation.special",
"string",
"string.special",
"tag",
"type",
"type.builtin",
"variable",
"variable.builtin",
"variable.parameter",
];
#[cfg(any(
feature = "syntax-rust",
feature = "syntax-python",
feature = "syntax-javascript",
feature = "syntax-typescript",
feature = "syntax-go",
feature = "syntax-bash",
feature = "syntax-json",
feature = "syntax-toml",
feature = "syntax-c",
feature = "syntax-cpp",
feature = "syntax-java",
feature = "syntax-ruby",
feature = "syntax-css",
feature = "syntax-html",
feature = "syntax-yaml",
))]
use std::sync::OnceLock;
#[cfg(any(
feature = "syntax-rust",
feature = "syntax-python",
feature = "syntax-javascript",
feature = "syntax-typescript",
feature = "syntax-go",
feature = "syntax-bash",
feature = "syntax-json",
feature = "syntax-toml",
feature = "syntax-c",
feature = "syntax-cpp",
feature = "syntax-java",
feature = "syntax-ruby",
feature = "syntax-css",
feature = "syntax-html",
feature = "syntax-yaml",
))]
use tree_sitter_highlight::HighlightConfiguration;
#[cfg(any(
feature = "syntax-rust",
feature = "syntax-python",
feature = "syntax-javascript",
feature = "syntax-typescript",
feature = "syntax-go",
feature = "syntax-bash",
feature = "syntax-json",
feature = "syntax-toml",
feature = "syntax-c",
feature = "syntax-cpp",
feature = "syntax-java",
feature = "syntax-ruby",
feature = "syntax-css",
feature = "syntax-html",
feature = "syntax-yaml",
))]
fn get_config(lang: &str) -> Option<&'static HighlightConfiguration> {
match lang {
#[cfg(feature = "syntax-rust")]
"rust" | "rs" => {
static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
CFG.get_or_init(|| {
HighlightConfiguration::new(
tree_sitter_rust::LANGUAGE.into(),
"rust",
tree_sitter_rust::HIGHLIGHTS_QUERY,
tree_sitter_rust::INJECTIONS_QUERY,
"",
)
.ok()
.map(|mut c| {
c.configure(HIGHLIGHT_NAMES);
c
})
})
.as_ref()
}
#[cfg(feature = "syntax-python")]
"python" | "py" => {
static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
CFG.get_or_init(|| {
HighlightConfiguration::new(
tree_sitter_python::LANGUAGE.into(),
"python",
tree_sitter_python::HIGHLIGHTS_QUERY,
"",
"",
)
.ok()
.map(|mut c| {
c.configure(HIGHLIGHT_NAMES);
c
})
})
.as_ref()
}
#[cfg(feature = "syntax-javascript")]
"javascript" | "js" | "jsx" => {
static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
CFG.get_or_init(|| {
HighlightConfiguration::new(
tree_sitter_javascript::LANGUAGE.into(),
"javascript",
tree_sitter_javascript::HIGHLIGHT_QUERY,
tree_sitter_javascript::INJECTIONS_QUERY,
tree_sitter_javascript::LOCALS_QUERY,
)
.ok()
.map(|mut c| {
c.configure(HIGHLIGHT_NAMES);
c
})
})
.as_ref()
}
#[cfg(feature = "syntax-go")]
"go" | "golang" => {
static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
CFG.get_or_init(|| {
HighlightConfiguration::new(
tree_sitter_go::LANGUAGE.into(),
"go",
tree_sitter_go::HIGHLIGHTS_QUERY,
"",
"",
)
.ok()
.map(|mut c| {
c.configure(HIGHLIGHT_NAMES);
c
})
})
.as_ref()
}
#[cfg(feature = "syntax-bash")]
"bash" | "sh" | "shell" | "zsh" => {
static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
CFG.get_or_init(|| {
HighlightConfiguration::new(
tree_sitter_bash::LANGUAGE.into(),
"bash",
tree_sitter_bash::HIGHLIGHT_QUERY,
"",
"",
)
.ok()
.map(|mut c| {
c.configure(HIGHLIGHT_NAMES);
c
})
})
.as_ref()
}
#[cfg(feature = "syntax-json")]
"json" | "jsonc" => {
static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
CFG.get_or_init(|| {
HighlightConfiguration::new(
tree_sitter_json::LANGUAGE.into(),
"json",
tree_sitter_json::HIGHLIGHTS_QUERY,
"",
"",
)
.ok()
.map(|mut c| {
c.configure(HIGHLIGHT_NAMES);
c
})
})
.as_ref()
}
#[cfg(feature = "syntax-toml")]
"toml" => {
static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
CFG.get_or_init(|| {
HighlightConfiguration::new(
tree_sitter_toml_ng::LANGUAGE.into(),
"toml",
tree_sitter_toml_ng::HIGHLIGHTS_QUERY,
"",
"",
)
.ok()
.map(|mut c| {
c.configure(HIGHLIGHT_NAMES);
c
})
})
.as_ref()
}
#[cfg(feature = "syntax-c")]
"c" | "h" => {
static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
CFG.get_or_init(|| {
HighlightConfiguration::new(
tree_sitter_c::LANGUAGE.into(),
"c",
tree_sitter_c::HIGHLIGHT_QUERY,
"",
"",
)
.ok()
.map(|mut c| {
c.configure(HIGHLIGHT_NAMES);
c
})
})
.as_ref()
}
#[cfg(feature = "syntax-cpp")]
"cpp" | "c++" | "cxx" | "cc" | "hpp" => {
static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
CFG.get_or_init(|| {
#[cfg(feature = "syntax-c")]
let highlights = {
let mut combined = String::with_capacity(
tree_sitter_c::HIGHLIGHT_QUERY.len()
+ tree_sitter_cpp::HIGHLIGHT_QUERY.len()
+ 1,
);
combined.push_str(tree_sitter_c::HIGHLIGHT_QUERY);
combined.push('\n');
combined.push_str(tree_sitter_cpp::HIGHLIGHT_QUERY);
combined
};
#[cfg(not(feature = "syntax-c"))]
let highlights = tree_sitter_cpp::HIGHLIGHT_QUERY.to_string();
HighlightConfiguration::new(
tree_sitter_cpp::LANGUAGE.into(),
"cpp",
&highlights,
"",
"",
)
.ok()
.map(|mut c| {
c.configure(HIGHLIGHT_NAMES);
c
})
})
.as_ref()
}
#[cfg(feature = "syntax-typescript")]
"typescript" | "ts" => {
static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
CFG.get_or_init(|| {
HighlightConfiguration::new(
tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
"typescript",
tree_sitter_typescript::HIGHLIGHTS_QUERY,
tree_sitter_typescript::LOCALS_QUERY,
"",
)
.ok()
.map(|mut c| {
c.configure(HIGHLIGHT_NAMES);
c
})
})
.as_ref()
}
#[cfg(feature = "syntax-typescript")]
"tsx" => {
static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
CFG.get_or_init(|| {
HighlightConfiguration::new(
tree_sitter_typescript::LANGUAGE_TSX.into(),
"tsx",
tree_sitter_typescript::HIGHLIGHTS_QUERY,
tree_sitter_typescript::LOCALS_QUERY,
"",
)
.ok()
.map(|mut c| {
c.configure(HIGHLIGHT_NAMES);
c
})
})
.as_ref()
}
#[cfg(feature = "syntax-java")]
"java" => {
static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
CFG.get_or_init(|| {
HighlightConfiguration::new(
tree_sitter_java::LANGUAGE.into(),
"java",
tree_sitter_java::HIGHLIGHTS_QUERY,
"",
"",
)
.ok()
.map(|mut c| {
c.configure(HIGHLIGHT_NAMES);
c
})
})
.as_ref()
}
#[cfg(feature = "syntax-ruby")]
"ruby" | "rb" => {
static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
CFG.get_or_init(|| {
HighlightConfiguration::new(
tree_sitter_ruby::LANGUAGE.into(),
"ruby",
tree_sitter_ruby::HIGHLIGHTS_QUERY,
tree_sitter_ruby::LOCALS_QUERY,
"",
)
.ok()
.map(|mut c| {
c.configure(HIGHLIGHT_NAMES);
c
})
})
.as_ref()
}
#[cfg(feature = "syntax-css")]
"css" => {
static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
CFG.get_or_init(|| {
HighlightConfiguration::new(
tree_sitter_css::LANGUAGE.into(),
"css",
tree_sitter_css::HIGHLIGHTS_QUERY,
"",
"",
)
.ok()
.map(|mut c| {
c.configure(HIGHLIGHT_NAMES);
c
})
})
.as_ref()
}
#[cfg(feature = "syntax-html")]
"html" | "htm" => {
static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
CFG.get_or_init(|| {
HighlightConfiguration::new(
tree_sitter_html::LANGUAGE.into(),
"html",
tree_sitter_html::HIGHLIGHTS_QUERY,
tree_sitter_html::INJECTIONS_QUERY,
"",
)
.ok()
.map(|mut c| {
c.configure(HIGHLIGHT_NAMES);
c
})
})
.as_ref()
}
#[cfg(feature = "syntax-yaml")]
"yaml" | "yml" => {
static CFG: OnceLock<Option<HighlightConfiguration>> = OnceLock::new();
CFG.get_or_init(|| {
HighlightConfiguration::new(
tree_sitter_yaml::LANGUAGE.into(),
"yaml",
tree_sitter_yaml::HIGHLIGHTS_QUERY,
"",
"",
)
.ok()
.map(|mut c| {
c.configure(HIGHLIGHT_NAMES);
c
})
})
.as_ref()
}
_ => None,
}
}
#[cfg(any(
feature = "syntax-rust",
feature = "syntax-python",
feature = "syntax-javascript",
feature = "syntax-typescript",
feature = "syntax-go",
feature = "syntax-bash",
feature = "syntax-json",
feature = "syntax-toml",
feature = "syntax-c",
feature = "syntax-cpp",
feature = "syntax-java",
feature = "syntax-ruby",
feature = "syntax-css",
feature = "syntax-html",
feature = "syntax-yaml",
))]
fn highlight_name_to_style(name: &str, theme: &Theme) -> Style {
let dark = theme.is_dark;
match name {
"keyword" => Style::new().fg(if dark {
Color::Rgb(198, 120, 221)
} else {
Color::Rgb(166, 38, 164)
}),
"string" | "string.special" => Style::new().fg(if dark {
Color::Rgb(152, 195, 121)
} else {
Color::Rgb(80, 161, 79)
}),
"comment" => Style::new().fg(theme.text_dim).italic(),
"number" | "constant" | "constant.builtin" => Style::new().fg(if dark {
Color::Rgb(209, 154, 102)
} else {
Color::Rgb(152, 104, 1)
}),
"function" | "function.builtin" => Style::new().fg(if dark {
Color::Rgb(97, 175, 239)
} else {
Color::Rgb(64, 120, 242)
}),
"function.macro" => Style::new().fg(if dark {
Color::Rgb(86, 182, 194)
} else {
Color::Rgb(1, 132, 188)
}),
"type" | "type.builtin" | "constructor" => Style::new().fg(if dark {
Color::Rgb(229, 192, 123)
} else {
Color::Rgb(152, 104, 1)
}),
"variable.builtin" => Style::new().fg(if dark {
Color::Rgb(224, 108, 117)
} else {
Color::Rgb(166, 38, 164)
}),
"property" | "property.builtin" => Style::new().fg(if dark {
Color::Rgb(97, 175, 239)
} else {
Color::Rgb(64, 120, 242)
}),
"tag" => Style::new().fg(if dark {
Color::Rgb(224, 108, 117)
} else {
Color::Rgb(166, 38, 164)
}),
"attribute" => Style::new().fg(if dark {
Color::Rgb(209, 154, 102)
} else {
Color::Rgb(152, 104, 1)
}),
"module" | "embedded" | "operator" | "variable" | "variable.parameter" => {
Style::new().fg(theme.text)
}
"punctuation" | "punctuation.bracket" | "punctuation.delimiter" | "punctuation.special" => {
Style::new().fg(theme.text_dim)
}
_ => Style::new().fg(theme.text),
}
}
#[allow(unused_variables)]
pub fn highlight_code(code: &str, lang: &str, theme: &Theme) -> Option<Vec<Vec<(String, Style)>>> {
#[cfg(any(
feature = "syntax-rust",
feature = "syntax-python",
feature = "syntax-javascript",
feature = "syntax-typescript",
feature = "syntax-go",
feature = "syntax-bash",
feature = "syntax-json",
feature = "syntax-toml",
feature = "syntax-c",
feature = "syntax-cpp",
feature = "syntax-java",
feature = "syntax-ruby",
feature = "syntax-css",
feature = "syntax-html",
feature = "syntax-yaml",
))]
{
use tree_sitter_highlight::{HighlightEvent, Highlighter};
let config = get_config(lang)?;
let mut highlighter = Highlighter::new();
let highlights = highlighter
.highlight(config, code.as_bytes(), None, |_| None)
.ok()?;
let default_style = Style::new().fg(theme.text);
let mut result: Vec<Vec<(String, Style)>> = Vec::new();
let mut current_line: Vec<(String, Style)> = Vec::new();
let mut style_stack: Vec<Style> = vec![default_style];
for event in highlights {
match event.ok()? {
HighlightEvent::Source { start, end } => {
let text = &code[start..end];
let style = *style_stack.last().unwrap_or(&default_style);
for (i, part) in text.split('\n').enumerate() {
if i > 0 {
result.push(std::mem::take(&mut current_line));
}
if !part.is_empty() {
current_line.push((part.to_string(), style));
}
}
}
HighlightEvent::HighlightStart(highlight) => {
let name = HIGHLIGHT_NAMES.get(highlight.0).copied().unwrap_or("");
let style = highlight_name_to_style(name, theme);
style_stack.push(style);
}
HighlightEvent::HighlightEnd => {
style_stack.pop();
}
}
}
if !current_line.is_empty() {
result.push(current_line);
}
Some(result)
}
#[cfg(not(any(
feature = "syntax-rust",
feature = "syntax-python",
feature = "syntax-javascript",
feature = "syntax-typescript",
feature = "syntax-go",
feature = "syntax-bash",
feature = "syntax-json",
feature = "syntax-toml",
feature = "syntax-c",
feature = "syntax-cpp",
feature = "syntax-java",
feature = "syntax-ruby",
feature = "syntax-css",
feature = "syntax-html",
feature = "syntax-yaml",
)))]
{
None
}
}
#[allow(unused_variables)]
pub fn is_language_supported(lang: &str) -> bool {
#[cfg(any(
feature = "syntax-rust",
feature = "syntax-python",
feature = "syntax-javascript",
feature = "syntax-typescript",
feature = "syntax-go",
feature = "syntax-bash",
feature = "syntax-json",
feature = "syntax-toml",
feature = "syntax-c",
feature = "syntax-cpp",
feature = "syntax-java",
feature = "syntax-ruby",
feature = "syntax-css",
feature = "syntax-html",
feature = "syntax-yaml",
))]
{
get_config(lang).is_some()
}
#[cfg(not(any(
feature = "syntax-rust",
feature = "syntax-python",
feature = "syntax-javascript",
feature = "syntax-typescript",
feature = "syntax-go",
feature = "syntax-bash",
feature = "syntax-json",
feature = "syntax-toml",
feature = "syntax-c",
feature = "syntax-cpp",
feature = "syntax-java",
feature = "syntax-ruby",
feature = "syntax-css",
feature = "syntax-html",
feature = "syntax-yaml",
)))]
{
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::style::Theme;
#[test]
fn highlight_returns_none_for_unknown_lang() {
let theme = Theme::dark();
assert!(highlight_code("let x = 1;", "brainfuck", &theme).is_none());
}
#[test]
fn is_language_supported_unknown() {
assert!(!is_language_supported("haskell"));
}
#[cfg(feature = "syntax-rust")]
#[test]
fn highlight_rust_basic() {
let theme = Theme::dark();
let result = highlight_code("let x = 1;", "rust", &theme);
assert!(result.is_some());
let lines = result.unwrap();
assert_eq!(lines.len(), 1);
let flat: String = lines[0].iter().map(|(t, _)| t.as_str()).collect();
assert!(flat.contains("let"));
assert!(flat.contains("1"));
}
#[cfg(feature = "syntax-rust")]
#[test]
fn highlight_rust_multiline() {
let theme = Theme::dark();
let code = "fn main() {\n println!(\"hello\");\n}";
let result = highlight_code(code, "rust", &theme).unwrap();
assert_eq!(result.len(), 3);
}
#[cfg(feature = "syntax-rust")]
#[test]
fn highlight_rust_rs_alias() {
let theme = Theme::dark();
assert!(highlight_code("let x = 1;", "rs", &theme).is_some());
}
#[cfg(feature = "syntax-python")]
#[test]
fn highlight_python_basic() {
let theme = Theme::dark();
let result = highlight_code("def foo():\n return 42", "python", &theme);
assert!(result.is_some());
let lines = result.unwrap();
assert_eq!(lines.len(), 2);
}
#[cfg(feature = "syntax-javascript")]
#[test]
fn highlight_javascript_basic() {
let theme = Theme::dark();
let result = highlight_code("const x = () => 42;", "js", &theme);
assert!(result.is_some());
}
#[cfg(feature = "syntax-bash")]
#[test]
fn highlight_bash_basic() {
let theme = Theme::dark();
let result = highlight_code("echo \"hello\"", "sh", &theme);
assert!(result.is_some());
}
#[cfg(feature = "syntax-json")]
#[test]
fn highlight_json_basic() {
let theme = Theme::dark();
let result = highlight_code("{\"key\": 42}", "json", &theme);
assert!(result.is_some());
}
#[cfg(feature = "syntax-toml")]
#[test]
fn highlight_toml_basic() {
let theme = Theme::dark();
let result = highlight_code("[package]\nname = \"slt\"", "toml", &theme);
assert!(result.is_some());
}
#[cfg(feature = "syntax-go")]
#[test]
fn highlight_go_basic() {
let theme = Theme::dark();
let result = highlight_code("package main\nfunc main() {}", "go", &theme);
assert!(result.is_some());
}
#[cfg(feature = "syntax-rust")]
#[test]
fn highlight_light_theme_differs() {
let dark = Theme::dark();
let light = Theme::light();
let dark_result = highlight_code("let x = 1;", "rust", &dark).unwrap();
let light_result = highlight_code("let x = 1;", "rust", &light).unwrap();
let dark_styles: Vec<Style> = dark_result[0].iter().map(|(_, s)| *s).collect();
let light_styles: Vec<Style> = light_result[0].iter().map(|(_, s)| *s).collect();
assert_ne!(dark_styles, light_styles);
}
#[cfg(feature = "syntax-rust")]
#[test]
fn highlight_incomplete_code_does_not_panic() {
let theme = Theme::dark();
let result = highlight_code("fn main( {", "rust", &theme);
assert!(result.is_some());
}
#[cfg(feature = "syntax-c")]
#[test]
fn highlight_c_basic() {
let theme = Theme::dark();
assert!(
highlight_code("#include <stdio.h>\nint main() { return 0; }", "c", &theme).is_some()
);
}
#[cfg(feature = "syntax-cpp")]
#[test]
fn highlight_cpp_basic() {
let theme = Theme::dark();
assert!(highlight_code("class Foo { public: void bar(); };", "cpp", &theme).is_some());
}
#[cfg(feature = "syntax-typescript")]
#[test]
fn highlight_typescript_basic() {
let theme = Theme::dark();
assert!(highlight_code("const x: number = 42;", "ts", &theme).is_some());
}
#[cfg(feature = "syntax-typescript")]
#[test]
fn highlight_tsx_basic() {
let theme = Theme::dark();
assert!(highlight_code("const App = () => <div>hello</div>;", "tsx", &theme).is_some());
}
#[cfg(feature = "syntax-java")]
#[test]
fn highlight_java_basic() {
let theme = Theme::dark();
assert!(highlight_code(
"public class Main { public static void main(String[] args) {} }",
"java",
&theme
)
.is_some());
}
#[cfg(feature = "syntax-ruby")]
#[test]
fn highlight_ruby_basic() {
let theme = Theme::dark();
assert!(highlight_code("def hello\n puts 'world'\nend", "ruby", &theme).is_some());
}
#[cfg(feature = "syntax-css")]
#[test]
fn highlight_css_basic() {
let theme = Theme::dark();
assert!(highlight_code("body { color: red; }", "css", &theme).is_some());
}
#[cfg(feature = "syntax-html")]
#[test]
fn highlight_html_basic() {
let theme = Theme::dark();
assert!(highlight_code("<div class=\"test\">hello</div>", "html", &theme).is_some());
}
#[cfg(feature = "syntax-yaml")]
#[test]
fn highlight_yaml_basic() {
let theme = Theme::dark();
assert!(highlight_code("name: slt\nversion: 0.14", "yaml", &theme).is_some());
}
}