diff --git a/build.zig.zon b/build.zig.zon index 0c6ccb6..a281e3c 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -30,8 +30,8 @@ .hash = "122019f077d09686b1ec47928ca2b4bf264422f3a27afc5b49dafb0129a4ceca0d01", }, .vaxis = .{ - .url = "https://github.com/neurocyte/libvaxis/archive/c26a190401bcd354263fa6f90446c947aca5ec2d.tar.gz", - .hash = "1220f89a746527174f2f297f5f6f85e1cca4267d5f75c7b6b840926ff0b1bb187e97", + .url = "https://github.com/neurocyte/libvaxis/archive/352fa9c89d6339325b9f851b9a27e62ec79ba33c.tar.gz", + .hash = "1220e604b723781b4a306db5801eee419a689b4d470232a1bacc525f45f00d455b1a", }, .zeit = .{ .url = "https://github.com/rockorager/zeit/archive/9cca8ec620a54c3b07cd249f25e5bcb3153d03d7.tar.gz", diff --git a/src/buffer/Buffer.zig b/src/buffer/Buffer.zig index 31ec237..69e0fa7 100644 --- a/src/buffer/Buffer.zig +++ b/src/buffer/Buffer.zig @@ -759,7 +759,7 @@ const Node = union(enum) { return if (found) ctx.result else error.NotFound; } - pub fn pos_to_width(self: *const Node, line: usize, pos: usize, metrics_: Metrics) !usize { + pub fn pos_to_width(self: *const Node, line: usize, pos: usize, metrics_: Metrics) error{NotFound}!usize { const do = struct { result: usize = 0, pos: usize, diff --git a/src/keybind/builtin/flow.json b/src/keybind/builtin/flow.json index 6267a45..d8a5470 100644 --- a/src/keybind/builtin/flow.json +++ b/src/keybind/builtin/flow.json @@ -91,6 +91,8 @@ ["alt+shift+i", "add_cursors_to_line_ends"], ["alt+shift+left", "shrink_selection"], ["alt+shift+right", "expand_selection"], + ["alt+home", "select_prev_sibling"], + ["alt+end", "select_next_sibling"], ["alt+shift+home", "move_scroll_left"], ["alt+shift+end", "move_scroll_right"], ["alt+shift+up", "add_cursor_up"], diff --git a/src/syntax/src/syntax.zig b/src/syntax/src/syntax.zig index 9f9a21f..0f8ff97 100644 --- a/src/syntax/src/syntax.zig +++ b/src/syntax/src/syntax.zig @@ -184,3 +184,9 @@ pub fn highlights_at_point(self: *const Self, ctx: anytype, comptime cb: CallBac } return; } + +pub fn node_at_point_range(self: *const Self, range: Range) error{Stop}!treez.Node { + const tree = self.tree orelse return error.Stop; + const root_node = tree.getRootNode(); + return treez.Node.externs.ts_node_descendant_for_point_range(root_node, range.start_point, range.end_point); +} diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 8ea49a6..3ca7eb1 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -111,6 +111,21 @@ pub const CurSel = struct { return sel; } + fn select_node(self: *Self, node: syntax.Node, root: Buffer.Root, metrics: Buffer.Metrics) error{NotFound}!void { + const range = node.getRange(); + self.selection = .{ + .begin = .{ + .row = range.start_point.row, + .col = try root.pos_to_width(range.start_point.row, range.start_point.column, metrics), + }, + .end = .{ + .row = range.end_point.row, + .col = try root.pos_to_width(range.end_point.row, range.end_point.column, metrics), + }, + }; + self.cursor = self.selection.?.end; + } + fn write(self: *const Self, writer: Buffer.MetaWriter) !void { try self.cursor.write(writer); if (self.selection) |sel| { @@ -2924,6 +2939,158 @@ pub const Editor = struct { } pub const selections_reverse_meta = .{ .description = "Reverse selection" }; + fn node_at_selection(self: *Self, sel: Selection, root: Buffer.Root, metrics: Buffer.Metrics) error{Stop}!syntax.Node { + const syn = self.syntax orelse return error.Stop; + const node = try syn.node_at_point_range(.{ + .start_point = .{ + .row = @intCast(sel.begin.row), + .column = @intCast(try root.get_line_width_to_pos(sel.begin.row, sel.begin.col, metrics)), + }, + .end_point = .{ + .row = @intCast(sel.end.row), + .column = @intCast(try root.get_line_width_to_pos(sel.end.row, sel.end.col, metrics)), + }, + .start_byte = 0, + .end_byte = 0, + }); + if (node.isNull()) return error.Stop; + return node; + } + + fn select_node_at_cursor(self: *Self, root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { + cursel.selection = null; + const sel = cursel.enable_selection().*; + return cursel.select_node(try self.node_at_selection(sel, root, metrics), root, metrics); + } + + fn expand_selection_to_parent_node(self: *Self, root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { + const sel = cursel.enable_selection().*; + const node = try self.node_at_selection(sel, root, metrics); + if (node.isNull()) return error.Stop; + const parent = node.getParent(); + if (parent.isNull()) return error.Stop; + return cursel.select_node(parent, root, metrics); + } + + pub fn expand_selection(self: *Self, _: Context) Result { + try self.send_editor_jump_source(); + const root = try self.buf_root(); + const cursel = self.get_primary(); + cursel.check_selection(); + try if (cursel.selection) |_| + self.expand_selection_to_parent_node(root, cursel, self.metrics) + else + self.select_node_at_cursor(root, cursel, self.metrics); + self.clamp(); + try self.send_editor_jump_destination(); + } + pub const expand_selection_meta = .{ .description = "Expand selection to AST parent node" }; + + fn shrink_selection_to_child_node(self: *Self, root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { + const sel = cursel.enable_selection().*; + const node = try self.node_at_selection(sel, root, metrics); + if (node.isNull() or node.getChildCount() == 0) return error.Stop; + const child = node.getChild(0); + if (child.isNull()) return error.Stop; + return cursel.select_node(child, root, metrics); + } + + fn shrink_selection_to_named_child_node(self: *Self, root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { + const sel = cursel.enable_selection().*; + const node = try self.node_at_selection(sel, root, metrics); + if (node.isNull() or node.getNamedChildCount() == 0) return error.Stop; + const child = node.getNamedChild(0); + if (child.isNull()) return error.Stop; + return cursel.select_node(child, root, metrics); + } + + pub fn shrink_selection(self: *Self, ctx: Context) Result { + var unnamed: bool = false; + _ = ctx.args.match(.{tp.extract(&unnamed)}) catch false; + try self.send_editor_jump_source(); + const root = try self.buf_root(); + const cursel = self.get_primary(); + cursel.check_selection(); + if (cursel.selection) |_| + try if (unnamed) + self.shrink_selection_to_child_node(root, cursel, self.metrics) + else + self.shrink_selection_to_named_child_node(root, cursel, self.metrics); + self.clamp(); + try self.send_editor_jump_destination(); + } + pub const shrink_selection_meta = .{ .description = "Shrink selection to first AST child node" }; + + fn select_next_sibling_node(self: *Self, root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { + const sel = cursel.enable_selection().*; + const node = try self.node_at_selection(sel, root, metrics); + if (node.isNull()) return error.Stop; + const sibling = syntax.Node.externs.ts_node_next_sibling(node); + if (sibling.isNull()) return error.Stop; + return cursel.select_node(sibling, root, metrics); + } + + fn select_next_named_sibling_node(self: *Self, root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { + const sel = cursel.enable_selection().*; + const node = try self.node_at_selection(sel, root, metrics); + if (node.isNull()) return error.Stop; + const sibling = syntax.Node.externs.ts_node_next_named_sibling(node); + if (sibling.isNull()) return error.Stop; + return cursel.select_node(sibling, root, metrics); + } + + pub fn select_next_sibling(self: *Self, ctx: Context) Result { + var unnamed: bool = false; + _ = ctx.args.match(.{tp.extract(&unnamed)}) catch false; + try self.send_editor_jump_source(); + const root = try self.buf_root(); + const cursel = self.get_primary(); + cursel.check_selection(); + if (cursel.selection) |_| + try if (unnamed) + self.select_next_sibling_node(root, cursel, self.metrics) + else + self.select_next_named_sibling_node(root, cursel, self.metrics); + self.clamp(); + try self.send_editor_jump_destination(); + } + pub const select_next_sibling_meta = .{ .description = "Move selection to next AST sibling node" }; + + fn select_prev_sibling_node(self: *Self, root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { + const sel = cursel.enable_selection().*; + const node = try self.node_at_selection(sel, root, metrics); + if (node.isNull()) return error.Stop; + const sibling = syntax.Node.externs.ts_node_prev_sibling(node); + if (sibling.isNull()) return error.Stop; + return cursel.select_node(sibling, root, metrics); + } + + fn select_prev_named_sibling_node(self: *Self, root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void { + const sel = cursel.enable_selection().*; + const node = try self.node_at_selection(sel, root, metrics); + if (node.isNull()) return error.Stop; + const sibling = syntax.Node.externs.ts_node_prev_named_sibling(node); + if (sibling.isNull()) return error.Stop; + return cursel.select_node(sibling, root, metrics); + } + + pub fn select_prev_sibling(self: *Self, ctx: Context) Result { + var unnamed: bool = false; + _ = ctx.args.match(.{tp.extract(&unnamed)}) catch false; + try self.send_editor_jump_source(); + const root = try self.buf_root(); + const cursel = self.get_primary(); + cursel.check_selection(); + if (cursel.selection) |_| + try if (unnamed) + self.select_prev_sibling_node(root, cursel, self.metrics) + else + self.select_prev_named_sibling_node(root, cursel, self.metrics); + self.clamp(); + try self.send_editor_jump_destination(); + } + pub const select_prev_sibling_meta = .{ .description = "Move selection to previous AST sibling node" }; + pub fn insert_chars(self: *Self, ctx: Context) Result { var chars: []const u8 = undefined; if (!try ctx.args.match(.{tp.extract(&chars)}))