diff --git a/.gitignore b/.gitignore index 3cef7be..e7f7b34 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ zig-cache/ +tmp/ diff --git a/.gitmodules b/.gitmodules index edc61f0..e4794b7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "vendor/zig-sdl"] path = vendor/zig-sdl url = https://git.sijman.nl/zig/zig-sdl.git +[submodule "vendor/tree-sitter-c"] + path = vendor/tree-sitter-c + url = https://github.com/tree-sitter/tree-sitter-c.git +[submodule "vendor/tree-sitter"] + path = vendor/tree-sitter + url = https://github.com/tree-sitter/tree-sitter.git diff --git a/build.zig b/build.zig index 6ce6efe..f2373ff 100644 --- a/build.zig +++ b/build.zig @@ -11,10 +11,16 @@ pub fn build(b: *Builder) void { // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. const mode = b.standardReleaseOptions(); + const cflags = &[_][]const u8{}; + const exe = b.addExecutable("zite", "src/main.zig"); exe.linkSystemLibrary("c"); exe.linkSystemLibrary("SDL2"); exe.linkSystemLibrary("SDL2_ttf"); + exe.addIncludeDir("vendor/tree-sitter/lib/include"); + exe.addIncludeDir("vendor/tree-sitter/lib/src"); + exe.addObjectFile("vendor/tree-sitter/libtree-sitter.a"); + exe.addCSourceFile("vendor/tree-sitter-c/src/parser.c", cflags); exe.addPackagePath("zig-sdl", "vendor/zig-sdl/src/main.zig"); diff --git a/src/color_theme.zig b/src/color_theme.zig index d68ce99..3411926 100644 --- a/src/color_theme.zig +++ b/src/color_theme.zig @@ -9,6 +9,20 @@ pub const ColorTheme = struct { treeFolderFg: sdl.Color, treeEntryHighlightBg: sdl.Color, + commentFg: sdl.Color, + constantFg: sdl.Color, + delimiterFg: sdl.Color, + functionFg: sdl.Color, + functionSpecialFg: sdl.Color, + keywordFg: sdl.Color, + labelFg: sdl.Color, + numberFg: sdl.Color, + operatorFg: sdl.Color, + propertyFg: sdl.Color, + stringFg: sdl.Color, + typeFg: sdl.Color, + variableFg: sdl.Color, + fn init() ColorTheme { var fg = sdl.Color{ .r = 56, .g = 58, .b = 68 }; return ColorTheme{ @@ -18,6 +32,20 @@ pub const ColorTheme = struct { .treeFileFg = fg, .treeFolderFg = sdl.Color{ .r = 64, .g = 120, .b = 242 }, .treeEntryHighlightBg = sdl.Color{ .r = 229, .g = 229, .b = 229 }, + + .commentFg = sdl.Color{ .r = 0x50, .g = 0xa1, .b = 0x4f }, + .constantFg = fg, + .delimiterFg = fg, + .functionFg = fg, + .functionSpecialFg = sdl.Color{ .r = 0xa6, .g = 0x26, .b = 0xa4 }, + .keywordFg = sdl.Color{ .r = 0xa6, .g = 0x26, .b = 0xa4 }, + .labelFg = fg, + .numberFg = sdl.Color{ .r = 0x98, .g = 0x68, .b = 0x01 }, + .operatorFg = fg, + .propertyFg = fg, + .stringFg = sdl.Color{ .r = 0x50, .g = 0xa1, .b = 0x4f }, + .typeFg = fg, + .variableFg = fg, }; } }; diff --git a/src/docview.zig b/src/docview.zig index bbbdf1b..a01be15 100644 --- a/src/docview.zig +++ b/src/docview.zig @@ -1,100 +1,252 @@ const std = @import("std"); -const zig_sdl = @import("zig-sdl"); -const sdl = zig_sdl.sdl; +const sdl = @import("zig-sdl").sdl; +const ts = @cImport(@cInclude("tree_sitter/api.h")); const Ui = @import("ui.zig").Ui; const TreeView = @import("treeview.zig").TreeView; const colorTheme = @import("color_theme.zig").colorTheme; +extern fn tree_sitter_c() ?*ts.TSLanguage; + +pub const SegmentKind = enum { + Comment, + Constant, + Delimiter, + Function, + FunctionSpecial, + Keyword, + Label, + Number, + Operator, + Property, + String, + Type, + Variable, + + fn init(string: []const u8) ?SegmentKind { + return if (std.mem.eql(u8, string, "comment")) + SegmentKind.Comment + else if (std.mem.eql(u8, string, "constant")) + SegmentKind.Constant + else if (std.mem.eql(u8, string, "delimiter")) + SegmentKind.Delimiter + else if (std.mem.eql(u8, string, "function")) + SegmentKind.Function + else if (std.mem.eql(u8, string, "function.special")) + SegmentKind.FunctionSpecial + else if (std.mem.eql(u8, string, "keyword")) + SegmentKind.Keyword + else if (std.mem.eql(u8, string, "label")) + SegmentKind.Label + else if (std.mem.eql(u8, string, "number")) + SegmentKind.Number + else if (std.mem.eql(u8, string, "operator")) + SegmentKind.Operator + else if (std.mem.eql(u8, string, "property")) + SegmentKind.Property + else if (std.mem.eql(u8, string, "string")) + SegmentKind.String + else if (std.mem.eql(u8, string, "type")) + SegmentKind.Type + else if (std.mem.eql(u8, string, "variable")) + SegmentKind.Variable + else + null; + } + + fn color(self: SegmentKind) sdl.Color { + return switch (self) { + .Comment => colorTheme.commentFg, + .Constant => colorTheme.constantFg, + .Delimiter => colorTheme.delimiterFg, + .Function => colorTheme.functionFg, + .FunctionSpecial => colorTheme.functionSpecialFg, + .Keyword => colorTheme.keywordFg, + .Label => colorTheme.labelFg, + .Number => colorTheme.numberFg, + .Operator => colorTheme.operatorFg, + .Property => colorTheme.propertyFg, + .String => colorTheme.stringFg, + .Type => colorTheme.typeFg, + .Variable => colorTheme.variableFg, + }; + } +}; + +pub const Segment = struct { + const height: i32 = 20; + + string: []const u8, + kind: ?SegmentKind = null, + + texture: ?sdl.Texture = null, + + x: i32 = 0, + y: i32 = 0, + textWidth: i32 = 0, + textHeight: i32 = 0, + + fn prerender(self: *Segment, ui: *Ui) !void { + if (self.texture) |texture| { + texture.deinit(); + } + + var color = colorTheme.fg; + var line = try std.mem.dupeZ(std.heap.c_allocator, u8, self.string); + var surface = try ui.codeFont.renderBlended( + line, + if (self.kind) |kind| kind.color() else colorTheme.fg, + ); + defer surface.deinit(); + + self.textWidth = surface.width(); + self.textHeight = surface.height(); + // std.debug.warn("surface size: '{}' {}x{}\n", .{ self.string, self.textWidth, self.textHeight }); + self.texture = try ui.renderer.createTextureFromSurface(surface); + } + + pub fn render(self: *Segment, ui: *Ui) !void { + if (self.string.len == 0) { + return; + } + + if (self.texture == null) { + try self.prerender(ui); + } + + var dst = sdl.Rect{ + .x = ui.scale(i32, DocView.x + self.x + 10), + .y = ui.scale(i32, self.y), + .w = self.textWidth, + .h = self.textHeight, + }; + if (self.texture) |texture| { + try ui.renderer.copy(texture, null, dst); + } + } +}; + pub const DocView = struct { - pub const Line = struct { - const height: i32 = 20; - - string: []const u8, - - texture: ?sdl.Texture = null, - - y: i32 = 0, - textWidth: i32 = 0, - textHeight: i32 = 0, - - fn prerender(self: *Line, ui: *Ui) !void { - if (self.texture) |texture| { - texture.deinit(); - } - - var color = colorTheme.fg; - var line = try std.mem.dupeZ(std.heap.c_allocator, u8, self.string); - var surface = try ui.codeFont.renderBlended(line, color); - defer surface.deinit(); - - self.textWidth = surface.width(); - self.textHeight = surface.height(); - std.debug.warn("surface size: {}x{}\n", .{ self.textWidth, self.textHeight }); - self.texture = try ui.renderer.createTextureFromSurface(surface); - } - - pub fn render(self: *Line, ui: *Ui) !void { - if (self.string.len == 0) { - return; - } - - if (self.texture == null) { - try self.prerender(ui); - } - - var dst = sdl.Rect{ - .x = ui.scale(i32, DocView.x + 10), - .y = ui.scale(i32, self.y), - .w = self.textWidth, - .h = self.textHeight, - }; - if (self.texture) |texture| { - try ui.renderer.copy(texture, null, dst); - } - } - }; - pub const x = TreeView.width + 10; path: ?[]const u8 = null, // TODO Do allocator stuff count: u64 = 0, - lines: [4096]Line = undefined, + segments: [4096]Segment = undefined, pub fn init() DocView { return DocView{}; } pub fn render(self: *DocView, ui: *Ui) !void { - for (self.lines[0..self.count]) |*line| { - try line.render(ui); + for (self.segments[0..self.count]) |*segment| { + try segment.render(ui); } } pub fn open(self: *DocView, path: []const u8) !void { self.path = path; + var parser = ts.ts_parser_new() orelse unreachable; + defer ts.ts_parser_delete(parser); + + var language = tree_sitter_c(); + _ = ts.ts_parser_set_language(parser, language); + const file = std.fs.cwd().openFile(self.path.?, .{}) catch |err| { std.debug.warn("unable to open file: {}\n", .{err}); return; }; - defer file.close(); - var line_buf: [1024 * 1024]u8 = undefined; - var len = file.readAll(&line_buf) catch unreachable; + var buffer: [1024 * 1024]u8 = undefined; + var len = file.readAll(&buffer) catch unreachable; + buffer[len] = 0; + file.close(); - var iterator = std.mem.split(line_buf[0..len], "\n"); - var index: u32 = 0; - while (iterator.next()) |line| { - std.debug.warn("line {}: '{}'\n", .{ index, line }); - self.lines[index] = Line{ - .string = line, - .y = @bitCast(i32, index) * Line.height + 10, - }; - index += 1; + std.debug.warn("{s}\n", .{buffer}); + var source_code = + \\#include + \\int main(int argc, char** argv) { + \\ int a = 3 + argc; + \\ printf("%d\n", a); + \\} + ; + + var tree = ts.ts_parser_parse_string( + parser, + null, + &buffer, + @intCast(u32, len), + ) orelse unreachable; + defer ts.ts_tree_delete(tree); + + var rootNode = ts.ts_tree_root_node(tree); + + var highlightsQuery: [4096]u8 = undefined; + var highlightsFile = std.fs.cwd().openFile( + "vendor/tree-sitter-c/queries/highlights.scm", + .{}, + ) catch unreachable; + var highlightsQueryLen = highlightsFile.readAll(&highlightsQuery) catch unreachable; + file.close(); + + var errorOffset: u32 = undefined; + var errorType: ts.TSQueryError = undefined; + var query = ts.ts_query_new( + language, + &highlightsQuery, + @intCast(u32, highlightsQueryLen), + &errorOffset, + &errorType, + ) orelse { + std.debug.warn( + "error parsing query at {}: {}\n", + .{ errorOffset, @tagName(errorType) }, + ); + return; + }; + defer ts.ts_query_delete(query); + + //var captures = [_][*c]const u8{null} ** ts.ts_query_capture_count(query); + var captureCount = ts.ts_query_capture_count(query); + var captures = std.ArrayList([]const u8).init(std.heap.c_allocator); + defer captures.deinit(); + + var cursor = ts.ts_query_cursor_new(); + defer ts.ts_query_cursor_delete(cursor); + ts.ts_query_cursor_exec(cursor, query, rootNode); + + var match: ts.TSQueryMatch = undefined; + var segmentIndex: usize = 0; + while (ts.ts_query_cursor_next_match(cursor, &match)) : (segmentIndex += 1) { + std.debug.warn("[{:>3}] index={}: ", .{ match.id, match.pattern_index }); + + var i: usize = 0; + while (i < match.capture_count) : (i += 1) { + var capture = &match.captures[i]; + var captureNameLength: u32 = 0; + var captureName = ts.ts_query_capture_name_for_id( + query, + @intCast(u32, capture.index), + &captureNameLength, + ); + var node = capture.node; + + var startByte = ts.ts_node_start_byte(node); + var endByte = ts.ts_node_end_byte(node); + var startPoint = ts.ts_node_start_point(node); + //std.debug.warn("[{d:>2}-{d:<2}] {s:<40} {s:<40} ", .{ start, end, string, parent }); + + self.segments[segmentIndex] = Segment{ + .string = buffer[startByte..endByte], + .x = @intCast(i32, startPoint.column) * 8, + .y = @intCast(i32, startPoint.row) * Segment.height + 10, + .kind = SegmentKind.init(captureName[0..captureNameLength]), + }; + std.debug.warn("{}\n", .{self.segments[segmentIndex]}); + } } - self.count = index; + self.count = segmentIndex; } }; diff --git a/vendor/tree-sitter b/vendor/tree-sitter new file mode 160000 index 0000000..ec870e9 --- /dev/null +++ b/vendor/tree-sitter @@ -0,0 +1 @@ +Subproject commit ec870e9e66c34354133ad865dd12fbaceb021083 diff --git a/vendor/tree-sitter-c b/vendor/tree-sitter-c new file mode 160000 index 0000000..99151b1 --- /dev/null +++ b/vendor/tree-sitter-c @@ -0,0 +1 @@ +Subproject commit 99151b1e9293c9e025498fee7e6691e1a52e1d03