diff options
Diffstat (limited to 'src/Selection.zig')
-rw-r--r-- | src/Selection.zig | 275 |
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), + }; + } +}; |