summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-07-03 09:01:38 -0300
committerGabriel Schneider <[email protected]>2026-08-01 15:02:07 -0300
commitda3263702e39574046b56586df8465b8eccdab7d (patch)
treef3e8582447a40a79345809e2c046405af4777f49
parent589269c0b3ca74a29f5889fa6e781e59cef7efdb (diff)
downloadpardes-da3263702e39574046b56586df8465b8eccdab7d.tar.gz
pardes-da3263702e39574046b56586df8465b8eccdab7d.zip
separated all ts grammars vs a small set of them
-rw-r--r--build.zig93
-rw-r--r--paint.zig172
2 files changed, 147 insertions, 118 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);
+ 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,
- });
- 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);
+ 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 <emscripten>/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();