diff options
Diffstat (limited to 'src/selection.zig')
-rw-r--r-- | src/selection.zig | 186 |
1 files changed, 186 insertions, 0 deletions
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); + } +}; |