summary refs log tree commit diff
path: root/src/Selection.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/Selection.zig')
-rw-r--r--src/Selection.zig275
1 files changed, 275 insertions, 0 deletions
diff --git a/src/Selection.zig b/src/Selection.zig
new file mode 100644
index 0000000..352af7a
--- /dev/null
+++ b/src/Selection.zig
@@ -0,0 +1,275 @@
+// 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 Node = tree_sitter.Node;
+const Tree = tree_sitter.Tree;
+const tree_sitter = @import("tree-sitter");
+
+const Grapheme = vaxis.Graphemes.Grapheme;
+const vaxis = @import("vaxis");
+
+const graphemes = &@import("root").graphemes;
+
+const Selection = @This();
+head: Unit,
+tail: Unit,
+
+pub fn startByte(selection: Selection) u32 {
+    return @min(selection.head.startByte(), selection.tail.startByte());
+}
+
+pub fn endByte(selection: Selection) u32 {
+    return @max(selection.head.endByte(), selection.tail.endByte());
+}
+
+pub fn inHead(selection: Selection, grapheme: Grapheme) bool {
+    return selection.head.contains(grapheme);
+}
+
+pub fn inBody(selection: Selection, grapheme: Grapheme) bool {
+    return (!selection.inHead(grapheme)
+            and selection.startByte() <= grapheme.offset
+            and grapheme.offset + grapheme.len <= selection.endByte());
+}
+
+fn graphemeAt(text: []const u8, index: u32) Grapheme {
+    var iterator = graphemes.iterator(text[index..]);
+    return .{ .offset = index, .len = iterator.peek().?.len };
+}
+
+fn graphemeBefore(text: []const u8, index: u32) Grapheme {
+    var iterator = graphemes.reverseIterator(text[0..index]);
+    return iterator.peek().?;
+}
+
+// TODO: nesting
+const Span = struct {
+    start: u32,
+    end: u32,
+    tree: *const Tree,
+
+    fn parent(unit: Span) Node {
+        const root_node = unit.tree.rootNode();
+        return root_node.descendantForByteRange(unit.start, unit.end).?;
+    }
+};
+
+const GraphemeUnit = struct {
+    slice: Grapheme,
+    tree: *const Tree,
+
+    fn startByte(unit: GraphemeUnit) u32 {
+        return unit.slice.offset;
+    }
+
+    fn endByte(unit: GraphemeUnit) u32 {
+        return unit.slice.offset + unit.slice.len;
+    }
+
+    fn parent(unit: GraphemeUnit) Span {
+        const start = unit.startByte();
+        const end = unit.endByte();
+        const root_node = unit.tree.rootNode();
+        const parent_node = root_node.descendantForByteRange(start, end).?;
+        if (parent_node.firstChildForByte(end)) |next_node| {
+            return if (next_node.prevSibling()) |prev_node| .{
+                .start = prev_node.endByte(),
+                .end = next_node.startByte(),
+                .tree = unit.tree,
+            } else .{
+                .start = parent_node.startByte(),
+                .end = next_node.startByte(),
+                .tree = unit.tree,
+            };
+        } else {
+            const siblings = parent_node.childCount();
+            if (siblings == 0)
+                return .{
+                    .start = parent_node.startByte(),
+                    .end = parent_node.endByte(),
+                    .tree = unit.tree,
+                };
+            const prev_node = parent_node.child(siblings - 1).?;
+            return .{
+                .start = prev_node.endByte(),
+                .end = parent_node.endByte(),
+                .tree = unit.tree,
+            };
+        }
+    }
+
+    fn at(unit: GraphemeUnit, text: []const u8, index: u32) GraphemeUnit {
+        return .{
+            .slice = graphemeAt(text, index),
+            .tree = unit.tree,
+        };
+    }
+
+    fn before(unit: GraphemeUnit,
+              text: []const u8, index: u32) GraphemeUnit {
+        return .{
+            .slice = graphemeBefore(text, index),
+            .tree = unit.tree,
+        };
+    }
+};
+
+// TODO: undo
+pub const Unit = union(enum) {
+    node: Node,
+    span: Span,
+    grapheme: GraphemeUnit,
+
+    fn startByte(unit: Unit) u32 {
+        return switch (unit) {
+            .node => |node| node.startByte(),
+            .span => |span| span.start,
+            .grapheme => |grapheme| grapheme.startByte(),
+        };
+    }
+
+    fn endByte(unit: Unit) u32 {
+        return switch (unit) {
+            .node => |node| node.endByte(),
+            .span => |span| span.end,
+            .grapheme => |grapheme| grapheme.endByte(),
+        };
+    }
+
+    pub fn up(unit: Unit, text: []const u8) Unit {
+        switch (unit) {
+            .node => |node| return if (node.parent()) |parent_node|
+                if (parent_node.eql(node))
+                    .up(.{ .node = parent_node }, text)
+                else
+                    .{ .node = parent_node }
+            else unit,
+            .span => |span| {
+                const parent_node = span.parent();
+                return if (parent_node.startByte() == span.start
+                               and parent_node.endByte() == span.end)
+                    .up(.{ .node = parent_node }, text)
+                else
+                    .{ .node = parent_node };
+            },
+            .grapheme => |grapheme| {
+                const parent_span = grapheme.parent();
+                return if (parent_span.start == grapheme.startByte()
+                               and parent_span.end == grapheme.endByte())
+                    .up(.{ .span = parent_span }, text)
+                else
+                    .{ .span = parent_span };
+            },
+        }
+    }
+
+    pub fn right(unit: Unit, 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 => .{
+                        .span = .{
+                            .start = prev,
+                            .end = next,
+                            .tree = node.tree,
+                        }
+                    },
+                    .eq => .{ .node = sibling },
+                    .gt => unreachable,
+                };
+            },
+            .span => |span| {
+                const parent_node = unit.up(text).node;
+                if (parent_node.firstChildForByte(span.end)) |next_node|
+                    return .{ .node = next_node };
+            },
+            .grapheme => |grapheme| {
+                const start = grapheme.endByte();
+                const next_grapheme = grapheme.at(text, start);
+                if (eql(next_grapheme.parent(), grapheme.parent()))
+                    return .{ .grapheme = next_grapheme };
+            },
+        }
+        return .up(unit, text);
+    }
+
+    pub fn left(unit: Unit, 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 => .{
+                        .span = .{
+                            .start = prev,
+                            .end = next,
+                            .tree = node.tree,
+                        }
+                    },
+                    .eq => .{ .node = sibling },
+                    .gt => unreachable,
+                };
+            },
+            .span => |span| {
+                const parent_node = unit.up(text).node;
+                if (parent_node.firstChildForByte(span.end)) |next_node| {
+                    if (next_node.prevSibling()) |prev_node|
+                        return .{ .node = prev_node };
+                } else {
+                    const siblings = parent_node.childCount();
+                    return .{ .node =  parent_node.child(siblings - 1).? };
+                }
+            },
+            .grapheme => |grapheme| {
+                const end = grapheme.slice.offset;
+                const prev_grapheme = grapheme.before(text, end);
+                if (eql(prev_grapheme.parent(), grapheme.parent()))
+                    return .{ .grapheme = prev_grapheme };
+            },
+        }
+        return .up(unit, text);
+    }
+
+    pub fn down(unit: Unit, 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 }, text)
+                    else
+                        .{ .node = child }
+                else .down(.{
+                    .span = .{ .start = start, .end = end, .tree = node.tree }
+                }, text);
+            },
+            .span => |span| return .{
+                .grapheme = .{
+                    // TODO: history
+                    .slice = graphemeAt(text, span.start),
+                    .tree = span.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()),
+            .span => |span| (span.start <= other.offset
+                             and other.offset + other.len <= span.end),
+            .grapheme => |grapheme| eql(grapheme.slice, other),
+        };
+    }
+};