feat: add syntax highlighting for 9 additional languages
- Cargo.toml: Add tree-sitter grammars for Nix, Python, JavaScript, TypeScript, Go, C, CSS, HTML, YAML. Upgrade tree-sitter-highlight to 0.26 for language version 15 compatibility. - src/highlight.rs: Add Language enum variants and get_config() match arms for all new languages. Update render() callback for 0.26 API (writes attributes to buffer). Add tests for Nix and Python highlighting. TOML excluded due to incompatible API (tree-sitter 0.20 vs 0.26).
This commit is contained in:
124
src/highlight.rs
124
src/highlight.rs
@@ -56,18 +56,36 @@ const HTML_ATTRS: &[&[u8]] = &[
|
||||
/// Supported languages for syntax highlighting.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Language {
|
||||
Rust,
|
||||
Bash,
|
||||
C,
|
||||
Css,
|
||||
Go,
|
||||
Html,
|
||||
JavaScript,
|
||||
Json,
|
||||
Nix,
|
||||
Python,
|
||||
Rust,
|
||||
TypeScript,
|
||||
Yaml,
|
||||
}
|
||||
|
||||
impl Language {
|
||||
/// Parse a language identifier from a code fence.
|
||||
pub fn from_fence(lang: &str) -> Option<Self> {
|
||||
match lang.to_lowercase().as_str() {
|
||||
"rust" | "rs" => Some(Language::Rust),
|
||||
"bash" | "sh" | "shell" | "zsh" => Some(Language::Bash),
|
||||
"c" => Some(Language::C),
|
||||
"css" => Some(Language::Css),
|
||||
"go" | "golang" => Some(Language::Go),
|
||||
"html" => Some(Language::Html),
|
||||
"javascript" | "js" => Some(Language::JavaScript),
|
||||
"json" => Some(Language::Json),
|
||||
"nix" => Some(Language::Nix),
|
||||
"python" | "py" => Some(Language::Python),
|
||||
"rust" | "rs" => Some(Language::Rust),
|
||||
"typescript" | "ts" | "tsx" => Some(Language::TypeScript),
|
||||
"yaml" | "yml" => Some(Language::Yaml),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -76,21 +94,66 @@ impl Language {
|
||||
/// Get highlight configuration for a language.
|
||||
fn get_config(lang: Language) -> HighlightConfiguration {
|
||||
let (language, name, highlights) = match lang {
|
||||
Language::Rust => (
|
||||
tree_sitter_rust::LANGUAGE.into(),
|
||||
"rust",
|
||||
tree_sitter_rust::HIGHLIGHTS_QUERY,
|
||||
),
|
||||
Language::Bash => (
|
||||
tree_sitter_bash::LANGUAGE.into(),
|
||||
"bash",
|
||||
tree_sitter_bash::HIGHLIGHT_QUERY,
|
||||
),
|
||||
Language::C => (
|
||||
tree_sitter_c::LANGUAGE.into(),
|
||||
"c",
|
||||
tree_sitter_c::HIGHLIGHT_QUERY,
|
||||
),
|
||||
Language::Css => (
|
||||
tree_sitter_css::LANGUAGE.into(),
|
||||
"css",
|
||||
tree_sitter_css::HIGHLIGHTS_QUERY,
|
||||
),
|
||||
Language::Go => (
|
||||
tree_sitter_go::LANGUAGE.into(),
|
||||
"go",
|
||||
tree_sitter_go::HIGHLIGHTS_QUERY,
|
||||
),
|
||||
Language::Html => (
|
||||
tree_sitter_html::LANGUAGE.into(),
|
||||
"html",
|
||||
tree_sitter_html::HIGHLIGHTS_QUERY,
|
||||
),
|
||||
Language::JavaScript => (
|
||||
tree_sitter_javascript::LANGUAGE.into(),
|
||||
"javascript",
|
||||
tree_sitter_javascript::HIGHLIGHT_QUERY,
|
||||
),
|
||||
Language::Json => (
|
||||
tree_sitter_json::LANGUAGE.into(),
|
||||
"json",
|
||||
tree_sitter_json::HIGHLIGHTS_QUERY,
|
||||
),
|
||||
Language::Nix => (
|
||||
tree_sitter_nix::LANGUAGE.into(),
|
||||
"nix",
|
||||
tree_sitter_nix::HIGHLIGHTS_QUERY,
|
||||
),
|
||||
Language::Python => (
|
||||
tree_sitter_python::LANGUAGE.into(),
|
||||
"python",
|
||||
tree_sitter_python::HIGHLIGHTS_QUERY,
|
||||
),
|
||||
Language::Rust => (
|
||||
tree_sitter_rust::LANGUAGE.into(),
|
||||
"rust",
|
||||
tree_sitter_rust::HIGHLIGHTS_QUERY,
|
||||
),
|
||||
Language::TypeScript => (
|
||||
tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
|
||||
"typescript",
|
||||
tree_sitter_typescript::HIGHLIGHTS_QUERY,
|
||||
),
|
||||
Language::Yaml => (
|
||||
tree_sitter_yaml::LANGUAGE.into(),
|
||||
"yaml",
|
||||
tree_sitter_yaml::HIGHLIGHTS_QUERY,
|
||||
),
|
||||
};
|
||||
|
||||
let mut config = HighlightConfiguration::new(language, name, highlights, "", "")
|
||||
@@ -111,8 +174,9 @@ pub fn highlight_code(lang: Language, source: &str) -> String {
|
||||
};
|
||||
|
||||
let mut renderer = HtmlRenderer::new();
|
||||
let result = renderer.render(highlights, source.as_bytes(), &|highlight| {
|
||||
HTML_ATTRS.get(highlight.0).copied().unwrap_or(b"<span>")
|
||||
let result = renderer.render(highlights, source.as_bytes(), &|highlight, buf| {
|
||||
let attrs = HTML_ATTRS.get(highlight.0).copied().unwrap_or(b"");
|
||||
buf.extend_from_slice(attrs);
|
||||
});
|
||||
|
||||
match result {
|
||||
@@ -139,6 +203,27 @@ mod tests {
|
||||
assert_eq!(Language::from_fence("bash"), Some(Language::Bash));
|
||||
assert_eq!(Language::from_fence("sh"), Some(Language::Bash));
|
||||
assert_eq!(Language::from_fence("json"), Some(Language::Json));
|
||||
assert_eq!(Language::from_fence("nix"), Some(Language::Nix));
|
||||
assert_eq!(Language::from_fence("python"), Some(Language::Python));
|
||||
assert_eq!(Language::from_fence("py"), Some(Language::Python));
|
||||
assert_eq!(
|
||||
Language::from_fence("javascript"),
|
||||
Some(Language::JavaScript)
|
||||
);
|
||||
assert_eq!(Language::from_fence("js"), Some(Language::JavaScript));
|
||||
assert_eq!(
|
||||
Language::from_fence("typescript"),
|
||||
Some(Language::TypeScript)
|
||||
);
|
||||
assert_eq!(Language::from_fence("ts"), Some(Language::TypeScript));
|
||||
assert_eq!(Language::from_fence("tsx"), Some(Language::TypeScript));
|
||||
assert_eq!(Language::from_fence("go"), Some(Language::Go));
|
||||
assert_eq!(Language::from_fence("golang"), Some(Language::Go));
|
||||
assert_eq!(Language::from_fence("c"), Some(Language::C));
|
||||
assert_eq!(Language::from_fence("yaml"), Some(Language::Yaml));
|
||||
assert_eq!(Language::from_fence("yml"), Some(Language::Yaml));
|
||||
assert_eq!(Language::from_fence("css"), Some(Language::Css));
|
||||
assert_eq!(Language::from_fence("html"), Some(Language::Html));
|
||||
assert_eq!(Language::from_fence("unknown"), None);
|
||||
}
|
||||
|
||||
@@ -147,11 +232,8 @@ mod tests {
|
||||
let code = "fn main() { println!(\"hello\"); }";
|
||||
let html = highlight_code(Language::Rust, code);
|
||||
|
||||
// Should contain span elements with highlight classes
|
||||
assert!(html.contains("class=\"hl-"));
|
||||
// Should contain the keyword "fn"
|
||||
assert!(html.contains("fn"));
|
||||
// Should contain the string
|
||||
assert!(html.contains("hello"));
|
||||
}
|
||||
|
||||
@@ -164,6 +246,24 @@ mod tests {
|
||||
assert!(html.contains("echo"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight_nix_code() {
|
||||
let code = "{ pkgs, ... }: { environment.systemPackages = [ pkgs.vim ]; }";
|
||||
let html = highlight_code(Language::Nix, code);
|
||||
|
||||
assert!(html.contains("class=\"hl-"));
|
||||
assert!(html.contains("pkgs"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight_python_code() {
|
||||
let code = "def hello():\n print(\"world\")";
|
||||
let html = highlight_code(Language::Python, code);
|
||||
|
||||
assert!(html.contains("class=\"hl-"));
|
||||
assert!(html.contains("def"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_html_escape_fallback() {
|
||||
let escaped = html_escape("<script>alert('xss')</script>");
|
||||
|
||||
Reference in New Issue
Block a user