From da3263702e39574046b56586df8465b8eccdab7d Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Fri, 3 Jul 2026 09:01:38 -0300 Subject: separated all ts grammars vs a small set of them --- build.zig | 95 ++++++++++++++++++++++------------ paint.zig | 172 +++++++++++++++++++++++++++++++------------------------------- 2 files changed, 148 insertions(+), 119 deletions(-) diff --git a/build.zig b/build.zig index 1b9a4351..f272e26e 100644 --- a/build.zig +++ b/build.zig @@ -2,6 +2,7 @@ const std = @import("std"); const AppKind = enum { main, replay }; const GhosttyArtifact = enum { ghostty, lib }; +const TreeSitterGrammars = enum { disabled, minimal, full }; pub fn build(b: *std.Build) void { const requested_optimize = b.standardOptimizeOption(.{}); @@ -30,7 +31,11 @@ pub fn build(b: *std.Build) void { const sdl = b.option(bool, "sdl", "build the SDL3 GPU UI frontend instead of the terminal backend") orelse false; const sdl_backend = sdl or is_emscripten; const replay_dump = b.option([]const u8, "replay-dump", "embed this replay dump .zon into pardes-replay"); - const syntax_highlighting = !is_emscripten; + const default_tree_sitter_grammars: TreeSitterGrammars = if (is_emscripten) .minimal else .full; + const tree_sitter_grammars = b.option(TreeSitterGrammars, "tree-sitter", "tree-sitter grammar set: disabled, minimal, full") orelse default_tree_sitter_grammars; + const syntax_highlighting = tree_sitter_grammars != .disabled; + const syntax_minimal_grammars = tree_sitter_grammars == .minimal or tree_sitter_grammars == .full; + const syntax_full_grammars = tree_sitter_grammars == .full; const emscripten_include: ?std.Build.LazyPath = if (emscripten_sysroot) |sysroot| .{ .cwd_relative = b.pathJoin(&.{ sysroot, "include" }) } else @@ -86,11 +91,15 @@ pub fn build(b: *std.Build) void { const main_app_config = b.addOptions(); main_app_config.addOption(AppKind, "app_kind", .main); main_app_config.addOption(bool, "syntax_highlighting", syntax_highlighting); + main_app_config.addOption(bool, "syntax_minimal_grammars", syntax_minimal_grammars); + main_app_config.addOption(bool, "syntax_full_grammars", syntax_full_grammars); exe.root_module.addOptions("app_config", main_app_config); const replay_app_config = b.addOptions(); replay_app_config.addOption(AppKind, "app_kind", .replay); replay_app_config.addOption(bool, "syntax_highlighting", syntax_highlighting); + replay_app_config.addOption(bool, "syntax_minimal_grammars", syntax_minimal_grammars); + replay_app_config.addOption(bool, "syntax_full_grammars", syntax_full_grammars); replay.root_module.addOptions("app_config", replay_app_config); const replay_config = b.addOptions(); replay_config.addOption(bool, "embedded_dump", replay_dump != null); @@ -245,12 +254,40 @@ pub fn build(b: *std.Build) void { exe.root_module.addImport("zstbi", zstbi_mod); replay.root_module.addImport("zstbi", zstbi_mod); + const replay_wasm_ready = is_emscripten and replay_dump != null and emscripten_sysroot != null; + const emcc_for_wasm = if (replay_wasm_ready) blk: { + const replay_obj = b.addObject(.{ + .name = "pardes-replay", + .root_module = replay.root_module, + }); + if (emscripten_libc_file) |libc_file| replay_obj.setLibCFile(libc_file); + + const emcc_path = b.option([]const u8, "emcc", "path to emcc for replay-wasm final link") orelse "/usr/lib/emscripten/emcc"; + const emcc = b.addSystemCommand(&.{emcc_path}); + emcc.addFileArg(replay_obj.getEmittedBinDirectory().path(b, "pardes-replay_zcu.o")); + emcc.addFileArg(replay_obj.getEmittedBin()); + emcc.addFileArg(b.path("vendor/stb/stb_truetype_impl.c")); + emcc.addFileArg(b.path("vendor/stb/font.c")); + emcc.addFileArg(zstbi_dep.path("src/zstbi.c")); + if (sdl_lib_for_wasm) |sdl_lib| emcc.addFileArg(sdl_lib.getEmittedBin()); + break :blk emcc; + } else null; + if (syntax_highlighting) { // tree-sitter syntax highlighting for file panes: the Zig bindings + C runtime // (the `tree-sitter` module), each grammar's parser.c linked as a static lib, // and its highlights.scm read at build time into the `ts_queries` options module. const tree_sitter = b.dependency("tree_sitter", .{ .target = target, .optimize = optimize }); const tree_sitter_mod = tree_sitter.module("tree_sitter"); + if (is_emscripten) { + for (tree_sitter_mod.link_objects.items) |link_object| switch (link_object) { + .other_step => |lib| { + if (emscripten_libc_file) |libc_file| lib.setLibCFile(libc_file); + if (emcc_for_wasm) |emcc| emcc.addFileArg(lib.getEmittedBin()); + }, + else => {}, + }; + } exe.root_module.addImport("tree-sitter", tree_sitter_mod); replay.root_module.addImport("tree-sitter", tree_sitter_mod); @@ -261,14 +298,15 @@ pub fn build(b: *std.Build) void { scanner: bool = false, query: []const u8 = "queries/highlights.scm", local_query: ?[]const u8 = null, + minimal: bool = false, }; const grammars = [_]Grammar{ .{ .name = "ada", .dep = "ts_ada" }, .{ .name = "bash", .dep = "ts_bash", .scanner = true }, - .{ .name = "c", .dep = "ts_c" }, + .{ .name = "c", .dep = "ts_c", .minimal = true }, .{ .name = "c_sharp", .dep = "ts_c_sharp", .scanner = true }, .{ .name = "clojure", .dep = "ts_clojure" }, - .{ .name = "cpp", .dep = "ts_cpp", .scanner = true }, + .{ .name = "cpp", .dep = "ts_cpp", .scanner = true, .minimal = true }, .{ .name = "css", .dep = "ts_css", .scanner = true }, .{ .name = "elixir", .dep = "ts_elixir", .scanner = true }, .{ .name = "erlang", .dep = "ts_erlang", .scanner = true }, @@ -288,25 +326,29 @@ pub fn build(b: *std.Build) void { .{ .name = "ruby", .dep = "ts_ruby", .scanner = true }, .{ .name = "rust", .dep = "ts_rust", .scanner = true }, .{ .name = "scala", .dep = "ts_scala", .scanner = true }, - .{ .name = "zig", .dep = "ts_zig" }, + .{ .name = "zig", .dep = "ts_zig", .minimal = true }, }; const ts_queries = b.addOptions(); inline for (grammars) |g| { - const dep = b.dependency(g.dep, .{}); - const query_path = if (g.local_query) |p| b.path(p) else dep.path(g.query); - const query = std.Io.Dir.cwd().readFileAlloc(io, query_path.getPath(b), b.allocator, .limited(0x100000)) catch @panic("read " ++ g.name ++ " highlights"); - ts_queries.addOption([]const u8, g.name ++ "_highlights", query); - - const lib = b.addLibrary(.{ - .name = "tree-sitter-" ++ g.name, - .root_module = b.createModule(.{ .target = target, .optimize = optimize, .link_libc = true }), - .linkage = .static, - }); - lib.root_module.addCSourceFile(.{ .file = dep.path(g.src ++ "/parser.c") }); - if (g.scanner) lib.root_module.addCSourceFile(.{ .file = dep.path(g.src ++ "/scanner.c") }); - lib.root_module.addIncludePath(dep.path(g.src)); - exe.root_module.linkLibrary(lib); - replay.root_module.linkLibrary(lib); + if (tree_sitter_grammars == .full or g.minimal) { + const dep = b.dependency(g.dep, .{}); + const query_path = if (g.local_query) |p| b.path(p) else dep.path(g.query); + const query = std.Io.Dir.cwd().readFileAlloc(io, query_path.getPath(b), b.allocator, .limited(0x100000)) catch @panic("read " ++ g.name ++ " highlights"); + ts_queries.addOption([]const u8, g.name ++ "_highlights", query); + + const lib = b.addLibrary(.{ + .name = "tree-sitter-" ++ g.name, + .root_module = b.createModule(.{ .target = target, .optimize = optimize, .link_libc = true }), + .linkage = .static, + }); + if (emscripten_libc_file) |libc_file| lib.setLibCFile(libc_file); + lib.root_module.addCSourceFile(.{ .file = dep.path(g.src ++ "/parser.c") }); + if (g.scanner) lib.root_module.addCSourceFile(.{ .file = dep.path(g.src ++ "/scanner.c") }); + lib.root_module.addIncludePath(dep.path(g.src)); + exe.root_module.linkLibrary(lib); + replay.root_module.linkLibrary(lib); + if (emcc_for_wasm) |emcc| emcc.addFileArg(lib.getEmittedBin()); + } } exe.root_module.addOptions("ts_queries", ts_queries); replay.root_module.addOptions("ts_queries", ts_queries); @@ -335,20 +377,7 @@ pub fn build(b: *std.Build) void { } else if (emscripten_sysroot == null) { replay_wasm_step.dependOn(&b.addFail("replay-wasm requires an Emscripten sysroot; pass --sysroot /cache/sysroot or set EM_CACHE/EMSDK").step); } else { - const replay_obj = b.addObject(.{ - .name = "pardes-replay", - .root_module = replay.root_module, - }); - if (emscripten_libc_file) |libc_file| replay_obj.setLibCFile(libc_file); - - const emcc_path = b.option([]const u8, "emcc", "path to emcc for replay-wasm final link") orelse "/usr/lib/emscripten/emcc"; - const emcc = b.addSystemCommand(&.{emcc_path}); - emcc.addFileArg(replay_obj.getEmittedBinDirectory().path(b, "pardes-replay_zcu.o")); - emcc.addFileArg(replay_obj.getEmittedBin()); - emcc.addFileArg(b.path("vendor/stb/stb_truetype_impl.c")); - emcc.addFileArg(b.path("vendor/stb/font.c")); - emcc.addFileArg(zstbi_dep.path("src/zstbi.c")); - if (sdl_lib_for_wasm) |sdl_lib| emcc.addFileArg(sdl_lib.getEmittedBin()); + const emcc = emcc_for_wasm.?; emcc.addPrefixedDirectoryArg("-I", b.path("vendor/stb")); emcc.addPrefixedDirectoryArg("-I", zstbi_dep.path("libs/stbi")); emcc.addArgs(&.{ diff --git a/paint.zig b/paint.zig index 2f953a5a..b4ab932b 100644 --- a/paint.zig +++ b/paint.zig @@ -2,10 +2,13 @@ const std = @import("std"); const ui = @import("ui"); const app_config = @import("app_config"); const ghostty_vt = @import("ghostty-vt"); -const ts = if (app_config.syntax_highlighting) @import("tree-sitter") else struct { +const syntax_highlighting = app_config.syntax_highlighting; +const syntax_minimal_grammars = app_config.syntax_minimal_grammars; +const syntax_full_grammars = app_config.syntax_full_grammars; +const ts = if (syntax_highlighting) @import("tree-sitter") else struct { pub const Language = opaque {}; }; -const ts_queries = if (app_config.syntax_highlighting) @import("ts_queries") else struct {}; +const ts_queries = if (syntax_highlighting) @import("ts_queries") else struct {}; extern fn tree_sitter_ada() callconv(.c) *const ts.Language; extern fn tree_sitter_bash() callconv(.c) *const ts.Language; @@ -168,89 +171,86 @@ fn extIs(ext: []const u8, comptime choices: anytype) bool { } pub fn highlightFile(gpa: std.mem.Allocator, path: []const u8, content: []const u8) ![]u8 { - if (!app_config.syntax_highlighting) return &.{}; + if (!syntax_highlighting) return &.{}; const ext = std.fs.path.extension(path); - var lang: *const ts.Language = undefined; - var query_src: []const u8 = undefined; - if (extIs(ext, .{ ".adb", ".ads", ".ada" })) { - lang = tree_sitter_ada(); - query_src = ts_queries.ada_highlights; - } else if (extIs(ext, .{ ".sh", ".bash", ".zsh" })) { - lang = tree_sitter_bash(); - query_src = ts_queries.bash_highlights; - } else if (extIs(ext, .{ ".c", ".h" })) { - lang = tree_sitter_c(); - query_src = ts_queries.c_highlights; - } else if (extIs(ext, .{ ".cs", ".csx" })) { - lang = tree_sitter_c_sharp(); - query_src = ts_queries.c_sharp_highlights; - } else if (extIs(ext, .{ ".clj", ".cljs", ".cljc", ".edn" })) { - lang = tree_sitter_clojure(); - query_src = ts_queries.clojure_highlights; - } else if (extIs(ext, .{ ".cpp", ".cc", ".cxx", ".hpp", ".hh", ".hxx" })) { - lang = tree_sitter_cpp(); - query_src = ts_queries.cpp_highlights; - } else if (extIs(ext, .{".css"})) { - lang = tree_sitter_css(); - query_src = ts_queries.css_highlights; - } else if (extIs(ext, .{ ".ex", ".exs" })) { - lang = tree_sitter_elixir(); - query_src = ts_queries.elixir_highlights; - } else if (extIs(ext, .{ ".erl", ".hrl" })) { - lang = tree_sitter_erlang(); - query_src = ts_queries.erlang_highlights; - } else if (extIs(ext, .{ ".f", ".for", ".ftn", ".f90", ".f95", ".f03", ".f08" })) { - lang = tree_sitter_fortran(); - query_src = ts_queries.fortran_highlights; - } else if (extIs(ext, .{".go"})) { - lang = tree_sitter_go(); - query_src = ts_queries.go_highlights; - } else if (extIs(ext, .{ ".hs", ".lhs" })) { - lang = tree_sitter_haskell(); - query_src = ts_queries.haskell_highlights; - } else if (extIs(ext, .{ ".html", ".htm" })) { - lang = tree_sitter_html(); - query_src = ts_queries.html_highlights; - } else if (extIs(ext, .{".java"})) { - lang = tree_sitter_java(); - query_src = ts_queries.java_highlights; - } else if (extIs(ext, .{ ".js", ".jsx", ".mjs", ".cjs" })) { - lang = tree_sitter_javascript(); - query_src = ts_queries.javascript_highlights; - } else if (extIs(ext, .{".json"})) { - lang = tree_sitter_json(); - query_src = ts_queries.json_highlights; - } else if (extIs(ext, .{ ".kt", ".kts" })) { - lang = tree_sitter_kotlin(); - query_src = ts_queries.kotlin_highlights; - } else if (extIs(ext, .{ ".ml", ".mli" })) { - lang = tree_sitter_ocaml(); - query_src = ts_queries.ocaml_highlights; - } else if (extIs(ext, .{ ".pas", ".pp", ".p" })) { - lang = tree_sitter_pascal(); - query_src = ts_queries.pascal_highlights; - } else if (extIs(ext, .{ ".php", ".phtml", ".php3", ".php4", ".php5" })) { - lang = tree_sitter_php(); - query_src = ts_queries.php_highlights; - } else if (extIs(ext, .{ ".ps1", ".psm1", ".psd1" })) { - lang = tree_sitter_powershell(); - query_src = ts_queries.powershell_highlights; - } else if (extIs(ext, .{ ".py", ".pyw" })) { - lang = tree_sitter_python(); - query_src = ts_queries.python_highlights; - } else if (extIs(ext, .{ ".rb", ".rake" })) { - lang = tree_sitter_ruby(); - query_src = ts_queries.ruby_highlights; - } else if (extIs(ext, .{".rs"})) { - lang = tree_sitter_rust(); - query_src = ts_queries.rust_highlights; - } else if (extIs(ext, .{ ".scala", ".sc" })) { - lang = tree_sitter_scala(); - query_src = ts_queries.scala_highlights; - } else if (extIs(ext, .{ ".zig", ".zon" })) { - lang = tree_sitter_zig(); - query_src = ts_queries.zig_highlights; - } else return &.{}; + const selected = blk: { + const Selected = struct { + lang: *const ts.Language, + query_src: []const u8, + }; + + if (syntax_full_grammars) { + if (extIs(ext, .{ ".adb", ".ads", ".ada" })) { + break :blk Selected{ .lang = tree_sitter_ada(), .query_src = ts_queries.ada_highlights }; + } else if (extIs(ext, .{ ".sh", ".bash", ".zsh" })) { + break :blk Selected{ .lang = tree_sitter_bash(), .query_src = ts_queries.bash_highlights }; + } + } + if (syntax_minimal_grammars) { + if (extIs(ext, .{ ".c", ".h" })) { + break :blk Selected{ .lang = tree_sitter_c(), .query_src = ts_queries.c_highlights }; + } + } + if (syntax_full_grammars) { + if (extIs(ext, .{ ".cs", ".csx" })) { + break :blk Selected{ .lang = tree_sitter_c_sharp(), .query_src = ts_queries.c_sharp_highlights }; + } else if (extIs(ext, .{ ".clj", ".cljs", ".cljc", ".edn" })) { + break :blk Selected{ .lang = tree_sitter_clojure(), .query_src = ts_queries.clojure_highlights }; + } + } + if (syntax_minimal_grammars) { + if (extIs(ext, .{ ".cpp", ".cc", ".cxx", ".hpp", ".hh", ".hxx" })) { + break :blk Selected{ .lang = tree_sitter_cpp(), .query_src = ts_queries.cpp_highlights }; + } + } + if (syntax_full_grammars) { + if (extIs(ext, .{".css"})) { + break :blk Selected{ .lang = tree_sitter_css(), .query_src = ts_queries.css_highlights }; + } else if (extIs(ext, .{ ".ex", ".exs" })) { + break :blk Selected{ .lang = tree_sitter_elixir(), .query_src = ts_queries.elixir_highlights }; + } else if (extIs(ext, .{ ".erl", ".hrl" })) { + break :blk Selected{ .lang = tree_sitter_erlang(), .query_src = ts_queries.erlang_highlights }; + } else if (extIs(ext, .{ ".f", ".for", ".ftn", ".f90", ".f95", ".f03", ".f08" })) { + break :blk Selected{ .lang = tree_sitter_fortran(), .query_src = ts_queries.fortran_highlights }; + } else if (extIs(ext, .{".go"})) { + break :blk Selected{ .lang = tree_sitter_go(), .query_src = ts_queries.go_highlights }; + } else if (extIs(ext, .{ ".hs", ".lhs" })) { + break :blk Selected{ .lang = tree_sitter_haskell(), .query_src = ts_queries.haskell_highlights }; + } else if (extIs(ext, .{ ".html", ".htm" })) { + break :blk Selected{ .lang = tree_sitter_html(), .query_src = ts_queries.html_highlights }; + } else if (extIs(ext, .{".java"})) { + break :blk Selected{ .lang = tree_sitter_java(), .query_src = ts_queries.java_highlights }; + } else if (extIs(ext, .{ ".js", ".jsx", ".mjs", ".cjs" })) { + break :blk Selected{ .lang = tree_sitter_javascript(), .query_src = ts_queries.javascript_highlights }; + } else if (extIs(ext, .{".json"})) { + break :blk Selected{ .lang = tree_sitter_json(), .query_src = ts_queries.json_highlights }; + } else if (extIs(ext, .{ ".kt", ".kts" })) { + break :blk Selected{ .lang = tree_sitter_kotlin(), .query_src = ts_queries.kotlin_highlights }; + } else if (extIs(ext, .{ ".ml", ".mli" })) { + break :blk Selected{ .lang = tree_sitter_ocaml(), .query_src = ts_queries.ocaml_highlights }; + } else if (extIs(ext, .{ ".pas", ".pp", ".p" })) { + break :blk Selected{ .lang = tree_sitter_pascal(), .query_src = ts_queries.pascal_highlights }; + } else if (extIs(ext, .{ ".php", ".phtml", ".php3", ".php4", ".php5" })) { + break :blk Selected{ .lang = tree_sitter_php(), .query_src = ts_queries.php_highlights }; + } else if (extIs(ext, .{ ".ps1", ".psm1", ".psd1" })) { + break :blk Selected{ .lang = tree_sitter_powershell(), .query_src = ts_queries.powershell_highlights }; + } else if (extIs(ext, .{ ".py", ".pyw" })) { + break :blk Selected{ .lang = tree_sitter_python(), .query_src = ts_queries.python_highlights }; + } else if (extIs(ext, .{ ".rb", ".rake" })) { + break :blk Selected{ .lang = tree_sitter_ruby(), .query_src = ts_queries.ruby_highlights }; + } else if (extIs(ext, .{".rs"})) { + break :blk Selected{ .lang = tree_sitter_rust(), .query_src = ts_queries.rust_highlights }; + } else if (extIs(ext, .{ ".scala", ".sc" })) { + break :blk Selected{ .lang = tree_sitter_scala(), .query_src = ts_queries.scala_highlights }; + } + } + if (syntax_minimal_grammars) { + if (extIs(ext, .{ ".zig", ".zon" })) { + break :blk Selected{ .lang = tree_sitter_zig(), .query_src = ts_queries.zig_highlights }; + } + } + return &.{}; + }; const styles = try gpa.alloc(u8, content.len); errdefer gpa.free(styles); @@ -258,12 +258,12 @@ pub fn highlightFile(gpa: std.mem.Allocator, path: []const u8, content: []const const parser = ts.Parser.create(); defer parser.destroy(); - parser.setLanguage(lang) catch return styles; + parser.setLanguage(selected.lang) catch return styles; const tree = parser.parseString(content, null) orelse return styles; defer tree.destroy(); var error_offset: u32 = 0; - const query = ts.Query.create(lang, query_src, &error_offset) catch return styles; + const query = ts.Query.create(selected.lang, selected.query_src, &error_offset) catch return styles; defer query.destroy(); const cursor = ts.QueryCursor.create(); defer cursor.destroy(); -- cgit v1.3