Initial release
This commit is contained in:
parent
84a25cc089
commit
395374409c
10 changed files with 1301 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/.cache/
|
||||||
|
/zig-cache/
|
||||||
|
/zig-out/
|
99
build.zig
Normal file
99
build.zig
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
const tree_sitter_dep = b.dependency("tree-sitter", .{ .target = target, .optimize = optimize });
|
||||||
|
const clap_dep = b.dependency("clap", .{ .target = target, .optimize = optimize });
|
||||||
|
const themes_dep = b.dependency("themes", .{});
|
||||||
|
|
||||||
|
const syntax_mod = b.createModule(.{
|
||||||
|
.root_source_file = .{ .path = "src/syntax.zig" },
|
||||||
|
.imports = &.{
|
||||||
|
.{ .name = "tree-sitter", .module = tree_sitter_dep.module("tree-sitter") },
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-agda/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-bash/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-c-sharp/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-c/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-cpp/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-css/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-diff/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-dockerfile/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-git-rebase/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-gitcommit/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-go/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-fish/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-haskell/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-html/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-java/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-javascript/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-jsdoc/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-json/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-lua/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-make/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-markdown/tree-sitter-markdown/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-markdown/tree-sitter-markdown-inline/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-nasm/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-ninja/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-nix/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-ocaml/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-openscad/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-php/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-python/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-purescript/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-regex/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-ruby/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-rust/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-ssh-config/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-scala/queries/scala/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-scheme/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-toml/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-typescript/queries/highlights.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-zig/queries/highlights.scm"),
|
||||||
|
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-cpp/queries/injections.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-gitcommit/queries/injections.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-html/queries/injections.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-javascript/queries/injections.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-lua/queries/injections.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-markdown/tree-sitter-markdown-inline/queries/injections.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-markdown/tree-sitter-markdown/queries/injections.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-nasm/queries/injections.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-nix/queries/injections.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-openscad/queries/injections.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-php/queries/injections.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-purescript/queries/injections.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-purescript/vim_queries/injections.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-rust/queries/injections.scm"),
|
||||||
|
file_module(b, tree_sitter_dep, "tree-sitter-zig/queries/injections.scm"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const exe = b.addExecutable(.{
|
||||||
|
.name = "zat",
|
||||||
|
.root_source_file = .{ .path = "src/main.zig" },
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
exe.root_module.addImport("syntax", syntax_mod);
|
||||||
|
exe.root_module.addImport("theme", themes_dep.module("theme"));
|
||||||
|
exe.root_module.addImport("themes", themes_dep.module("themes"));
|
||||||
|
exe.root_module.addImport("clap", clap_dep.module("clap"));
|
||||||
|
b.installArtifact(exe);
|
||||||
|
|
||||||
|
const run_cmd = b.addRunArtifact(exe);
|
||||||
|
run_cmd.step.dependOn(b.getInstallStep());
|
||||||
|
if (b.args) |args| run_cmd.addArgs(args);
|
||||||
|
const run_step = b.step("run", "Run the app");
|
||||||
|
run_step.dependOn(&run_cmd.step);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file_module(b: *std.Build, dep: *std.Build.Dependency, comptime sub_path: []const u8) std.Build.Module.Import {
|
||||||
|
return .{
|
||||||
|
.name = sub_path,
|
||||||
|
.module = b.createModule(.{
|
||||||
|
.root_source_file = dep.path(sub_path),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
23
build.zig.zon
Normal file
23
build.zig.zon
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
.{
|
||||||
|
.name = "zat",
|
||||||
|
.version = "0.0.1",
|
||||||
|
.dependencies = .{
|
||||||
|
.@"tree-sitter" = .{
|
||||||
|
.url = "https://github.com/neurocyte/tree-sitter/releases/download/master-1ab391b43aa06e8648108f4d194e769a55a187be/source.tar.gz",
|
||||||
|
.hash = "12209d0d502f6ee33386c3369d87936b919d56f0302bc030e6deee5d806451a4ca34",
|
||||||
|
},
|
||||||
|
.clap = .{
|
||||||
|
.url = "https://github.com/Hejsil/zig-clap/archive/9c23bcb5aebe0c2542b4de4472f60959974e2222.tar.gz",
|
||||||
|
.hash = "12209e829da9d7d0bc089e4e0cbc07bb882f6192cd583277277da34df53cd05b8f2a",
|
||||||
|
},
|
||||||
|
.themes = .{
|
||||||
|
.url = "https://github.com/neurocyte/flow-themes/releases/download/master-9ee6d7bc28256202aa7b3b20555bf480715c4e5c/flow-themes.tar.gz",
|
||||||
|
.hash = "1220cd21ee1f3e194f1cca5d52175c20d2c663a53eadaa9057997e72aa828f5d3864",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.paths = .{
|
||||||
|
"build.zig",
|
||||||
|
"build.zig.zon",
|
||||||
|
"src",
|
||||||
|
},
|
||||||
|
}
|
2
src/ansi-term.zig
Normal file
2
src/ansi-term.zig
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub const style = @import("ansi-term/style.zig");
|
||||||
|
pub const format = @import("ansi-term/format.zig");
|
304
src/ansi-term/format.zig
Normal file
304
src/ansi-term/format.zig
Normal file
|
@ -0,0 +1,304 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const fixedBufferStream = std.io.fixedBufferStream;
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
const style = @import("style.zig");
|
||||||
|
const Style = style.Style;
|
||||||
|
const FontStyle = style.FontStyle;
|
||||||
|
const Color = style.Color;
|
||||||
|
|
||||||
|
const esc = "\x1B";
|
||||||
|
const csi = esc ++ "[";
|
||||||
|
|
||||||
|
const reset = csi ++ "0m";
|
||||||
|
|
||||||
|
const font_style_codes = std.ComptimeStringMap([]const u8, .{
|
||||||
|
.{ "bold", "1" },
|
||||||
|
.{ "dim", "2" },
|
||||||
|
.{ "italic", "3" },
|
||||||
|
.{ "underline", "4" },
|
||||||
|
.{ "slowblink", "5" },
|
||||||
|
.{ "rapidblink", "6" },
|
||||||
|
.{ "reverse", "7" },
|
||||||
|
.{ "hidden", "8" },
|
||||||
|
.{ "crossedout", "9" },
|
||||||
|
.{ "fraktur", "20" },
|
||||||
|
.{ "overline", "53" },
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Update the current style of the ANSI terminal
|
||||||
|
///
|
||||||
|
/// Optionally accepts the previous style active on the
|
||||||
|
/// terminal. Using this information, the function will update only
|
||||||
|
/// the attributes which are new in order to minimize the amount
|
||||||
|
/// written.
|
||||||
|
///
|
||||||
|
/// Tries to use as little bytes as necessary. Use this function if
|
||||||
|
/// you want to optimize for smallest amount of transmitted bytes
|
||||||
|
/// instead of computation speed.
|
||||||
|
pub fn updateStyle(writer: anytype, new: Style, old: ?Style) !void {
|
||||||
|
if (old) |sty| if (new.eql(sty)) return;
|
||||||
|
if (new.isDefault()) return try resetStyle(writer);
|
||||||
|
|
||||||
|
// A reset is required if the new font style has attributes not
|
||||||
|
// present in the old style or if the old style is not known
|
||||||
|
const reset_required = if (old) |sty| !sty.font_style.subsetOf(new.font_style) else true;
|
||||||
|
if (reset_required) try resetStyle(writer);
|
||||||
|
|
||||||
|
// Start the escape sequence
|
||||||
|
try writer.writeAll(csi);
|
||||||
|
var written_something = false;
|
||||||
|
|
||||||
|
// Font styles
|
||||||
|
const write_styles = if (reset_required) new.font_style else new.font_style.without(old.?.font_style);
|
||||||
|
inline for (std.meta.fields(FontStyle)) |field| {
|
||||||
|
if (@field(write_styles, field.name)) {
|
||||||
|
const code = font_style_codes.get(field.name).?;
|
||||||
|
if (written_something) {
|
||||||
|
try writer.writeAll(";");
|
||||||
|
} else {
|
||||||
|
written_something = true;
|
||||||
|
}
|
||||||
|
try writer.writeAll(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Foreground color
|
||||||
|
if (reset_required and new.foreground != .Default or old != null and !old.?.foreground.eql(new.foreground)) {
|
||||||
|
if (written_something) {
|
||||||
|
try writer.writeAll(";");
|
||||||
|
} else {
|
||||||
|
written_something = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (new.foreground) {
|
||||||
|
.Default => try writer.writeAll("39"),
|
||||||
|
.Black => try writer.writeAll("30"),
|
||||||
|
.Red => try writer.writeAll("31"),
|
||||||
|
.Green => try writer.writeAll("32"),
|
||||||
|
.Yellow => try writer.writeAll("33"),
|
||||||
|
.Blue => try writer.writeAll("34"),
|
||||||
|
.Magenta => try writer.writeAll("35"),
|
||||||
|
.Cyan => try writer.writeAll("36"),
|
||||||
|
.White => try writer.writeAll("37"),
|
||||||
|
.Fixed => |fixed| try writer.print("38;5;{}", .{fixed}),
|
||||||
|
.Grey => |grey| try writer.print("38;2;{};{};{}", .{ grey, grey, grey }),
|
||||||
|
.RGB => |rgb| try writer.print("38;2;{};{};{}", .{ rgb.r, rgb.g, rgb.b }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Background color
|
||||||
|
if (reset_required and new.background != .Default or old != null and !old.?.background.eql(new.background)) {
|
||||||
|
if (written_something) {
|
||||||
|
try writer.writeAll(";");
|
||||||
|
} else {
|
||||||
|
written_something = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (new.background) {
|
||||||
|
.Default => try writer.writeAll("49"),
|
||||||
|
.Black => try writer.writeAll("40"),
|
||||||
|
.Red => try writer.writeAll("41"),
|
||||||
|
.Green => try writer.writeAll("42"),
|
||||||
|
.Yellow => try writer.writeAll("43"),
|
||||||
|
.Blue => try writer.writeAll("44"),
|
||||||
|
.Magenta => try writer.writeAll("45"),
|
||||||
|
.Cyan => try writer.writeAll("46"),
|
||||||
|
.White => try writer.writeAll("47"),
|
||||||
|
.Fixed => |fixed| try writer.print("48;5;{}", .{fixed}),
|
||||||
|
.Grey => |grey| try writer.print("48;2;{};{};{}", .{ grey, grey, grey }),
|
||||||
|
.RGB => |rgb| try writer.print("48;2;{};{};{}", .{ rgb.r, rgb.g, rgb.b }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// End the escape sequence
|
||||||
|
try writer.writeAll("m");
|
||||||
|
}
|
||||||
|
|
||||||
|
test "same style default, no update" {
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
var fixed_buf_stream = fixedBufferStream(&buf);
|
||||||
|
|
||||||
|
try updateStyle(fixed_buf_stream.writer(), Style{}, Style{});
|
||||||
|
|
||||||
|
const expected = "";
|
||||||
|
const actual = fixed_buf_stream.getWritten();
|
||||||
|
|
||||||
|
try testing.expectEqualSlices(u8, expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "same style non-default, no update" {
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
var fixed_buf_stream = fixedBufferStream(&buf);
|
||||||
|
|
||||||
|
const sty = Style{
|
||||||
|
.foreground = Color.Green,
|
||||||
|
};
|
||||||
|
try updateStyle(fixed_buf_stream.writer(), sty, sty);
|
||||||
|
|
||||||
|
const expected = "";
|
||||||
|
const actual = fixed_buf_stream.getWritten();
|
||||||
|
|
||||||
|
try testing.expectEqualSlices(u8, expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "reset to default, old null" {
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
var fixed_buf_stream = fixedBufferStream(&buf);
|
||||||
|
|
||||||
|
try updateStyle(fixed_buf_stream.writer(), Style{}, null);
|
||||||
|
|
||||||
|
const expected = "\x1B[0m";
|
||||||
|
const actual = fixed_buf_stream.getWritten();
|
||||||
|
|
||||||
|
try testing.expectEqualSlices(u8, expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "reset to default, old non-null" {
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
var fixed_buf_stream = fixedBufferStream(&buf);
|
||||||
|
|
||||||
|
try updateStyle(fixed_buf_stream.writer(), Style{}, Style{
|
||||||
|
.font_style = FontStyle.bold,
|
||||||
|
});
|
||||||
|
|
||||||
|
const expected = "\x1B[0m";
|
||||||
|
const actual = fixed_buf_stream.getWritten();
|
||||||
|
|
||||||
|
try testing.expectEqualSlices(u8, expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "bold style" {
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
var fixed_buf_stream = fixedBufferStream(&buf);
|
||||||
|
|
||||||
|
try updateStyle(fixed_buf_stream.writer(), Style{
|
||||||
|
.font_style = FontStyle.bold,
|
||||||
|
}, Style{});
|
||||||
|
|
||||||
|
const expected = "\x1B[1m";
|
||||||
|
const actual = fixed_buf_stream.getWritten();
|
||||||
|
|
||||||
|
try testing.expectEqualSlices(u8, expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "add bold style" {
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
var fixed_buf_stream = fixedBufferStream(&buf);
|
||||||
|
|
||||||
|
try updateStyle(fixed_buf_stream.writer(), Style{
|
||||||
|
.font_style = FontStyle{ .bold = true, .italic = true },
|
||||||
|
}, Style{
|
||||||
|
.font_style = FontStyle.italic,
|
||||||
|
});
|
||||||
|
|
||||||
|
const expected = "\x1B[1m";
|
||||||
|
const actual = fixed_buf_stream.getWritten();
|
||||||
|
|
||||||
|
try testing.expectEqualSlices(u8, expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "reset required font style" {
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
var fixed_buf_stream = fixedBufferStream(&buf);
|
||||||
|
|
||||||
|
try updateStyle(fixed_buf_stream.writer(), Style{
|
||||||
|
.font_style = FontStyle.bold,
|
||||||
|
}, Style{
|
||||||
|
.font_style = FontStyle{ .bold = true, .underline = true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const expected = "\x1B[0m\x1B[1m";
|
||||||
|
const actual = fixed_buf_stream.getWritten();
|
||||||
|
|
||||||
|
try testing.expectEqualSlices(u8, expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "reset required color style" {
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
var fixed_buf_stream = fixedBufferStream(&buf);
|
||||||
|
|
||||||
|
try updateStyle(fixed_buf_stream.writer(), Style{
|
||||||
|
.foreground = Color.Red,
|
||||||
|
}, null);
|
||||||
|
|
||||||
|
const expected = "\x1B[0m\x1B[31m";
|
||||||
|
const actual = fixed_buf_stream.getWritten();
|
||||||
|
|
||||||
|
try testing.expectEqualSlices(u8, expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "no reset required color style" {
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
var fixed_buf_stream = fixedBufferStream(&buf);
|
||||||
|
|
||||||
|
try updateStyle(fixed_buf_stream.writer(), Style{
|
||||||
|
.foreground = Color.Red,
|
||||||
|
}, Style{});
|
||||||
|
|
||||||
|
const expected = "\x1B[31m";
|
||||||
|
const actual = fixed_buf_stream.getWritten();
|
||||||
|
|
||||||
|
try testing.expectEqualSlices(u8, expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "no reset required add color style" {
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
var fixed_buf_stream = fixedBufferStream(&buf);
|
||||||
|
|
||||||
|
try updateStyle(fixed_buf_stream.writer(), Style{
|
||||||
|
.foreground = Color.Red,
|
||||||
|
.background = Color.Magenta,
|
||||||
|
}, Style{
|
||||||
|
.background = Color.Magenta,
|
||||||
|
});
|
||||||
|
|
||||||
|
const expected = "\x1B[31m";
|
||||||
|
const actual = fixed_buf_stream.getWritten();
|
||||||
|
|
||||||
|
try testing.expectEqualSlices(u8, expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resetStyle(writer: anytype) !void {
|
||||||
|
try writer.writeAll(reset);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "reset style" {
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
var fixed_buf_stream = fixedBufferStream(&buf);
|
||||||
|
|
||||||
|
try resetStyle(fixed_buf_stream.writer());
|
||||||
|
|
||||||
|
const expected = "\x1B[0m";
|
||||||
|
const actual = fixed_buf_stream.getWritten();
|
||||||
|
|
||||||
|
try testing.expectEqualSlices(u8, expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Grey foreground color" {
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
var fixed_buf_stream = fixedBufferStream(&buf);
|
||||||
|
var new_style = Style{};
|
||||||
|
new_style.foreground = Color{ .Grey = 1 };
|
||||||
|
|
||||||
|
try updateStyle(fixed_buf_stream.writer(), new_style, Style{});
|
||||||
|
|
||||||
|
const expected = "\x1B[38;2;1;1;1m";
|
||||||
|
const actual = fixed_buf_stream.getWritten();
|
||||||
|
|
||||||
|
try testing.expectEqualSlices(u8, expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Grey background color" {
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
var fixed_buf_stream = fixedBufferStream(&buf);
|
||||||
|
var new_style = Style{};
|
||||||
|
new_style.background = Color{ .Grey = 1 };
|
||||||
|
|
||||||
|
try updateStyle(fixed_buf_stream.writer(), new_style, Style{});
|
||||||
|
|
||||||
|
const expected = "\x1B[48;2;1;1;1m";
|
||||||
|
const actual = fixed_buf_stream.getWritten();
|
||||||
|
|
||||||
|
try testing.expectEqualSlices(u8, expected, actual);
|
||||||
|
}
|
220
src/ansi-term/style.zig
Normal file
220
src/ansi-term/style.zig
Normal file
|
@ -0,0 +1,220 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const meta = std.meta;
|
||||||
|
const expect = std.testing.expect;
|
||||||
|
const expectEqual = std.testing.expectEqual;
|
||||||
|
|
||||||
|
pub const ColorRGB = struct {
|
||||||
|
r: u8,
|
||||||
|
g: u8,
|
||||||
|
b: u8,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn eql(self: Self, other: Self) bool {
|
||||||
|
return meta.eql(self, other);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Color = union(enum) {
|
||||||
|
Default,
|
||||||
|
Black,
|
||||||
|
Red,
|
||||||
|
Green,
|
||||||
|
Yellow,
|
||||||
|
Blue,
|
||||||
|
Magenta,
|
||||||
|
Cyan,
|
||||||
|
White,
|
||||||
|
Fixed: u8,
|
||||||
|
Grey: u8,
|
||||||
|
RGB: ColorRGB,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn eql(self: Self, other: Self) bool {
|
||||||
|
return meta.eql(self, other);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const FontStyle = packed struct {
|
||||||
|
bold: bool = false,
|
||||||
|
dim: bool = false,
|
||||||
|
italic: bool = false,
|
||||||
|
underline: bool = false,
|
||||||
|
slowblink: bool = false,
|
||||||
|
rapidblink: bool = false,
|
||||||
|
reverse: bool = false,
|
||||||
|
hidden: bool = false,
|
||||||
|
crossedout: bool = false,
|
||||||
|
fraktur: bool = false,
|
||||||
|
overline: bool = false,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub const bold = Self{
|
||||||
|
.bold = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const dim = Self{
|
||||||
|
.dim = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const italic = Self{
|
||||||
|
.italic = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const underline = Self{
|
||||||
|
.underline = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const slowblink = Self{
|
||||||
|
.slowblink = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const rapidblink = Self{
|
||||||
|
.rapidblink = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const reverse = Self{
|
||||||
|
.reverse = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const hidden = Self{
|
||||||
|
.hidden = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const crossedout = Self{
|
||||||
|
.crossedout = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const fraktur = Self{
|
||||||
|
.fraktur = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const overline = Self{
|
||||||
|
.overline = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn toU11(self: Self) u11 {
|
||||||
|
return @bitCast(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fromU11(bits: u11) Self {
|
||||||
|
return @bitCast(bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true iff this font style contains no attributes
|
||||||
|
pub fn isDefault(self: Self) bool {
|
||||||
|
return self.toU11() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true iff these font styles contain exactly the same
|
||||||
|
/// attributes
|
||||||
|
pub fn eql(self: Self, other: Self) bool {
|
||||||
|
return self.toU11() == other.toU11();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true iff self is a subset of the attributes of
|
||||||
|
/// other, i.e. all attributes of self are at least present in
|
||||||
|
/// other as well
|
||||||
|
pub fn subsetOf(self: Self, other: Self) bool {
|
||||||
|
return self.toU11() & other.toU11() == self.toU11();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns this font style with all attributes removed that are
|
||||||
|
/// contained in other
|
||||||
|
pub fn without(self: Self, other: Self) Self {
|
||||||
|
return fromU11(self.toU11() & ~other.toU11());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test "FontStyle bits" {
|
||||||
|
try expectEqual(@as(u11, 0), (FontStyle{}).toU11());
|
||||||
|
try expectEqual(@as(u11, 1), (FontStyle.bold).toU11());
|
||||||
|
try expectEqual(@as(u11, 1 << 2), (FontStyle.italic).toU11());
|
||||||
|
try expectEqual(@as(u11, 1 << 2) | 1, (FontStyle{ .bold = true, .italic = true }).toU11());
|
||||||
|
try expectEqual(FontStyle{}, FontStyle.fromU11((FontStyle{}).toU11()));
|
||||||
|
try expectEqual(FontStyle.bold, FontStyle.fromU11((FontStyle.bold).toU11()));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "FontStyle subsetOf" {
|
||||||
|
const default = FontStyle{};
|
||||||
|
const bold = FontStyle.bold;
|
||||||
|
const italic = FontStyle.italic;
|
||||||
|
const bold_and_italic = FontStyle{ .bold = true, .italic = true };
|
||||||
|
|
||||||
|
try expect(default.subsetOf(default));
|
||||||
|
try expect(default.subsetOf(bold));
|
||||||
|
try expect(bold.subsetOf(bold));
|
||||||
|
try expect(!bold.subsetOf(default));
|
||||||
|
try expect(!bold.subsetOf(italic));
|
||||||
|
try expect(default.subsetOf(bold_and_italic));
|
||||||
|
try expect(bold.subsetOf(bold_and_italic));
|
||||||
|
try expect(italic.subsetOf(bold_and_italic));
|
||||||
|
try expect(bold_and_italic.subsetOf(bold_and_italic));
|
||||||
|
try expect(!bold_and_italic.subsetOf(bold));
|
||||||
|
try expect(!bold_and_italic.subsetOf(italic));
|
||||||
|
try expect(!bold_and_italic.subsetOf(default));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "FontStyle without" {
|
||||||
|
const default = FontStyle{};
|
||||||
|
const bold = FontStyle.bold;
|
||||||
|
const italic = FontStyle.italic;
|
||||||
|
const bold_and_italic = FontStyle{ .bold = true, .italic = true };
|
||||||
|
|
||||||
|
try expectEqual(default, default.without(default));
|
||||||
|
try expectEqual(bold, bold.without(default));
|
||||||
|
try expectEqual(default, bold.without(bold));
|
||||||
|
try expectEqual(bold, bold.without(italic));
|
||||||
|
try expectEqual(bold, bold_and_italic.without(italic));
|
||||||
|
try expectEqual(italic, bold_and_italic.without(bold));
|
||||||
|
try expectEqual(default, bold_and_italic.without(bold_and_italic));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Style = struct {
|
||||||
|
foreground: Color = .Default,
|
||||||
|
background: Color = .Default,
|
||||||
|
font_style: FontStyle = FontStyle{},
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
/// Returns true iff this style equals the other style in
|
||||||
|
/// foreground color, background color and font style
|
||||||
|
pub fn eql(self: Self, other: Self) bool {
|
||||||
|
if (!self.font_style.eql(other.font_style))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!meta.eql(self.foreground, other.foreground))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return meta.eql(self.background, other.background);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true iff this style equals the default set of styles
|
||||||
|
pub fn isDefault(self: Self) bool {
|
||||||
|
return eql(self, Self{});
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub const parse = @import("parse_style.zig").parseStyle;
|
||||||
|
};
|
||||||
|
|
||||||
|
test "style equality" {
|
||||||
|
const a = Style{};
|
||||||
|
const b = Style{
|
||||||
|
.font_style = FontStyle.bold,
|
||||||
|
};
|
||||||
|
const c = Style{
|
||||||
|
.foreground = Color.Red,
|
||||||
|
};
|
||||||
|
|
||||||
|
try expect(a.isDefault());
|
||||||
|
|
||||||
|
try expect(a.eql(a));
|
||||||
|
try expect(b.eql(b));
|
||||||
|
try expect(c.eql(c));
|
||||||
|
|
||||||
|
try expect(!a.eql(b));
|
||||||
|
try expect(!b.eql(a));
|
||||||
|
try expect(!a.eql(c));
|
||||||
|
}
|
119
src/file_type.zig
Normal file
119
src/file_type.zig
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const ts = @import("tree-sitter");
|
||||||
|
pub const FileType = @This();
|
||||||
|
|
||||||
|
color: u24,
|
||||||
|
icon: []const u8,
|
||||||
|
name: []const u8,
|
||||||
|
lang_fn: LangFn,
|
||||||
|
extensions: []const []const u8,
|
||||||
|
highlights: [:0]const u8,
|
||||||
|
injections: ?[:0]const u8,
|
||||||
|
first_line_matches: ?FirstLineMatch = null,
|
||||||
|
comment: []const u8,
|
||||||
|
|
||||||
|
pub fn guess(file_path: ?[]const u8, content: []const u8) ?*const FileType {
|
||||||
|
if (guess_first_line(content)) |ft| return ft;
|
||||||
|
for (file_types) |*file_type|
|
||||||
|
if (file_path) |fp| if (match_file_type(file_type, fp))
|
||||||
|
return file_type;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn guess_first_line(content: []const u8) ?*const FileType {
|
||||||
|
const first_line = if (std.mem.indexOf(u8, content, "\n")) |pos| content[0..pos] else content;
|
||||||
|
for (file_types) |*file_type|
|
||||||
|
if (file_type.first_line_matches) |match|
|
||||||
|
if (match_first_line(match, first_line))
|
||||||
|
return file_type;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn match_first_line(match: FirstLineMatch, first_line: []const u8) bool {
|
||||||
|
if (match.prefix) |prefix|
|
||||||
|
if (prefix.len > first_line.len or !std.mem.eql(u8, first_line[0..prefix.len], prefix))
|
||||||
|
return false;
|
||||||
|
if (match.content) |content|
|
||||||
|
if (std.mem.indexOf(u8, first_line, content)) |_| {} else return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn match_file_type(file_type: *const FileType, file_path: []const u8) bool {
|
||||||
|
const basename = std.fs.path.basename(file_path);
|
||||||
|
const extension = std.fs.path.extension(file_path);
|
||||||
|
return for (file_type.extensions) |ext| {
|
||||||
|
if (ext.len == basename.len and std.mem.eql(u8, ext, basename))
|
||||||
|
return true;
|
||||||
|
if (extension.len > 0 and ext.len == extension.len - 1 and std.mem.eql(u8, ext, extension[1..]))
|
||||||
|
return true;
|
||||||
|
} else false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Parser(comptime lang: []const u8) LangFn {
|
||||||
|
return get_parser(lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_parser(comptime lang: []const u8) LangFn {
|
||||||
|
const language_name = ft_func_name(lang);
|
||||||
|
return @extern(?LangFn, .{ .name = "tree_sitter_" ++ language_name }) orelse @compileError(std.fmt.comptimePrint("Cannot find extern tree_sitter_{s}", .{language_name}));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ft_func_name(comptime lang: []const u8) []const u8 {
|
||||||
|
var func_name: [lang.len]u8 = undefined;
|
||||||
|
for (lang, 0..) |c, i|
|
||||||
|
func_name[i] = if (c == '-') '_' else c;
|
||||||
|
return &func_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LangFn = *const fn () callconv(.C) ?*const ts.Language;
|
||||||
|
|
||||||
|
const FirstLineMatch = struct {
|
||||||
|
prefix: ?[]const u8 = null,
|
||||||
|
content: ?[]const u8 = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const FileTypeOptions = struct {
|
||||||
|
extensions: []const []const u8 = &[_][]const u8{},
|
||||||
|
comment: []const u8,
|
||||||
|
icon: ?[]const u8 = null,
|
||||||
|
color: ?u24 = null,
|
||||||
|
highlights: ?[:0]const u8 = null,
|
||||||
|
injections: ?[:0]const u8 = null,
|
||||||
|
first_line_matches: ?FirstLineMatch = null,
|
||||||
|
parser: ?LangFn = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn DeclLang(comptime lang: []const u8, comptime args: FileTypeOptions) FileType {
|
||||||
|
return .{
|
||||||
|
.color = args.color orelse 0xffffff,
|
||||||
|
.icon = args.icon orelse "",
|
||||||
|
.name = lang,
|
||||||
|
.lang_fn = if (args.parser) |p| p else get_parser(lang),
|
||||||
|
.extensions = args.extensions,
|
||||||
|
.comment = args.comment,
|
||||||
|
.highlights = if (args.highlights) |h| h else @embedFile("tree-sitter-" ++ lang ++ "/queries/highlights.scm"),
|
||||||
|
.injections = args.injections,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const file_types = load_file_types(@import("file_types.zig"));
|
||||||
|
|
||||||
|
fn load_file_types(comptime Namespace: type) []FileType {
|
||||||
|
comptime switch (@typeInfo(Namespace)) {
|
||||||
|
.Struct => |info| {
|
||||||
|
var count = 0;
|
||||||
|
for (info.decls) |_| {
|
||||||
|
// @compileLog(decl.name, @TypeOf(@field(Namespace, decl.name)));
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
var cmds: [count]FileType = undefined;
|
||||||
|
var i = 0;
|
||||||
|
for (info.decls) |decl| {
|
||||||
|
cmds[i] = DeclLang(decl.name, @field(Namespace, decl.name));
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
return &cmds;
|
||||||
|
},
|
||||||
|
else => @compileError("expected tuple or struct type"),
|
||||||
|
};
|
||||||
|
}
|
262
src/file_types.zig
Normal file
262
src/file_types.zig
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
pub const agda = .{
|
||||||
|
.extensions = &[_][]const u8{"agda"},
|
||||||
|
.comment = "--",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const bash = .{
|
||||||
|
.color = 0x3e474a,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{ "sh", "bash" },
|
||||||
|
.comment = "#",
|
||||||
|
.first_line_matches = .{ .prefix = "#!", .content = "/bin/bash" },
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const c = .{
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{ "c", "h" },
|
||||||
|
.comment = "//",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const @"c-sharp" = .{
|
||||||
|
.color = 0x68217a,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{"cs"},
|
||||||
|
.comment = "//",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const conf = .{
|
||||||
|
.color = 0x000000,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{ "conf", "config" },
|
||||||
|
.highlights = fish.highlights,
|
||||||
|
.comment = "#",
|
||||||
|
.parser = fish.parser,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const cpp = .{
|
||||||
|
.color = 0x9c033a,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{ "cc", "cpp", "cxx", "hpp", "hxx", "h" },
|
||||||
|
.comment = "//",
|
||||||
|
.injections = @embedFile("tree-sitter-cpp/queries/injections.scm"),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const css = .{
|
||||||
|
.color = 0x3d8fc6,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{"css"},
|
||||||
|
.comment = "//",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const diff = .{
|
||||||
|
.extensions = &[_][]const u8{"diff"},
|
||||||
|
.comment = "#",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const dockerfile = .{
|
||||||
|
.color = 0x019bc6,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{ "Dockerfile", "dockerfile", "docker", "Containerfile", "container" },
|
||||||
|
.comment = "#",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const fish = .{
|
||||||
|
.extensions = &[_][]const u8{"fish"},
|
||||||
|
.comment = "#",
|
||||||
|
.parser = @import("file_type.zig").Parser("fish"),
|
||||||
|
.highlights = @embedFile("tree-sitter-fish/queries/highlights.scm"),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const @"git-rebase" = .{
|
||||||
|
.color = 0xf34f29,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{"git-rebase-todo"},
|
||||||
|
.comment = "#",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const gitcommit = .{
|
||||||
|
.color = 0xf34f29,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{"COMMIT_EDITMSG"},
|
||||||
|
.comment = "#",
|
||||||
|
.injections = @embedFile("tree-sitter-gitcommit/queries/injections.scm"),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const go = .{
|
||||||
|
.color = 0x00acd7,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{"go"},
|
||||||
|
.comment = "//",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const haskell = .{
|
||||||
|
.color = 0x5E5185,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{"hs"},
|
||||||
|
.comment = "--",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const html = .{
|
||||||
|
.color = 0xe54d26,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{"html"},
|
||||||
|
.comment = "<!--",
|
||||||
|
.injections = @embedFile("tree-sitter-html/queries/injections.scm"),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const java = .{
|
||||||
|
.color = 0xEA2D2E,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{"java"},
|
||||||
|
.comment = "//",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const javascript = .{
|
||||||
|
.color = 0xf0db4f,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{"js"},
|
||||||
|
.comment = "//",
|
||||||
|
.injections = @embedFile("tree-sitter-javascript/queries/injections.scm"),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const json = .{
|
||||||
|
.extensions = &[_][]const u8{"json"},
|
||||||
|
.comment = "//",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const lua = .{
|
||||||
|
.color = 0x000080,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{"lua"},
|
||||||
|
.comment = "--",
|
||||||
|
.injections = @embedFile("tree-sitter-lua/queries/injections.scm"),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const make = .{
|
||||||
|
.extensions = &[_][]const u8{ "makefile", "Makefile", "MAKEFILE", "GNUmakefile", "mk", "mak", "dsp" },
|
||||||
|
.comment = "#",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const markdown = .{
|
||||||
|
.color = 0x000000,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{"md"},
|
||||||
|
.comment = "<!--",
|
||||||
|
.highlights = @embedFile("tree-sitter-markdown/tree-sitter-markdown/queries/highlights.scm"),
|
||||||
|
.injections = @embedFile("tree-sitter-markdown/tree-sitter-markdown/queries/injections.scm"),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const @"markdown-inline" = .{
|
||||||
|
.color = 0x000000,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{},
|
||||||
|
.comment = "<!--",
|
||||||
|
.highlights = @embedFile("tree-sitter-markdown/tree-sitter-markdown-inline/queries/highlights.scm"),
|
||||||
|
.injections = @embedFile("tree-sitter-markdown/tree-sitter-markdown-inline/queries/injections.scm"),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const nasm = .{
|
||||||
|
.extensions = &[_][]const u8{ "asm", "nasm" },
|
||||||
|
.comment = "#",
|
||||||
|
.injections = @embedFile("tree-sitter-nasm/queries/injections.scm"),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ninja = .{
|
||||||
|
.extensions = &[_][]const u8{"ninja"},
|
||||||
|
.comment = "#",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const nix = .{
|
||||||
|
.color = 0x5277C3,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{"nix"},
|
||||||
|
.comment = "#",
|
||||||
|
.injections = @embedFile("tree-sitter-nix/queries/injections.scm"),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ocaml = .{
|
||||||
|
.color = 0xF18803,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{ "ml", "mli" },
|
||||||
|
.comment = "(*",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const openscad = .{
|
||||||
|
.color = 0x000000,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{"scad"},
|
||||||
|
.comment = "//",
|
||||||
|
.injections = @embedFile("tree-sitter-openscad/queries/injections.scm"),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const php = .{
|
||||||
|
.color = 0x6181b6,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{"php"},
|
||||||
|
.comment = "//",
|
||||||
|
.injections = @embedFile("tree-sitter-php/queries/injections.scm"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// pub const purescript = .{
|
||||||
|
// .extensions = &[_][]const u8{"purs"},
|
||||||
|
// .comment = "--",
|
||||||
|
// .injections = @embedFile("tree-sitter-purescript/queries/injections.scm"),
|
||||||
|
// };
|
||||||
|
|
||||||
|
pub const python = .{
|
||||||
|
.color = 0xffd845,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{"py"},
|
||||||
|
.comment = "#",
|
||||||
|
.first_line_matches = .{ .prefix = "#!", .content = "/bin/bash" },
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const regex = .{
|
||||||
|
.extensions = &[_][]const u8{},
|
||||||
|
.comment = "#",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ruby = .{
|
||||||
|
.color = 0xd91404,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{"rb"},
|
||||||
|
.comment = "#",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const rust = .{
|
||||||
|
.color = 0x000000,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{"rs"},
|
||||||
|
.comment = "//",
|
||||||
|
.injections = @embedFile("tree-sitter-rust/queries/injections.scm"),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const scheme = .{
|
||||||
|
.extensions = &[_][]const u8{ "scm", "ss" },
|
||||||
|
.comment = ";",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const @"ssh-config" = .{
|
||||||
|
.extensions = &[_][]const u8{".ssh/config"},
|
||||||
|
.comment = "#",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const toml = .{
|
||||||
|
.extensions = &[_][]const u8{"toml"},
|
||||||
|
.comment = "#",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const typescript = .{
|
||||||
|
.color = 0x007acc,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{ "ts", "tsx" },
|
||||||
|
.comment = "//",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const zig = .{
|
||||||
|
.color = 0xf7a41d,
|
||||||
|
.icon = "",
|
||||||
|
.extensions = &[_][]const u8{ "zig", "zon" },
|
||||||
|
.comment = "//",
|
||||||
|
.injections = @embedFile("tree-sitter-zig/queries/injections.scm"),
|
||||||
|
};
|
198
src/main.zig
Normal file
198
src/main.zig
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const clap = @import("clap");
|
||||||
|
const syntax = @import("syntax");
|
||||||
|
const Theme = @import("theme");
|
||||||
|
const themes = @import("themes");
|
||||||
|
const term = @import("ansi-term.zig");
|
||||||
|
|
||||||
|
const StyleCache = std.AutoHashMap(u32, ?Theme.Token);
|
||||||
|
var style_cache: StyleCache = undefined;
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
const params = comptime clap.parseParamsComptime(
|
||||||
|
\\-h, --help Display this help and exit.
|
||||||
|
\\<str>... File to open.
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
|
||||||
|
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{};
|
||||||
|
const a = gpa.allocator();
|
||||||
|
style_cache = StyleCache.init(a);
|
||||||
|
|
||||||
|
var diag = clap.Diagnostic{};
|
||||||
|
var res = clap.parse(clap.Help, ¶ms, clap.parsers.default, .{
|
||||||
|
.diagnostic = &diag,
|
||||||
|
.allocator = a,
|
||||||
|
}) catch |err| {
|
||||||
|
diag.report(std.io.getStdErr().writer(), err) catch {};
|
||||||
|
clap.help(std.io.getStdErr().writer(), clap.Help, ¶ms, .{}) catch {};
|
||||||
|
std.os.exit(1);
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
defer res.deinit();
|
||||||
|
|
||||||
|
if (res.args.help != 0)
|
||||||
|
return clap.help(std.io.getStdErr().writer(), clap.Help, ¶ms, .{});
|
||||||
|
|
||||||
|
const theme = get_theme_by_name("default") orelse unreachable;
|
||||||
|
|
||||||
|
const stdout_file = std.io.getStdOut().writer();
|
||||||
|
var bw = std.io.bufferedWriter(stdout_file);
|
||||||
|
const writer = bw.writer();
|
||||||
|
|
||||||
|
if (res.positionals.len > 0) {
|
||||||
|
for (res.positionals) |arg| {
|
||||||
|
const file = try std.fs.cwd().openFile(arg, .{ .mode = .read_only });
|
||||||
|
defer file.close();
|
||||||
|
const content = try file.readToEndAlloc(a, std.math.maxInt(u32));
|
||||||
|
defer a.free(content);
|
||||||
|
try render_file(a, writer, content, arg, &theme);
|
||||||
|
try bw.flush();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const content = try std.io.getStdIn().readToEndAlloc(a, std.math.maxInt(u32));
|
||||||
|
defer a.free(content);
|
||||||
|
try render_file(a, writer, content, "-", &theme);
|
||||||
|
}
|
||||||
|
try bw.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_file(a: std.mem.Allocator, writer: anytype, content: []const u8, file_path: []const u8, theme: *const Theme) !void {
|
||||||
|
const parser = try syntax.create_guess_file_type(a, content, file_path);
|
||||||
|
|
||||||
|
const Ctx = struct {
|
||||||
|
writer: @TypeOf(writer),
|
||||||
|
content: []const u8,
|
||||||
|
theme: *const Theme,
|
||||||
|
last_pos: usize = 0,
|
||||||
|
fn cb(ctx: *@This(), range: syntax.Range, scope: []const u8, id: u32, idx: usize) error{Stop}!void {
|
||||||
|
defer ctx.last_pos = range.end_byte;
|
||||||
|
|
||||||
|
if (idx > 0) return;
|
||||||
|
|
||||||
|
if (ctx.last_pos < range.start_byte)
|
||||||
|
ctx.writer.writeAll(ctx.content[ctx.last_pos..range.start_byte]) catch return error.Stop;
|
||||||
|
|
||||||
|
if (range.start_byte < ctx.last_pos) return;
|
||||||
|
|
||||||
|
const style_ = style_cache_lookup(ctx.theme, scope, id);
|
||||||
|
const style = if (style_) |sty| sty.style else {
|
||||||
|
ctx.writer.writeAll(ctx.content[range.start_byte..range.end_byte]) catch return error.Stop;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
set_ansi_style(ctx.writer, style) catch return error.Stop;
|
||||||
|
ctx.writer.writeAll(ctx.content[range.start_byte..range.end_byte]) catch return error.Stop;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var ctx: Ctx = .{ .writer = writer, .content = content, .theme = theme };
|
||||||
|
try parser.render(&ctx, Ctx.cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn style_cache_lookup(theme: *const Theme, scope: []const u8, id: u32) ?Theme.Token {
|
||||||
|
return if (style_cache.get(id)) |sty| ret: {
|
||||||
|
break :ret sty;
|
||||||
|
} else ret: {
|
||||||
|
const sty = find_scope_style(theme, scope) orelse null;
|
||||||
|
style_cache.put(id, sty) catch {};
|
||||||
|
break :ret sty;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_scope_style(theme: *const Theme, scope: []const u8) ?Theme.Token {
|
||||||
|
return if (find_scope_fallback(scope)) |tm_scope|
|
||||||
|
find_scope_style_nofallback(theme, tm_scope) orelse find_scope_style_nofallback(theme, scope)
|
||||||
|
else
|
||||||
|
find_scope_style_nofallback(theme, scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_scope_style_nofallback(theme: *const Theme, scope: []const u8) ?Theme.Token {
|
||||||
|
var idx = theme.tokens.len - 1;
|
||||||
|
var done = false;
|
||||||
|
while (!done) : (if (idx == 0) {
|
||||||
|
done = true;
|
||||||
|
} else {
|
||||||
|
idx -= 1;
|
||||||
|
}) {
|
||||||
|
const token = theme.tokens[idx];
|
||||||
|
const name = themes.scopes[token.id];
|
||||||
|
if (name.len > scope.len)
|
||||||
|
continue;
|
||||||
|
if (std.mem.eql(u8, name, scope[0..name.len]))
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_scope_fallback(scope: []const u8) ?[]const u8 {
|
||||||
|
for (fallbacks) |fallback| {
|
||||||
|
if (fallback.ts.len > scope.len)
|
||||||
|
continue;
|
||||||
|
if (std.mem.eql(u8, fallback.ts, scope[0..fallback.ts.len]))
|
||||||
|
return fallback.tm;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const FallBack = struct { ts: []const u8, tm: []const u8 };
|
||||||
|
pub const fallbacks: []const FallBack = &[_]FallBack{
|
||||||
|
.{ .ts = "namespace", .tm = "entity.name.namespace" },
|
||||||
|
.{ .ts = "type", .tm = "entity.name.type" },
|
||||||
|
.{ .ts = "type.defaultLibrary", .tm = "support.type" },
|
||||||
|
.{ .ts = "struct", .tm = "storage.type.struct" },
|
||||||
|
.{ .ts = "class", .tm = "entity.name.type.class" },
|
||||||
|
.{ .ts = "class.defaultLibrary", .tm = "support.class" },
|
||||||
|
.{ .ts = "interface", .tm = "entity.name.type.interface" },
|
||||||
|
.{ .ts = "enum", .tm = "entity.name.type.enum" },
|
||||||
|
.{ .ts = "function", .tm = "entity.name.function" },
|
||||||
|
.{ .ts = "function.defaultLibrary", .tm = "support.function" },
|
||||||
|
.{ .ts = "method", .tm = "entity.name.function.member" },
|
||||||
|
.{ .ts = "macro", .tm = "entity.name.function.macro" },
|
||||||
|
.{ .ts = "variable", .tm = "variable.other.readwrite , entity.name.variable" },
|
||||||
|
.{ .ts = "variable.readonly", .tm = "variable.other.constant" },
|
||||||
|
.{ .ts = "variable.readonly.defaultLibrary", .tm = "support.constant" },
|
||||||
|
.{ .ts = "parameter", .tm = "variable.parameter" },
|
||||||
|
.{ .ts = "property", .tm = "variable.other.property" },
|
||||||
|
.{ .ts = "property.readonly", .tm = "variable.other.constant.property" },
|
||||||
|
.{ .ts = "enumMember", .tm = "variable.other.enummember" },
|
||||||
|
.{ .ts = "event", .tm = "variable.other.event" },
|
||||||
|
|
||||||
|
// zig
|
||||||
|
.{ .ts = "attribute", .tm = "keyword" },
|
||||||
|
.{ .ts = "number", .tm = "constant.numeric" },
|
||||||
|
.{ .ts = "conditional", .tm = "keyword.control.conditional" },
|
||||||
|
.{ .ts = "operator", .tm = "keyword.operator" },
|
||||||
|
.{ .ts = "boolean", .tm = "keyword.constant.bool" },
|
||||||
|
.{ .ts = "string", .tm = "string.quoted" },
|
||||||
|
.{ .ts = "repeat", .tm = "keyword.control.flow" },
|
||||||
|
.{ .ts = "field", .tm = "variable" },
|
||||||
|
};
|
||||||
|
|
||||||
|
fn get_theme_by_name(name: []const u8) ?Theme {
|
||||||
|
for (themes.themes) |theme| {
|
||||||
|
if (std.mem.eql(u8, theme.name, name))
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_ansi_style(writer: anytype, style: Theme.Style) !void {
|
||||||
|
const ansi_style = .{
|
||||||
|
.foreground = if (style.fg) |color| to_rgb_color(color) else .Default,
|
||||||
|
.background = if (style.bg) |color| to_rgb_color(color) else .Default,
|
||||||
|
.font_style = switch (style.fs orelse .normal) {
|
||||||
|
.normal => term.style.FontStyle{},
|
||||||
|
.bold => term.style.FontStyle.bold,
|
||||||
|
.italic => term.style.FontStyle.italic,
|
||||||
|
.underline => term.style.FontStyle.underline,
|
||||||
|
.strikethrough => term.style.FontStyle.crossedout,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
try term.format.updateStyle(writer, ansi_style, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_rgb_color(color: u24) term.style.Color {
|
||||||
|
const r = @as(u8, @intCast(color >> 16 & 0xFF));
|
||||||
|
const g = @as(u8, @intCast(color >> 8 & 0xFF));
|
||||||
|
const b = @as(u8, @intCast(color & 0xFF));
|
||||||
|
return .{ .RGB = .{ .r = r, .g = g, .b = b } };
|
||||||
|
}
|
71
src/syntax.zig
Normal file
71
src/syntax.zig
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const ts = @import("tree-sitter");
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub const Edit = ts.InputEdit;
|
||||||
|
pub const FileType = @import("file_type.zig");
|
||||||
|
pub const Range = ts.Range;
|
||||||
|
|
||||||
|
a: std.mem.Allocator,
|
||||||
|
lang: *const ts.Language,
|
||||||
|
file_type: *const FileType,
|
||||||
|
parser: *ts.Parser,
|
||||||
|
query: *ts.Query,
|
||||||
|
injections: *ts.Query,
|
||||||
|
tree: ?*ts.Tree = null,
|
||||||
|
content: []const u8,
|
||||||
|
|
||||||
|
pub fn create(file_type: *const FileType, a: std.mem.Allocator, content: []const u8) !*Self {
|
||||||
|
const self = try a.create(Self);
|
||||||
|
self.* = .{
|
||||||
|
.a = a,
|
||||||
|
.lang = file_type.lang_fn() orelse std.debug.panic("tree-sitter parser function failed for language: {d}", .{file_type.name}),
|
||||||
|
.file_type = file_type,
|
||||||
|
.parser = try ts.Parser.create(),
|
||||||
|
.query = try ts.Query.create(self.lang, file_type.highlights),
|
||||||
|
.injections = try ts.Query.create(self.lang, file_type.highlights),
|
||||||
|
.content = content,
|
||||||
|
};
|
||||||
|
errdefer self.destroy();
|
||||||
|
try self.parser.setLanguage(self.lang);
|
||||||
|
try self.parse();
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_guess_file_type(a: std.mem.Allocator, content: []const u8, file_path: ?[]const u8) !*Self {
|
||||||
|
const file_type = FileType.guess(file_path, content) orelse return error.NotFound;
|
||||||
|
return create(file_type, a, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *Self) void {
|
||||||
|
if (self.tree) |tree| tree.destroy();
|
||||||
|
self.query.destroy();
|
||||||
|
self.parser.destroy();
|
||||||
|
self.a.destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(self: *Self) !void {
|
||||||
|
if (self.tree) |tree| tree.destroy();
|
||||||
|
self.tree = try self.parser.parseString(null, self.content);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn CallBack(comptime T: type) type {
|
||||||
|
return fn (ctx: T, sel: Range, scope: []const u8, id: u32, idx: usize) error{Stop}!void;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(self: *const Self, ctx: anytype, comptime cb: CallBack(@TypeOf(ctx))) !void {
|
||||||
|
const cursor = try ts.Query.Cursor.create();
|
||||||
|
defer cursor.destroy();
|
||||||
|
const tree = if (self.tree) |p| p else return;
|
||||||
|
cursor.execute(self.query, tree.getRootNode());
|
||||||
|
while (cursor.nextMatch()) |match| {
|
||||||
|
var idx: usize = 0;
|
||||||
|
for (match.captures()) |capture| {
|
||||||
|
const range = capture.node.getRange();
|
||||||
|
const scope = self.query.getCaptureNameForId(capture.id);
|
||||||
|
try cb(ctx, range, scope, capture.id, idx);
|
||||||
|
idx += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue