diff options
-rw-r--r-- | build.zig.zon | 8 | ||||
-rw-r--r-- | src/main.janet | 25 | ||||
-rw-r--r-- | src/main.zig | 184 | ||||
-rw-r--r-- | src/selection.zig | 186 |
4 files changed, 349 insertions, 54 deletions
diff --git a/build.zig.zon b/build.zig.zon index 7923a60..f62d31f 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -13,12 +13,12 @@ .hash = "tree_sitter-0.25.0-8heIf51vAQConvVIgvm-9mVIbqh7yabZYqPXfOpS3YoG", }, .vaxis = .{ - .url = "git+https://github.com/rockorager/libvaxis#cc9154d5f4afa3fdfd289157f195ec67804ef437", - .hash = "vaxis-0.5.1-BWNV_MYNCQDO92x2PTpsfv6GwDHmjL98rf6iL3pbMLj4", + .url = "git+https://github.com/rockorager/libvaxis#025cf928ffc3ee7a4de36a91f4cca54a5810b2cc", + .hash = "vaxis-0.5.1-BWNV_CAVCQAmzxx2i2hBs5SvEupHwDbKHtE5VSOZwwtl", }, .zsanett = .{ - .url = "git+https://trong.loang.net/~cnx/zsanett#b2addf1901c1540797d740f35594df1ed7e72027", - .hash = "zsanett-0.0.0-1TMqaEbLAABbm3ueu1BMakaJ22PDqCGWpCA4yRYPacgB", + .url = "git+https://trong.loang.net/~cnx/zsanett#f5b2d6f3122b54fcdcb3bd6953420480bb9bc752", + .hash = "zsanett-0.0.0-1TMqaI7-AAD5R_t1Udvn92vPkwJnBQXnVzLgKMlJkOIB", }, }, .paths = .{ diff --git a/src/main.janet b/src/main.janet new file mode 100644 index 0000000..8ca1215 --- /dev/null +++ b/src/main.janet @@ -0,0 +1,25 @@ +# Event loop +# SPDX-FileCopyrightText: 2025 Nguyễn Gia Phong +# SPDX-License-Identifier: GPL-3.0-or-later + +(defn handle + [event] + (match event + [:key-press key] (cond + (:matches key (chr "c") {:ctrl true}) [:quit] + (:matches key (chr "h") {}) (do (:go-up env) []) + (:matches key (chr "j") {}) (do (:go-right env) []) + (:matches key (chr "k") {}) (do (:go-left env) []) + (:matches key (chr "l") {}) (do (:go-down env) []) + []))) + +(defn run + [events] + (if-let [event (array/pop events)] + (unless (= :quit event) + (let [next-events (handle event)] + (:render env) + (run (array/join events next-events)))) + (run @[(:next-event env)]))) + +(run @[]) diff --git a/src/main.zig b/src/main.zig index aec275c..c2d9d12 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4,23 +4,40 @@ const Allocator = std.mem.Allocator; const DebugAllocator = std.heap.DebugAllocator; +const argsAlloc = std.process.argsAlloc; +const argsFree = std.process.argsFree; const builtin = @import("builtin"); +const cwd = std.fs.cwd; const eql = std.mem.eql; +const maxInt = std.math.maxInt; const ns_per_s = std.time.ns_per_s; const smp_allocator = std.heap.smp_allocator; const std = @import("std"); const Key = vaxis.Key; const Loop = vaxis.Loop; +const Style = vaxis.Cell.Style; const Tty = vaxis.Tty; const Unicode = vaxis.Unicode; +const Window = vaxis.Window; const Winsize = vaxis.Winsize; const gwidth = vaxis.gwidth.gwidth; +const Vaxis = vaxis.Vaxis; const vaxis = @import("vaxis"); + +const Parser = tree_sitter.Parser; +const tree_sitter = @import("tree-sitter"); + const zsanett = @import("zsanett"); +pub fn zsanettIntern(T: type) bool { + return switch (T) { + Environment, Key, Loop(Event) => true, + else => false, + }; +} const Config = @import("Config.zig"); -const Token = @import("Token.zig"); +const selection = @import("selection.zig"); const languages = @import("languages"); const Event = union(enum) { @@ -28,6 +45,88 @@ const Event = union(enum) { winsize: Winsize, }; +const Environment = struct { + allocator: Allocator, + loop: *Loop(Event), + parser: *Parser, + span: *selection.Span, + text: []const u8, + tty: *Tty, + vaxis: *Vaxis, + + pub fn nextEvent(self: Environment) !Event { + while (true) { + const event = self.loop.nextEvent(); + switch (event) { + .key_press => return event, + .winsize => |ws| { + try self.vaxis.resize(self.allocator, + self.tty.anyWriter(), ws); + try self.render(); + }, + } + } + } + + pub fn render(self: Environment) !void { + const window = self.vaxis.window(); + window.clear(); + var col: u16 = 0; + var row: u16 = 0; + var graphemes = self.vaxis.unicode.graphemeIterator(self.text); + while (graphemes.next()) |grapheme| { + const style = Style { + .reverse = self.span.contains(grapheme), + }; + const bytes = grapheme.bytes(self.text); + if (eql(u8, bytes, "\n")) { + const width = window.gwidth("$"); + defer col = 0; + defer row += 1; + window.writeCell(col, row, .{ + .char = .{ .grapheme = "$", .width = @intCast(width) }, + .style = style, + }); + } else if (eql(u8, bytes, "\t")) { + var tab = self.vaxis.unicode.graphemeIterator(" "); + while (tab.next()) |g| { + const b = g.bytes(" "); + const width = window.gwidth(b); + defer col += width; + window.writeCell(col, row, .{ + .char = .{ .grapheme = b, .width = @intCast(width) }, + .style = style, + }); + } + } else { + const width = window.gwidth(bytes); + defer col += width; + window.writeCell(col, row, .{ + .char = .{ .grapheme = bytes, .width = @intCast(width) }, + .style = style, + }); + } + } + try self.vaxis.render(self.tty.anyWriter()); + } + + pub fn goUp(self: Environment) void { + self.span.goUp(); + } + + pub fn goRight(self: Environment) void { + self.span.goRight(self.vaxis.unicode.width_data.graphemes, self.text); + } + + pub fn goLeft(self: Environment) void { + self.span.goLeft(self.vaxis.unicode.width_data.graphemes, self.text); + } + + pub fn goDown(self: Environment) void { + self.span.goDown(self.vaxis.unicode.width_data.graphemes, self.text); + } +}; + pub fn main() !void { var debug_allocator = DebugAllocator(.{}).init; defer _ = debug_allocator.deinit(); @@ -36,64 +135,49 @@ pub fn main() !void { .ReleaseFast, .ReleaseSmall => smp_allocator, }; - zsanett.init(); - defer zsanett.deinit(); - const janet_env = zsanett.Environment.init(null); - const config = try Config.parse(allocator, janet_env); - var tty = try Tty.init(); defer tty.deinit(); var vx = try vaxis.init(allocator, .{}); defer vx.deinit(allocator, tty.anyWriter()); + var loop = Loop(Event){ .tty = &tty, .vaxis = &vx }; try loop.init(); try loop.start(); defer loop.stop(); + const parser = Parser.create(); + defer parser.destroy(); + + const args = try argsAlloc(allocator); + defer argsFree(allocator, args); + const text = try cwd().readFileAlloc(allocator, args[1], maxInt(u32)); + defer allocator.free(text); try vx.enterAltScreen(tty.anyWriter()); - // For the alternate screen - try vx.queryTerminal(tty.anyWriter(), 1 * ns_per_s); - - const text = "int main()\n{\n\treturn 0;\n}\n"; - var tokens = try Token.ize(text, languages.c); - defer tokens.deinit(); - - while (true) { - switch (loop.nextEvent()) { - .key_press => |key| { - if (key.matches('c', .{ .ctrl = true })) { - break; - } - }, - .winsize => |ws| try vx.resize(allocator, tty.anyWriter(), ws), - } - const window = vx.window(); - window.clear(); - var col: u16 = 0; - var row: u16 = 0; - tokens.reset(); - while (tokens.next()) |token| { - var graphemes = vx.unicode.graphemeIterator(token.text); - while (graphemes.next()) |grapheme| { - const bytes = grapheme.bytes(token.text); - if (eql(u8, bytes, "\r\n") - or eql(u8, bytes, "\r") - or eql(u8, bytes, "\n")) { - col = 0; - row += 1; - continue; - } - const width: u8 = if (eql(u8, bytes, "\t")) - config.tab_width - else - @intCast(gwidth(bytes, vx.caps.unicode, - &vx.unicode.width_data)); - defer col += width; - window.writeCell(col, row, .{ - .char = .{ .grapheme = bytes, .width = width }, - }); - } - } - try vx.render(tty.anyWriter()); + try vx.queryTerminal(tty.anyWriter(), 1 * ns_per_s); // for alt screen + + const language = languages.c(); + defer language.destroy(); + try parser.setLanguage(language); + const tree = parser.parseString(text, null).?; + defer tree.destroy(); + + zsanett.init(); + defer zsanett.deinit(); + var span = selection.Span{ + .head = .{ .node = tree.rootNode() }, + .tail = .{ .node = tree.rootNode() }, + }; + try zsanett.def("env", Environment{ + .allocator = allocator, + .loop = &loop, + .parser = parser, + .span = &span, + .text = text, + .tty = &tty, + .vaxis = &vx, + }, "eval environment"); + switch (try zsanett.eval(void, @embedFile("main.janet"), "main.janet")) { + .ret => {}, + .err => |err| @panic(err), } } diff --git a/src/selection.zig b/src/selection.zig new file mode 100644 index 0000000..1fb8266 --- /dev/null +++ b/src/selection.zig @@ -0,0 +1,186 @@ +// Selection +// SPDX-FileCopyrightText: 2025 Nguyễn Gia Phong +// SPDX-License-Identifier: GPL-3.0-or-later + +const eql = std.meta.eql; +const order = std.math.order; +const std = @import("std"); + +const Grapheme = Graphemes.Grapheme; +const Graphemes = vaxis.Graphemes; +const Node = tree_sitter.Node; +const Tree = tree_sitter.Tree; +const tree_sitter = @import("tree-sitter"); +const vaxis = @import("vaxis"); + +fn graphemeAt(graphemes: Graphemes, text: []const u8, index: u32) Grapheme { + var iterator = graphemes.iterator(text[index..]); + return .{ .offset = index, .len = iterator.peek().?.len }; +} + +fn graphemeBefore(graphemes: Graphemes, text: []const u8, index: u32) Grapheme { + var iterator = graphemes.reverseIterator(text[0..index]); + return iterator.peek().?; +} + +// TODO: undo +pub const Unit = union(enum) { + node: Node, + grapheme: struct { + const GraphemeUnit = @This(); + slice: Grapheme, + tree: *const Tree, + + fn parent(unit: GraphemeUnit) Node { + const start = unit.slice.offset; + const end = start + unit.slice.len; + return unit.tree.rootNode().descendantForByteRange(start, end).?; + } + + fn at(unit: GraphemeUnit, graphemes: Graphemes, + text: []const u8, index: u32) GraphemeUnit { + return .{ + .slice = graphemeAt(graphemes, text, index), + .tree = unit.tree, + }; + } + + fn before(unit: GraphemeUnit, graphemes: Graphemes, + text: []const u8, index: u32) GraphemeUnit { + return .{ + .slice = graphemeBefore(graphemes, text, index), + .tree = unit.tree, + }; + } + }, + + pub fn up(unit: Unit) Unit { + return switch (unit) { + .node => |node| if (node.parent()) |parent_node| + if (parent_node.eql(node)) + .up(.{ .node = parent_node }) + else + .{ .node = parent_node } + else unit, + .grapheme => |grapheme| .{ .node = grapheme.parent() }, + }; + } + + pub fn right(unit: Unit, graphemes: Graphemes, text: []const u8) Unit { + switch (unit) { + .node => |node| if (node.nextSibling()) |sibling| { + const prev = node.endByte(); + const next = sibling.startByte(); + return switch (order(prev, next)) { + .lt => .{ + .grapheme = .{ + .slice = graphemeAt(graphemes, text, prev), + .tree = node.tree, + } + }, + .eq => .{ .node = sibling }, + .gt => unreachable, + }; + } else return .up(unit), + .grapheme => |grapheme| { + const start = grapheme.slice.offset + grapheme.slice.len; + const next_grapheme = grapheme.at(graphemes, text, start); + const parent = next_grapheme.parent(); + return if (parent.eql(grapheme.parent())) .{ + .grapheme = next_grapheme + } else .{ + .node = parent + }; + }, + } + } + + pub fn left(unit: Unit, graphemes: Graphemes, text: []const u8) Unit { + switch (unit) { + .node => |node| if (node.prevSibling()) |sibling| { + const prev = sibling.endByte(); + const next = node.startByte(); + return switch (order(prev, next)) { + .lt => .{ + .grapheme = .{ + .slice = graphemeBefore(graphemes, text, next), + .tree = node.tree, + } + }, + .eq => .{ .node = sibling }, + .gt => unreachable, + }; + } else return .up(unit), + .grapheme => |grapheme| { + const end = grapheme.slice.offset; + const prev_grapheme = grapheme.before(graphemes, text, end); + const parent = prev_grapheme.parent(); + return if (parent.eql(grapheme.parent())) .{ + .grapheme = prev_grapheme + } else .{ + .node = parent + }; + }, + } + } + + pub fn down(unit: Unit, graphemes: Graphemes, text: []const u8) Unit { + switch (unit) { + .node => |node| { + const start = node.startByte(); + const end = node.endByte(); + // TODO: history + return if (node.child(0)) |child| + if (child.startByte() == start and child.endByte() == end) + .down(.{ .node = child }, graphemes, text) + else + .{ .node = child } + else .{ + .grapheme = .{ + .slice = graphemeAt(graphemes, text, start), + .tree = node.tree, + } + }; + }, + .grapheme => return unit, + } + } + + pub fn contains(unit: Unit, other: Grapheme) bool { + return switch (unit) { + .node => |node| (node.startByte() <= other.offset + and other.offset + other.len <= node.endByte()), + .grapheme => |grapheme| eql(grapheme.slice, other), + }; + } +}; + +pub const Span = struct { + head: Unit, + tail: Unit, + + pub fn contains(span: Span, grapheme: Grapheme) bool { + // TODO: range + return span.head.contains(grapheme); + } + + pub fn goUp(span: *Span) void { + span.head = span.head.up(); + span.tail = span.tail.up(); + } + + pub fn goRight(span: *Span, graphemes: Graphemes, text: []const u8) void { + span.head = span.head.right(graphemes, text); + span.tail = span.tail.right(graphemes, text); + } + + pub fn goLeft(span: *Span, graphemes: Graphemes, text: []const u8) void { + span.head = span.head.left(graphemes, text); + span.tail = span.tail.left(graphemes, text); + } + + pub fn goDown(span: *Span, graphemes: Graphemes, text: []const u8) void { + span.head = span.head.down(graphemes, text); + span.tail = span.tail.down(graphemes, text); + } +}; |