First attempt at adding syntax highlighting

This commit is contained in:
Sijmen 2020-05-28 00:54:11 +02:00
parent 9dee6e739c
commit 72f0ea221c
Signed by: vijfhoek
GPG key ID: DAF7821E067D9C48
7 changed files with 261 additions and 66 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
zig-cache/ zig-cache/
tmp/

6
.gitmodules vendored
View file

@ -1,3 +1,9 @@
[submodule "vendor/zig-sdl"] [submodule "vendor/zig-sdl"]
path = vendor/zig-sdl path = vendor/zig-sdl
url = https://git.sijman.nl/zig/zig-sdl.git 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

View file

@ -11,10 +11,16 @@ pub fn build(b: *Builder) void {
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
const mode = b.standardReleaseOptions(); const mode = b.standardReleaseOptions();
const cflags = &[_][]const u8{};
const exe = b.addExecutable("zite", "src/main.zig"); const exe = b.addExecutable("zite", "src/main.zig");
exe.linkSystemLibrary("c"); exe.linkSystemLibrary("c");
exe.linkSystemLibrary("SDL2"); exe.linkSystemLibrary("SDL2");
exe.linkSystemLibrary("SDL2_ttf"); 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"); exe.addPackagePath("zig-sdl", "vendor/zig-sdl/src/main.zig");

View file

@ -9,6 +9,20 @@ pub const ColorTheme = struct {
treeFolderFg: sdl.Color, treeFolderFg: sdl.Color,
treeEntryHighlightBg: 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 { fn init() ColorTheme {
var fg = sdl.Color{ .r = 56, .g = 58, .b = 68 }; var fg = sdl.Color{ .r = 56, .g = 58, .b = 68 };
return ColorTheme{ return ColorTheme{
@ -18,6 +32,20 @@ pub const ColorTheme = struct {
.treeFileFg = fg, .treeFileFg = fg,
.treeFolderFg = sdl.Color{ .r = 64, .g = 120, .b = 242 }, .treeFolderFg = sdl.Color{ .r = 64, .g = 120, .b = 242 },
.treeEntryHighlightBg = sdl.Color{ .r = 229, .g = 229, .b = 229 }, .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,
}; };
} }
}; };

View file

@ -1,100 +1,252 @@
const std = @import("std"); const std = @import("std");
const zig_sdl = @import("zig-sdl"); const sdl = @import("zig-sdl").sdl;
const sdl = zig_sdl.sdl; const ts = @cImport(@cInclude("tree_sitter/api.h"));
const Ui = @import("ui.zig").Ui; const Ui = @import("ui.zig").Ui;
const TreeView = @import("treeview.zig").TreeView; const TreeView = @import("treeview.zig").TreeView;
const colorTheme = @import("color_theme.zig").colorTheme; 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 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; pub const x = TreeView.width + 10;
path: ?[]const u8 = null, path: ?[]const u8 = null,
// TODO Do allocator stuff // TODO Do allocator stuff
count: u64 = 0, count: u64 = 0,
lines: [4096]Line = undefined, segments: [4096]Segment = undefined,
pub fn init() DocView { pub fn init() DocView {
return DocView{}; return DocView{};
} }
pub fn render(self: *DocView, ui: *Ui) !void { pub fn render(self: *DocView, ui: *Ui) !void {
for (self.lines[0..self.count]) |*line| { for (self.segments[0..self.count]) |*segment| {
try line.render(ui); try segment.render(ui);
} }
} }
pub fn open(self: *DocView, path: []const u8) !void { pub fn open(self: *DocView, path: []const u8) !void {
self.path = path; 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| { const file = std.fs.cwd().openFile(self.path.?, .{}) catch |err| {
std.debug.warn("unable to open file: {}\n", .{err}); std.debug.warn("unable to open file: {}\n", .{err});
return; return;
}; };
defer file.close();
var line_buf: [1024 * 1024]u8 = undefined; var buffer: [1024 * 1024]u8 = undefined;
var len = file.readAll(&line_buf) catch unreachable; var len = file.readAll(&buffer) catch unreachable;
buffer[len] = 0;
file.close();
var iterator = std.mem.split(line_buf[0..len], "\n"); std.debug.warn("{s}\n", .{buffer});
var index: u32 = 0; var source_code =
while (iterator.next()) |line| { \\#include <assert.h>
std.debug.warn("line {}: '{}'\n", .{ index, line }); \\int main(int argc, char** argv) {
self.lines[index] = Line{ \\ int a = 3 + argc;
.string = line, \\ printf("%d\n", a);
.y = @bitCast(i32, index) * Line.height + 10, \\}
}; ;
index += 1;
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;
} }
}; };

1
vendor/tree-sitter vendored Submodule

@ -0,0 +1 @@
Subproject commit ec870e9e66c34354133ad865dd12fbaceb021083

1
vendor/tree-sitter-c vendored Submodule

@ -0,0 +1 @@
Subproject commit 99151b1e9293c9e025498fee7e6691e1a52e1d03