diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..03299ae --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/zig-cache/ diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..642f65a --- /dev/null +++ b/build.zig @@ -0,0 +1,87 @@ +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, + }); + + _ = b.addModule("syntax", .{ + .root_source_file = .{ .path = "src/syntax.zig" }, + .imports = &.{ + .{ .name = "treez", .module = tree_sitter_dep.module("treez") }, + ts_queryfile(b, tree_sitter_dep, "tree-sitter-agda/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-bash/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-c-sharp/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-c/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-cpp/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-css/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-diff/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-dockerfile/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-git-rebase/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-gitcommit/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-go/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-fish/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-haskell/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-html/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-java/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-javascript/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-jsdoc/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-json/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-lua/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-make/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-markdown/tree-sitter-markdown/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-markdown/tree-sitter-markdown-inline/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-nasm/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-ninja/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-nix/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-ocaml/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-openscad/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-org/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-php/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-python/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-purescript/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-regex/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-ruby/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-rust/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-ssh-config/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-scala/queries/scala/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-scheme/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-toml/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-typescript/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-xml/dtd/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-xml/xml/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-zig/queries/highlights.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-ziggy/tree-sitter-ziggy/queries/highlights.scm"), + + ts_queryfile(b, tree_sitter_dep, "tree-sitter-cpp/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-gitcommit/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-html/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-javascript/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-lua/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-markdown/tree-sitter-markdown-inline/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-markdown/tree-sitter-markdown/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-nasm/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-nix/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-openscad/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-php/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-purescript/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-purescript/vim_queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-rust/queries/injections.scm"), + ts_queryfile(b, tree_sitter_dep, "tree-sitter-zig/queries/injections.scm"), + }, + }); + +} + +fn ts_queryfile(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), + }), + }; +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..e1d44b0 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,16 @@ +.{ + .name = "flow-syntax", + .version = "0.0.1", + + .dependencies = .{ + .@"tree-sitter" = .{ + .url = "https://github.com/neurocyte/tree-sitter/releases/download/master-fb56a3e014f77e8189ae8b60408c555f8fe8be88/source.tar.gz", + .hash = "12200ead593d0f6aa6352dba239cc83a1d1457b060738560dbb890fd5cc5957d710f", + }, + }, + .paths = .{ + "src", + "build.zig", + "build.zig.zon", + }, +} diff --git a/src/file_type.zig b/src/file_type.zig new file mode 100644 index 0000000..bba91e4 --- /dev/null +++ b/src/file_type.zig @@ -0,0 +1,127 @@ +const std = @import("std"); +const treez = @import("treez"); +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 get_by_name(name: []const u8) ?*const FileType { + for (file_types) |*file_type| + if (std.mem.eql(u8, file_type.name, name)) + return file_type; + return null; +} + +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 treez.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, + .first_line_matches = args.first_line_matches, + }; +} + +pub 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"), + }; +} diff --git a/src/file_types.zig b/src/file_types.zig new file mode 100644 index 0000000..b718b8a --- /dev/null +++ b/src/file_types.zig @@ -0,0 +1,294 @@ +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 = "sh" }, +}; + +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", ".gitconfig" }, + .highlights = fish.highlights, + .comment = "#", + .parser = fish.parser, +}; + +pub const cpp = .{ + .color = 0x9c033a, + .icon = "", + .extensions = &[_][]const u8{ "cc", "cpp", "cxx", "hpp", "hxx", "h", "ipp", "ixx" }, + .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", "patch" }, + .comment = "#", +}; + +pub const dockerfile = .{ + .color = 0x019bc6, + .icon = "", + .extensions = &[_][]const u8{ "Dockerfile", "dockerfile", "docker", "Containerfile", "container" }, + .comment = "#", +}; + +pub const dtd = .{ + .icon = "󰗀", + .extensions = &[_][]const u8{"dtd"}, + .comment = "