diff --git a/Cargo.lock b/Cargo.lock index e800240..fa22b28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1847,6 +1847,16 @@ dependencies = [ "tree-sitter-language", ] +[[package]] +name = "tree-sitter-zig" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab11fc124851b0db4dd5e55983bbd9631192e93238389dcd44521715e5d53e28" +dependencies = [ + "cc", + "tree-sitter-language", +] + [[package]] name = "typenum" version = "1.20.1" @@ -1933,6 +1943,7 @@ dependencies = [ "tree-sitter-toml-ng", "tree-sitter-typescript", "tree-sitter-yaml", + "tree-sitter-zig", "unicode-segmentation", "unicode-width", ] diff --git a/Cargo.toml b/Cargo.toml index 196ba32..c56fc1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ tree-sitter-rust = "0.24.2" tree-sitter-toml-ng = "0.7.0" tree-sitter-typescript = "0.23.2" tree-sitter-yaml = "0.7.2" +tree-sitter-zig = "1.1.2" unicode-segmentation = "1.12" unicode-width = "0.2" diff --git a/TODO.md b/TODO.md index ebf88e3..3eb991e 100644 --- a/TODO.md +++ b/TODO.md @@ -2,7 +2,7 @@ - [x] make selecting a file in the diff view file picker focus the after buffer instead of the before buffer - [x] improve the lsp hover floating window: - it should be taller -- [ ] add support for zig syntax highlighting +- [x] add support for zig syntax highlighting - [ ] add the `*` action from vim on word to highlights and searches in buffer in one action. - [ ] diff view: github integration (code review) - add comments on a pr, add review comments at specific lines of files, request changes, approve pr, etc. - [ ] diff view: local review comments (same features as for github reviews) that allows review of local changes to deliver specific reviews to ai agents used for coding. diff --git a/docs/editing.md b/docs/editing.md index 9bebe87..4420d5e 100644 --- a/docs/editing.md +++ b/docs/editing.md @@ -527,7 +527,7 @@ config.bind "leader f", config.core.follow_link ## Syntax highlighting -Buffers highlight automatically using [Tree-sitter](https://tree-sitter.github.io/tree-sitter/) and compiled-in grammar/query packages. The selected language set is Rust, Ruby, Python, JSON, Bash, Makefile, Markdown, JavaScript/JSX, TypeScript/TSX, HTML, CSS, YAML, TOML, C, C++, and Go. Detection uses filename/extension or the in-memory first-line shebang, including common special filenames such as Makefile, Gemfile, and Cargo.lock; no file reread occurs. Unsupported formats use the normal foreground. The detected language appears in status, and save-as redetects it. +Buffers highlight automatically using [Tree-sitter](https://tree-sitter.github.io/tree-sitter/) and compiled-in grammar/query packages. The selected language set is Rust, Ruby, Python, JSON, Bash, Makefile, Markdown, JavaScript/JSX, TypeScript/TSX, HTML, CSS, YAML, TOML, C, C++, Go, and Zig (`.zig`). Detection uses filename/extension or the in-memory first-line shebang, including common special filenames such as Makefile, Gemfile, and Cargo.lock; no file reread occurs. Unsupported formats use the normal foreground. The detected language appears in status, and save-as redetects it. Colors retain the terminal palette: comments dark gray, strings green, keywords magenta, numbers yellow, types/markup cyan, and functions light blue. Grammar queries handle multiline/nested constructs, even when their opening is above the viewport. Markdown uses its inline grammar and named fenced-code injections; HTML script/style content uses JavaScript/CSS. Known tagged-template and raw-string injections use the same selected grammar registry; unknown injected languages retain host highlighting or plain text. Highlighting styles whole graphemes and expanded tab cells; partially clipped wide placeholders remain unchanged. Search, Visual selections, and secondary carets retain foreground/background precedence. diff --git a/src/syntax.rs b/src/syntax.rs index a5f0040..fbb1d24 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -1658,6 +1658,43 @@ mod tests { ); } + #[test] + fn zig_highlights_literals_builtins_and_predicate_matched_identifiers() { + let source = r#"const std = @import("std"); +const MAX_COUNT: u32 = 42; +pub fn main() void { + const local = @as(u32, 7); + const value = Point; + const text = + \\first é + \\second + ; + // note +}"#; + let cache = self::cache(source, "main.zig"); + for (row, token, kind) in [ + (0, "const", SyntaxKind::Keyword), + (0, "@import", SyntaxKind::Keyword), + (0, "\"std\"", SyntaxKind::String), + (1, "MAX_COUNT", SyntaxKind::Keyword), + (1, "u32", SyntaxKind::Type), + (1, "42", SyntaxKind::Number), + (2, "pub", SyntaxKind::Keyword), + (2, "fn", SyntaxKind::Keyword), + (2, "main", SyntaxKind::Function), + (2, "void", SyntaxKind::Type), + (3, "@as", SyntaxKind::Function), + (4, "Point", SyntaxKind::Type), + (6, "first é", SyntaxKind::String), + (7, "second", SyntaxKind::String), + (9, "note", SyntaxKind::Comment), + ] { + assert_token(&cache, source, row, token, kind); + } + let local = source.lines().nth(3).unwrap().find("local").unwrap(); + assert_eq!(kind_at(&cache, 3, local), None); + } + #[test] fn filenames_shebang_save_as_and_tsx_use_actual_grammar_identity() { let source = lines("\u{feff}#!/usr/bin/env python3\nreturn 42"); diff --git a/src/syntax/languages.rs b/src/syntax/languages.rs index 6900893..5b66cdf 100644 --- a/src/syntax/languages.rs +++ b/src/syntax/languages.rs @@ -74,6 +74,13 @@ static MD_HIGHLIGHTS: LazyLock = LazyLock::new(|| { .join("\n") }); +// Upstream's Lua patterns are regex-compatible; spell-check metadata is not a color. +static ZIG_HIGHLIGHTS: LazyLock = LazyLock::new(|| { + tree_sitter_zig::HIGHLIGHTS_QUERY + .replace("#lua-match?", "#match?") + .replace(" @spell", "") +}); + // The MIT-licensed upstream C++ crate bundles, but does not export, injections.scm. // This minimal AST query uses its raw-string delimiter/content nodes: // https://github.com/tree-sitter/tree-sitter-cpp/blob/v0.23.4/queries/injections.scm @@ -111,7 +118,7 @@ fn spec( } } -static LANGUAGES: LazyLock<[LanguageSpec; 18]> = LazyLock::new(|| { +static LANGUAGES: LazyLock<[LanguageSpec; 19]> = LazyLock::new(|| { [ spec( super::RUST_ID, @@ -257,6 +264,14 @@ static LANGUAGES: LazyLock<[LanguageSpec; 18]> = LazyLock::new(|| { tree_sitter_md::INJECTION_QUERY_INLINE, "", ), + spec( + "zig", + "Zig", + tree_sitter_zig::LANGUAGE.into(), + ZIG_HIGHLIGHTS.as_str(), + tree_sitter_zig::INJECTIONS_QUERY, + "", + ), ] }); @@ -349,6 +364,7 @@ fn detect_path(path: &Path) -> Option<&'static LanguageSpec> { "cc" | "cpp" | "cxx" | "c++" | "hh" | "hpp" | "hxx" | "h++" | "ipp" | "inl" | "tpp" => "C++", "go" => "Go", + "zig" => "Zig", _ => return None, } } @@ -396,8 +412,6 @@ mod tests { #[test] fn selected_files_and_auxiliary_grammars_are_registered() { - assert_eq!(all().count(), 18); - assert!(all().all(|spec| !spec.highlights.is_empty())); for (filename, language) in [ ("main.rs", "Rust"), ("Gemfile", "Ruby"), @@ -417,6 +431,7 @@ mod tests { ("main.cpp", "C++"), ("main.C", "C++"), ("main.go", "Go"), + ("main.zig", "Zig"), ] { assert_eq!( detect(Some(Path::new(filename)), None).map(|spec| spec.name),