240 lines
8 KiB
Zig
240 lines
8 KiB
Zig
const std = @import("std");
|
|
const cbor = @import("cbor");
|
|
const Buffer = @import("Buffer.zig");
|
|
const View = @import("View.zig");
|
|
const Selection = @import("Selection.zig");
|
|
const Metrics = Buffer.Metrics;
|
|
|
|
row: usize = 0,
|
|
col: usize = 0,
|
|
target: usize = 0,
|
|
|
|
const Self = @This();
|
|
|
|
pub inline fn invalid() Self {
|
|
return .{
|
|
.row = std.math.maxInt(u32),
|
|
.col = std.math.maxInt(u32),
|
|
.target = std.math.maxInt(u32),
|
|
};
|
|
}
|
|
|
|
pub inline fn eql(self: Self, other: Self) bool {
|
|
return self.row == other.row and self.col == other.col;
|
|
}
|
|
|
|
pub inline fn right_of(self: Self, other: Self) bool {
|
|
return if (self.row > other.row) true else if (self.row == other.row and self.col > other.col) true else false;
|
|
}
|
|
|
|
pub fn clamp_to_buffer(self: *Self, root: Buffer.Root, metrics: Metrics) void {
|
|
self.row = @min(self.row, root.lines() - 1);
|
|
self.col = @min(self.col, root.line_width(self.row, metrics) catch 0);
|
|
}
|
|
|
|
fn follow_target(self: *Self, root: Buffer.Root, metrics: Metrics) void {
|
|
self.col = @min(self.target, root.line_width(self.row, metrics) catch 0);
|
|
}
|
|
|
|
fn move_right_no_target(self: *Self, root: Buffer.Root, metrics: Metrics) !void {
|
|
const lines = root.lines();
|
|
if (lines <= self.row) return error.Stop;
|
|
if (self.col < root.line_width(self.row, metrics) catch 0) {
|
|
_, const wcwidth, const offset = root.ecg_at(self.row, self.col, metrics) catch return error.Stop;
|
|
self.col += wcwidth - offset;
|
|
} else if (self.row < lines - 1) {
|
|
self.col = 0;
|
|
self.row += 1;
|
|
} else return error.Stop;
|
|
}
|
|
|
|
pub fn move_right(self: *Self, root: Buffer.Root, metrics: Metrics) !void {
|
|
try self.move_right_no_target(root, metrics);
|
|
self.target = self.col;
|
|
}
|
|
|
|
fn move_left_no_target(self: *Self, root: Buffer.Root, metrics: Metrics) !void {
|
|
if (self.col == 0) {
|
|
if (self.row == 0) return error.Stop;
|
|
self.row -= 1;
|
|
self.col = root.line_width(self.row, metrics) catch 0;
|
|
} else {
|
|
_, const wcwidth, _ = root.ecg_at(self.row, self.col - 1, metrics) catch return error.Stop;
|
|
if (self.col > wcwidth) self.col -= wcwidth else self.col = 0;
|
|
}
|
|
}
|
|
|
|
pub fn move_left(self: *Self, root: Buffer.Root, metrics: Metrics) !void {
|
|
try self.move_left_no_target(root, metrics);
|
|
self.target = self.col;
|
|
}
|
|
|
|
pub fn move_up(self: *Self, root: Buffer.Root, metrics: Metrics) !void {
|
|
if (self.row > 0) {
|
|
self.row -= 1;
|
|
self.follow_target(root, metrics);
|
|
self.move_left_no_target(root, metrics) catch return;
|
|
try self.move_right_no_target(root, metrics);
|
|
} else return error.Stop;
|
|
}
|
|
|
|
pub fn move_down(self: *Self, root: Buffer.Root, metrics: Metrics) !void {
|
|
if (self.row < root.lines() - 1) {
|
|
self.row += 1;
|
|
self.follow_target(root, metrics);
|
|
self.move_left_no_target(root, metrics) catch return;
|
|
try self.move_right_no_target(root, metrics);
|
|
} else return error.Stop;
|
|
}
|
|
|
|
pub fn move_page_up(self: *Self, root: Buffer.Root, view: *const View, metrics: Metrics) void {
|
|
self.row = if (self.row > view.rows) self.row - view.rows else 0;
|
|
self.follow_target(root, metrics);
|
|
self.move_left_no_target(root, metrics) catch return;
|
|
self.move_right_no_target(root, metrics) catch return;
|
|
}
|
|
|
|
pub fn move_page_down(self: *Self, root: Buffer.Root, view: *const View, metrics: Metrics) void {
|
|
if (root.lines() > self.row + view.rows) {
|
|
self.row += view.rows;
|
|
} else self.move_buffer_last(root, metrics);
|
|
self.follow_target(root, metrics);
|
|
self.move_left_no_target(root, metrics) catch return;
|
|
self.move_right_no_target(root, metrics) catch return;
|
|
}
|
|
|
|
pub fn move_half_page_up(self: *Self, root: Buffer.Root, view: *const View, metrics: Metrics) void {
|
|
const half_view_rows = @divTrunc(view.rows, 2);
|
|
self.row = if (self.row > half_view_rows) self.row - half_view_rows else 0;
|
|
self.follow_target(root, metrics);
|
|
self.move_left_no_target(root, metrics) catch return;
|
|
self.move_right_no_target(root, metrics) catch return;
|
|
}
|
|
|
|
pub fn move_half_page_down(self: *Self, root: Buffer.Root, view: *const View, metrics: Metrics) void {
|
|
const half_view_rows = @divTrunc(view.rows, 2);
|
|
if (root.lines() > self.row + half_view_rows) {
|
|
self.row += half_view_rows;
|
|
} else self.move_buffer_last(root, metrics);
|
|
self.follow_target(root, metrics);
|
|
self.move_left_no_target(root, metrics) catch return;
|
|
self.move_right_no_target(root, metrics) catch return;
|
|
}
|
|
|
|
pub fn move_to(self: *Self, root: Buffer.Root, row: usize, col: usize, metrics: Metrics) !void {
|
|
if (row < root.lines()) {
|
|
self.row = row;
|
|
self.col = @min(col, root.line_width(self.row, metrics) catch return error.Stop);
|
|
self.target = self.col;
|
|
} else return error.Stop;
|
|
}
|
|
|
|
pub fn move_abs(self: *Self, root: Buffer.Root, v: *View, y: usize, x: usize, metrics: Metrics) !void {
|
|
self.row = v.row + y;
|
|
self.col = v.col + x;
|
|
self.clamp_to_buffer(root, metrics);
|
|
self.target = self.col;
|
|
}
|
|
|
|
pub fn move_begin(self: *Self) void {
|
|
self.col = 0;
|
|
self.target = self.col;
|
|
}
|
|
|
|
pub fn move_end(self: *Self, root: Buffer.Root, metrics: Metrics) void {
|
|
if (self.row < root.lines()) self.col = root.line_width(self.row, metrics) catch 0;
|
|
self.target = std.math.maxInt(u32);
|
|
}
|
|
|
|
pub fn move_buffer_begin(self: *Self) void {
|
|
self.row = 0;
|
|
self.col = 0;
|
|
self.target = 0;
|
|
}
|
|
|
|
pub fn move_buffer_end(self: *Self, root: Buffer.Root, metrics: Metrics) void {
|
|
self.row = root.lines() - 1;
|
|
self.move_end(root, metrics);
|
|
if (self.col == 0) self.target = 0;
|
|
}
|
|
|
|
fn move_buffer_first(self: *Self, root: Buffer.Root, metrics: Metrics) void {
|
|
self.row = 0;
|
|
self.follow_target(root, metrics);
|
|
}
|
|
|
|
fn move_buffer_last(self: *Self, root: Buffer.Root, metrics: Metrics) void {
|
|
self.row = root.lines() - 1;
|
|
self.follow_target(root, metrics);
|
|
}
|
|
|
|
fn is_at_begin(self: *const Self) bool {
|
|
return self.col == 0;
|
|
}
|
|
|
|
fn is_at_end(self: *const Self, root: Buffer.Root, metrics: Metrics) bool {
|
|
return if (self.row < root.lines()) self.col == root.line_width(self.row, metrics) catch 0 else true;
|
|
}
|
|
|
|
pub fn test_at(self: *const Self, root: Buffer.Root, pred: *const fn (c: []const u8) bool, metrics: Metrics) bool {
|
|
return root.test_at(pred, self.row, self.col, metrics);
|
|
}
|
|
|
|
pub fn write(self: *const Self, writer: Buffer.MetaWriter) !void {
|
|
try cbor.writeValue(writer, .{
|
|
self.row,
|
|
self.col,
|
|
self.target,
|
|
});
|
|
}
|
|
|
|
pub fn extract(self: *Self, iter: *[]const u8) !bool {
|
|
return cbor.matchValue(iter, .{
|
|
cbor.extract(&self.row),
|
|
cbor.extract(&self.col),
|
|
cbor.extract(&self.target),
|
|
});
|
|
}
|
|
|
|
pub fn nudge_insert(self: *Self, nudge: Selection) void {
|
|
if (self.row < nudge.begin.row or (self.row == nudge.begin.row and self.col < nudge.begin.col)) return;
|
|
|
|
const rows = nudge.end.row - nudge.begin.row;
|
|
if (self.row == nudge.begin.row) {
|
|
if (nudge.begin.row < nudge.end.row) {
|
|
self.row += rows;
|
|
self.col = self.col - nudge.begin.col + nudge.end.col;
|
|
} else {
|
|
self.col += nudge.end.col - nudge.begin.col;
|
|
}
|
|
} else {
|
|
self.row += rows;
|
|
}
|
|
self.target = self.col;
|
|
}
|
|
|
|
pub fn nudge_delete(self: *Self, nudge: Selection) bool {
|
|
if (self.row < nudge.begin.row or (self.row == nudge.begin.row and self.col < nudge.begin.col)) return true;
|
|
if (self.row == nudge.begin.row) {
|
|
if (nudge.begin.row < nudge.end.row) {
|
|
return false;
|
|
} else {
|
|
if (self.col < nudge.end.col) {
|
|
return false;
|
|
}
|
|
self.col -= nudge.end.col - nudge.begin.col;
|
|
self.target = self.col;
|
|
return true;
|
|
}
|
|
}
|
|
if (self.row < nudge.end.row) return false;
|
|
if (self.row == nudge.end.row) {
|
|
if (self.col < nudge.end.col) return false;
|
|
self.row -= nudge.end.row - nudge.begin.row;
|
|
self.col -= nudge.end.col;
|
|
self.target = self.col;
|
|
return true;
|
|
}
|
|
self.row -= nudge.end.row - nudge.begin.row;
|
|
return true;
|
|
}
|