a73x

87544d89

Fix visible selection text extraction

a73x   2026-04-09 18:04


diff --git a/src/main.zig b/src/main.zig
index c5ac0cf..b25ee96 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -554,11 +554,19 @@ fn extractSelectedText(
    span: SelectionSpan,
) ![]u8 {
    const normalized = span.normalized();
    if (row_data.len == 0) return try alloc.alloc(u8, 0);

    const max_row = row_data.len - 1;
    if (normalized.start.row > max_row) return try alloc.alloc(u8, 0);

    const visible_end_row = @min(normalized.end.row, max_row);
    if (normalized.start.row > visible_end_row) return try alloc.alloc(u8, 0);

    var out: std.ArrayListUnmanaged(u8) = .empty;
    errdefer out.deinit(alloc);

    const start_row: usize = @intCast(normalized.start.row);
    const end_row: usize = @intCast(normalized.end.row);
    const end_row: usize = @intCast(visible_end_row);
    var row_idx = start_row;
    while (row_idx <= end_row) : (row_idx += 1) {
        if (row_idx > start_row) try out.append(alloc, '\n');
@@ -592,7 +600,7 @@ fn appendSelectedRowText(

    while (end_col >= start_col) {
        const cell = cells[end_col];
        if (cellContributesVisibleText(cell)) break;
        if (!isTrailingBlankCell(cell)) break;
        if (end_col == 0) return;
        end_col -= 1;
    }
@@ -600,20 +608,26 @@ fn appendSelectedRowText(
    var col = start_col;
    while (col <= end_col) : (col += 1) {
        const cell = cells[col];
        if (!cellContributesVisibleText(cell)) continue;
        try appendCellText(alloc, out, cell);
        try appendSelectedCellText(alloc, out, cell);
    }
}

fn cellContributesVisibleText(cell: anytype) bool {
    return cell.hasText() and cell.wide != .spacer_tail and cell.wide != .spacer_head;
fn isTrailingBlankCell(cell: anytype) bool {
    return !cell.hasText() and cell.wide != .spacer_tail and cell.wide != .spacer_head;
}

fn appendCellText(
fn appendSelectedCellText(
    alloc: std.mem.Allocator,
    out: *std.ArrayListUnmanaged(u8),
    cell: anytype,
) !void {
    if (cell.wide == .spacer_tail or cell.wide == .spacer_head) return;

    if (!cell.hasText()) {
        try out.append(alloc, ' ');
        return;
    }

    const cp = cell.codepoint();
    if (cp == 0) return;

@@ -2410,6 +2424,26 @@ test "extractSelectedText trims trailing blanks on each visible row" {
    try std.testing.expectEqualStrings("ab\nc", text);
}

test "extractSelectedText preserves interior spaces while trimming trailing blanks" {
    var term = try vt.Terminal.init(std.testing.allocator, .{
        .cols = 10,
        .rows = 1,
    });
    defer term.deinit();

    term.write("a b  c  ");
    try term.snapshot();

    const span = SelectionSpan{
        .start = .{ .col = 0, .row = 0 },
        .end = .{ .col = 9, .row = 0 },
    };
    const text = try extractSelectedText(std.testing.allocator, term.render_state.row_data.items(.cells), span);
    defer std.testing.allocator.free(text);

    try std.testing.expectEqualStrings("a b  c  ", text);
}

test "extractSelectedText respects partial first and last rows" {
    var term = try vt.Terminal.init(std.testing.allocator, .{
        .cols = 8,
@@ -2430,6 +2464,26 @@ test "extractSelectedText respects partial first and last rows" {
    try std.testing.expectEqualStrings("bc\nde", text);
}

test "extractSelectedText clamps an offscreen end row to visible rows" {
    var term = try vt.Terminal.init(std.testing.allocator, .{
        .cols = 8,
        .rows = 2,
    });
    defer term.deinit();

    term.write("row0\r\nrow1");
    try term.snapshot();

    const span = SelectionSpan{
        .start = .{ .col = 0, .row = 0 },
        .end = .{ .col = 7, .row = 9 },
    };
    const text = try extractSelectedText(std.testing.allocator, term.render_state.row_data.items(.cells), span);
    defer std.testing.allocator.free(text);

    try std.testing.expectEqualStrings("row0\nrow1", text);
}

test "drainSelectionPipeThenRoundtrip drains large paste before roundtrip" {
    const payload_len: usize = 8192;
    const payload = try std.testing.allocator.alloc(u8, payload_len);