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.zig367
1 files changed, 235 insertions, 132 deletions
diff --git a/src/Selection.zig b/src/Selection.zig
index 352af7a..5e01d32 100644
--- a/src/Selection.zig
+++ b/src/Selection.zig
@@ -2,6 +2,7 @@
 // SPDX-FileCopyrightText: 2025 Nguyễn Gia Phong
 // SPDX-License-Identifier: GPL-3.0-or-later
 
+const assert = std.debug.assert;
 const eql = std.meta.eql;
 const order = std.math.order;
 const std = @import("std");
@@ -13,6 +14,7 @@ const tree_sitter = @import("tree-sitter");
 const Grapheme = vaxis.Graphemes.Grapheme;
 const vaxis = @import("vaxis");
 
+const Buffer = @import("Buffer.zig");
 const graphemes = &@import("root").graphemes;
 
 const Selection = @This();
@@ -37,25 +39,79 @@ pub fn inBody(selection: Selection, grapheme: Grapheme) bool {
             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,
+pub const Span = struct {
+    start: u32 = 0,
+    end: u32 = 0,
     tree: *const Tree,
 
-    fn parent(unit: Span) Node {
-        const root_node = unit.tree.rootNode();
-        return root_node.descendantForByteRange(unit.start, unit.end).?;
+    pub fn root(text_len: u32, tree: *const Tree) Span {
+        return .{ .start = 0, .end = text_len, .tree = tree };
+    }
+
+    fn parent(span: Span, text_len: u32) Unit {
+        const root_span = Span.root(text_len, span.tree);
+        if (eql(root_span, span))
+            return .{ .span = span };
+        const root_node = span.tree.rootNode();
+        const root_node_start = root_node.startByte();
+        const root_node_end = root_node.endByte();
+        if (root_node_start == span.start and root_node_end == span.end)
+            return .{ .span = root_span };
+        if (root_node_start > span.start) {
+            const first_span = Span{
+                .start = 0,
+                .end = root_node_start,
+                .tree = span.tree,
+            };
+            return .{
+                .span = if (eql(first_span, span)) root_span else first_span
+            };
+        }
+        if (root_node_end < span.end) {
+            const last_span = Span{
+                .start = root_node_start,
+                .end = text_len,
+                .tree = span.tree,
+            };
+            return .{
+                .span = if (eql(last_span, span)) root_span else last_span
+            };
+        }
+        const parent_node = root_node.descendantForByteRange(span.start,
+                                                             span.end).?;
+        const parent_node_start = parent_node.startByte();
+        assert(parent_node_start <= span.start);
+        const parent_node_end = parent_node.endByte();
+        assert(parent_node_end >= span.end);
+        const parent_span = grow: {
+            if (parent_node.firstChildForByte(span.end)) |next_node| {
+                break :grow if (next_node.prevSibling()) |prev_node| Span{
+                    .start = prev_node.endByte(),
+                    .end = next_node.startByte(),
+                    .tree = span.tree,
+                } else Span{
+                    .start = parent_node_start,
+                    .end = next_node.startByte(),
+                    .tree = span.tree,
+                };
+            } else {
+                const siblings = parent_node.childCount();
+                if (siblings == 0)
+                    return .{ .node = parent_node };
+                const prev_node = parent_node.child(siblings - 1).?;
+                break :grow Span{
+                    .start = prev_node.endByte(),
+                    .end = parent_node_end,
+                    .tree = span.tree,
+                };
+            }
+        };
+        return if (eql(parent_span, span)) .{
+            .node = parent_node
+        } else .{
+            .span = parent_span
+        };
     }
 };
 
@@ -71,51 +127,26 @@ const GraphemeUnit = struct {
         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 parent(unit: GraphemeUnit, text_len: u32) Unit {
+        return Span.parent(.{
+            .start = unit.startByte(),
+            .end = unit.endByte(),
+            .tree = unit.tree,
+        }, text_len);
     }
 
-    fn at(unit: GraphemeUnit, text: []const u8, index: u32) GraphemeUnit {
+    fn at(unit: GraphemeUnit, buffer: Buffer, index: u32) GraphemeUnit {
         return .{
-            .slice = graphemeAt(text, index),
+            .slice = buffer.graphemeAt(index),
             .tree = unit.tree,
         };
     }
 
-    fn before(unit: GraphemeUnit,
-              text: []const u8, index: u32) GraphemeUnit {
-        return .{
-            .slice = graphemeBefore(text, index),
+    fn before(unit: GraphemeUnit, buffer: Buffer, index: u32) ?GraphemeUnit {
+        return if (buffer.graphemeBefore(index)) |grapheme| .{
+            .slice = grapheme,
             .tree = unit.tree,
-        };
+        } else null;
     }
 };
 
@@ -141,103 +172,175 @@ pub const Unit = union(enum) {
         };
     }
 
-    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 };
-            },
-        }
+    fn tree(unit: Unit) *const Tree {
+        return switch (unit) {
+            .node => |node| node.tree,
+            .span => |span| span.tree,
+            .grapheme => |grapheme| grapheme.tree,
+        };
+    }
+
+    fn equal(unit: Unit, other: Unit) bool {
+        return (unit.startByte() == other.startByte()
+                and unit.endByte() == other.endByte());
+    }
+
+    pub fn up(unit: Unit, buffer: Buffer) Unit {
+        const parent = switch (unit) {
+            .node => |node| if (node.parent()) |parent| Unit{
+                .node = parent
+            } else Span.parent(.{
+                .start = node.startByte(),
+                .end = node.endByte(),
+                .tree = node.tree,
+            }, buffer.length()),
+            .span => |span| span.parent(buffer.length()),
+            .grapheme => |grapheme| grapheme.parent(buffer.length()),
+        };
+        return if (!parent.equal(unit) or parent.equal(.{
+            .span = .root(buffer.length(), unit.tree())
+        })) parent else parent.up(buffer);
     }
 
-    pub fn right(unit: Unit, text: []const u8) Unit {
+    pub fn right(unit: Unit, buffer: Buffer) Unit {
         switch (unit) {
-            .node => |node| if (node.nextSibling()) |sibling| {
+            .node => |node| {
                 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,
-                };
+                const root_node = node.tree.rootNode();
+                if (root_node.eql(node)) {
+                    const span = Span{
+                        .start = prev,
+                        .end = buffer.length(),
+                        .tree = node.tree,
+                    };
+                    if (span.start < span.end)
+                        return .{ .span = span };
+                } else if (node.nextSibling()) |sibling| {
+                    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 };
+            .span => |span| switch (unit.up(buffer)) {
+                .node => |parent_node| {
+                    if (parent_node.firstChildForByte(span.end)) |next_node|
+                        return .{ .node = next_node };
+                },
+                .span => |parent_span| {
+                    const root_node = span.tree.rootNode();
+                    if (root_node.startByte() == span.end)
+                        return .{ .node = root_node };
+                    const next_span = Span{
+                        .start = span.end,
+                        .end = parent_span.end,
+                        .tree = span.tree,
+                    };
+                    if (next_span.start < next_span.end)
+                        return .{ .span = next_span };
+                },
+                .grapheme => unreachable,
             },
             .grapheme => |grapheme| {
-                const start = grapheme.endByte();
-                const next_grapheme = grapheme.at(text, start);
-                if (eql(next_grapheme.parent(), grapheme.parent()))
+                const next_grapheme = grapheme.at(buffer, grapheme.endByte());
+                const len = buffer.length();
+                if (next_grapheme.parent(len).equal(grapheme.parent(len)))
                     return .{ .grapheme = next_grapheme };
+                return .right(.{
+                    .span = .{
+                        .start = grapheme.startByte(),
+                        .end = grapheme.endByte(),
+                        .tree = grapheme.tree,
+                    }
+                }, buffer);
             },
         }
-        return .up(unit, text);
+        return unit.up(buffer);
     }
 
-    pub fn left(unit: Unit, text: []const u8) Unit {
+    pub fn left(unit: Unit, buffer: Buffer) Unit {
         switch (unit) {
-            .node => |node| if (node.prevSibling()) |sibling| {
-                const prev = sibling.endByte();
+            .node => |node| {
                 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).? };
+                const root_node = node.tree.rootNode();
+                if (root_node.eql(node)) {
+                    const span = Span{
+                        .start = 0,
+                        .end = next,
+                        .tree = node.tree,
+                    };
+                    if (span.start < span.end)
+                        return .{ .span = span };
+                } else if (node.prevSibling()) |sibling| {
+                    const prev = sibling.endByte();
+                    return switch (order(prev, next)) {
+                        .lt => .{
+                            .span = .{
+                                .start = prev,
+                                .end = next,
+                                .tree = node.tree,
+                            }
+                        },
+                        .eq => .{ .node = sibling },
+                        .gt => unreachable,
+                    };
                 }
             },
+            .span => |span| switch (unit.up(buffer)) {
+                .node => |parent_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();
+                        if (siblings > 0)
+                            return .{
+                                .node =  parent_node.child(siblings - 1).?
+                            };
+                    }
+                },
+                .span => |parent_span| {
+                    const root_node = span.tree.rootNode();
+                    if (root_node.endByte() == span.start)
+                        return .{ .node = root_node };
+                    const prev_span = Span{
+                        .start = span.start,
+                        .end = parent_span.start,
+                        .tree = span.tree,
+                    };
+                    if (prev_span.start < prev_span.end)
+                        return .{ .span = prev_span };
+                },
+                .grapheme => unreachable,
+            },
             .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 };
+                const start = grapheme.startByte();
+                if (grapheme.before(buffer, start)) |prev_grapheme| {
+                    const len = buffer.length();
+                    if (prev_grapheme.parent(len).equal(grapheme.parent(len)))
+                        return .{ .grapheme = prev_grapheme };
+                }
+                return .left(.{
+                    .span = .{
+                        .start = start,
+                        .end = grapheme.endByte(),
+                        .tree = grapheme.tree,
+                    }
+                }, buffer);
             },
         }
-        return .up(unit, text);
+        return unit.up(buffer);
     }
 
-    pub fn down(unit: Unit, text: []const u8) Unit {
+    pub fn down(unit: Unit, buffer: Buffer) Unit {
         switch (unit) {
             .node => |node| {
                 const start = node.startByte();
@@ -245,17 +348,17 @@ pub const Unit = union(enum) {
                 // TODO: history
                 return if (node.child(0)) |child|
                     if (child.startByte() == start and child.endByte() == end)
-                        .down(.{ .node = child }, text)
+                        .down(.{ .node = child }, buffer)
                     else
                         .{ .node = child }
                 else .down(.{
                     .span = .{ .start = start, .end = end, .tree = node.tree }
-                }, text);
+                }, buffer);
             },
             .span => |span| return .{
                 .grapheme = .{
                     // TODO: history
-                    .slice = graphemeAt(text, span.start),
+                    .slice = buffer.graphemeAt(span.start),
                     .tree = span.tree,
                 }
             },