summaryrefslogtreecommitdiff
path: root/paint.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-07-03 17:33:05 -0300
committerGabriel Schneider <[email protected]>2026-08-01 15:02:07 -0300
commitf3b62fe53b19e36f21f8020831718cbf095d6a7b (patch)
treecdfd008869bb5f5aeb69f0ef30584fec86f6f95b /paint.zig
parent6b4b1f76bb6bf969fb5106f95fb3f9ad077fac08 (diff)
downloadpardes-f3b62fe53b19e36f21f8020831718cbf095d6a7b.tar.gz
pardes-f3b62fe53b19e36f21f8020831718cbf095d6a7b.zip
performance pass
Diffstat (limited to 'paint.zig')
-rw-r--r--paint.zig197
1 files changed, 105 insertions, 92 deletions
diff --git a/paint.zig b/paint.zig
index b4ab932b..97efc334 100644
--- a/paint.zig
+++ b/paint.zig
@@ -7,6 +7,7 @@ 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 {};
+ pub const Query = opaque {};
};
const ts_queries = if (syntax_highlighting) @import("ts_queries") else struct {};
@@ -134,6 +135,99 @@ pub const SynStyle = struct {
bold: bool,
};
+const HighlightLanguage = struct {
+ lang: *const ts.Language,
+ query: *ts.Query,
+};
+
+const HighlightTier = enum { minimal, full };
+
+const HighlightSpec = struct {
+ tier: HighlightTier,
+ exts: []const []const u8,
+ language: *const fn () callconv(.c) *const ts.Language,
+ query_src: []const u8,
+ compiled_query: ?*ts.Query = null,
+};
+
+var highlight_specs = if (syntax_minimal_grammars) [_]HighlightSpec{
+ .{ .tier = .minimal, .exts = &[_][]const u8{ ".c", ".h" }, .language = tree_sitter_c, .query_src = ts_queries.c_highlights },
+ .{ .tier = .minimal, .exts = &[_][]const u8{ ".cpp", ".cc", ".cxx", ".hpp", ".hh", ".hxx" }, .language = tree_sitter_cpp, .query_src = ts_queries.cpp_highlights },
+ .{ .tier = .minimal, .exts = &[_][]const u8{ ".zig", ".zon" }, .language = tree_sitter_zig, .query_src = ts_queries.zig_highlights },
+} else [_]HighlightSpec{};
+
+var full_highlight_specs = if (syntax_full_grammars) [_]HighlightSpec{
+ .{ .tier = .full, .exts = &[_][]const u8{ ".adb", ".ads", ".ada" }, .language = tree_sitter_ada, .query_src = ts_queries.ada_highlights },
+ .{ .tier = .full, .exts = &[_][]const u8{ ".sh", ".bash", ".zsh" }, .language = tree_sitter_bash, .query_src = ts_queries.bash_highlights },
+ .{ .tier = .full, .exts = &[_][]const u8{ ".cs", ".csx" }, .language = tree_sitter_c_sharp, .query_src = ts_queries.c_sharp_highlights },
+ .{ .tier = .full, .exts = &[_][]const u8{ ".clj", ".cljs", ".cljc", ".edn" }, .language = tree_sitter_clojure, .query_src = ts_queries.clojure_highlights },
+ .{ .tier = .full, .exts = &[_][]const u8{".css"}, .language = tree_sitter_css, .query_src = ts_queries.css_highlights },
+ .{ .tier = .full, .exts = &[_][]const u8{ ".ex", ".exs" }, .language = tree_sitter_elixir, .query_src = ts_queries.elixir_highlights },
+ .{ .tier = .full, .exts = &[_][]const u8{ ".erl", ".hrl" }, .language = tree_sitter_erlang, .query_src = ts_queries.erlang_highlights },
+ .{ .tier = .full, .exts = &[_][]const u8{ ".f", ".for", ".ftn", ".f90", ".f95", ".f03", ".f08" }, .language = tree_sitter_fortran, .query_src = ts_queries.fortran_highlights },
+ .{ .tier = .full, .exts = &[_][]const u8{".go"}, .language = tree_sitter_go, .query_src = ts_queries.go_highlights },
+ .{ .tier = .full, .exts = &[_][]const u8{ ".hs", ".lhs" }, .language = tree_sitter_haskell, .query_src = ts_queries.haskell_highlights },
+ .{ .tier = .full, .exts = &[_][]const u8{ ".html", ".htm" }, .language = tree_sitter_html, .query_src = ts_queries.html_highlights },
+ .{ .tier = .full, .exts = &[_][]const u8{".java"}, .language = tree_sitter_java, .query_src = ts_queries.java_highlights },
+ .{ .tier = .full, .exts = &[_][]const u8{ ".js", ".jsx", ".mjs", ".cjs" }, .language = tree_sitter_javascript, .query_src = ts_queries.javascript_highlights },
+ .{ .tier = .full, .exts = &[_][]const u8{".json"}, .language = tree_sitter_json, .query_src = ts_queries.json_highlights },
+ .{ .tier = .full, .exts = &[_][]const u8{ ".kt", ".kts" }, .language = tree_sitter_kotlin, .query_src = ts_queries.kotlin_highlights },
+ .{ .tier = .full, .exts = &[_][]const u8{ ".ml", ".mli" }, .language = tree_sitter_ocaml, .query_src = ts_queries.ocaml_highlights },
+ .{ .tier = .full, .exts = &[_][]const u8{ ".pas", ".pp", ".p" }, .language = tree_sitter_pascal, .query_src = ts_queries.pascal_highlights },
+ .{ .tier = .full, .exts = &[_][]const u8{ ".php", ".phtml", ".php3", ".php4", ".php5" }, .language = tree_sitter_php, .query_src = ts_queries.php_highlights },
+ .{ .tier = .full, .exts = &[_][]const u8{ ".ps1", ".psm1", ".psd1" }, .language = tree_sitter_powershell, .query_src = ts_queries.powershell_highlights },
+ .{ .tier = .full, .exts = &[_][]const u8{ ".py", ".pyw" }, .language = tree_sitter_python, .query_src = ts_queries.python_highlights },
+ .{ .tier = .full, .exts = &[_][]const u8{ ".rb", ".rake" }, .language = tree_sitter_ruby, .query_src = ts_queries.ruby_highlights },
+ .{ .tier = .full, .exts = &[_][]const u8{".rs"}, .language = tree_sitter_rust, .query_src = ts_queries.rust_highlights },
+ .{ .tier = .full, .exts = &[_][]const u8{ ".scala", ".sc" }, .language = tree_sitter_scala, .query_src = ts_queries.scala_highlights },
+} else [_]HighlightSpec{};
+
+fn tierEnabled(tier: HighlightTier) bool {
+ return switch (tier) {
+ .minimal => syntax_minimal_grammars,
+ .full => syntax_full_grammars,
+ };
+}
+
+fn specMatchesExt(spec: HighlightSpec, ext: []const u8) bool {
+ for (spec.exts) |choice| {
+ if (std.ascii.eqlIgnoreCase(ext, choice)) return true;
+ }
+ return false;
+}
+
+fn ensureHighlightLanguage(spec: *HighlightSpec) !HighlightLanguage {
+ const lang = spec.language();
+ if (spec.compiled_query) |query| return .{ .lang = lang, .query = query };
+ var error_offset: u32 = 0;
+ const query = try ts.Query.create(lang, spec.query_src, &error_offset);
+ spec.compiled_query = query;
+ return .{ .lang = lang, .query = query };
+}
+
+fn highlightLanguageForExt(ext: []const u8) !?HighlightLanguage {
+ for (&highlight_specs) |*spec| {
+ if (!tierEnabled(spec.tier)) continue;
+ if (!specMatchesExt(spec.*, ext)) continue;
+ return try ensureHighlightLanguage(spec);
+ }
+ for (&full_highlight_specs) |*spec| {
+ if (!tierEnabled(spec.tier)) continue;
+ if (!specMatchesExt(spec.*, ext)) continue;
+ return try ensureHighlightLanguage(spec);
+ }
+ return null;
+}
+
+pub fn compileStartupHighlightQueries() void {
+ if (!syntax_highlighting) return;
+ for (&highlight_specs) |*spec| {
+ if (spec.tier != .minimal) continue;
+ if (!tierEnabled(spec.tier)) continue;
+ _ = ensureHighlightLanguage(spec) catch {};
+ }
+}
+
pub fn synStyle(s: Syn) ?SynStyle {
return switch (s) {
.none => null,
@@ -154,7 +248,6 @@ fn synFor(name: []const u8) Syn {
.{ "boolean", .number },
.{ "keyword", .keyword },
.{ "include", .keyword },
- .{ "operator", .keyword },
.{ "conditional", .keyword },
.{ "repeat", .keyword },
}) |m| {
@@ -163,115 +256,35 @@ fn synFor(name: []const u8) Syn {
return .none;
}
-fn extIs(ext: []const u8, comptime choices: anytype) bool {
- inline for (choices) |choice| {
- if (std.ascii.eqlIgnoreCase(ext, choice)) return true;
- }
- return false;
+pub fn highlightFile(gpa: std.mem.Allocator, path: []const u8, content: []const u8) ![]u8 {
+ return highlightFileRange(gpa, path, content, 0, content.len);
}
-pub fn highlightFile(gpa: std.mem.Allocator, path: []const u8, content: []const u8) ![]u8 {
+pub fn highlightFileRange(gpa: std.mem.Allocator, path: []const u8, content: []const u8, start_byte_raw: usize, end_byte_raw: usize) ![]u8 {
if (!syntax_highlighting) return &.{};
const ext = std.fs.path.extension(path);
- const selected = blk: {
- const Selected = struct {
- lang: *const ts.Language,
- query_src: []const u8,
- };
+ const selected = (highlightLanguageForExt(ext) catch return &.{}) orelse return &.{};
- 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);
+ const start_byte = @min(start_byte_raw, content.len);
+ const end_byte = @max(start_byte, @min(end_byte_raw, content.len));
+ const source = content[start_byte..end_byte];
+ const styles = try gpa.alloc(u8, source.len);
errdefer gpa.free(styles);
@memset(styles, 0);
const parser = ts.Parser.create();
defer parser.destroy();
parser.setLanguage(selected.lang) catch return styles;
- const tree = parser.parseString(content, null) orelse return styles;
+ const tree = parser.parseString(source, null) orelse return styles;
defer tree.destroy();
- var error_offset: u32 = 0;
- 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();
- cursor.exec(query, tree.rootNode());
+ cursor.exec(selected.query, tree.rootNode());
while (cursor.nextMatch()) |match| {
for (match.captures) |cap| {
- const syn = synFor(query.captureNameForId(cap.index) orelse "");
+ const syn = synFor(selected.query.captureNameForId(cap.index) orelse "");
if (syn == .none) continue;
var b: usize = cap.node.startByte();
const end = @min(@as(usize, cap.node.endByte()), styles.len);