diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-28 18:56:04 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:15 -0300 |
| commit | ab806dc38ddcd74958b999fe708ae9ea63609f72 (patch) | |
| tree | 467711a8175956c5726a1181aa6e34de256597a0 /src/edit.zig | |
| parent | c4ab29bce30f427decf3e199eebecfd415714a96 (diff) | |
| download | pardes-ab806dc38ddcd74958b999fe708ae9ea63609f72.tar.gz pardes-ab806dc38ddcd74958b999fe708ae9ea63609f72.zip | |
A mouse sweep selects a stream, as acme's does, not a rectangle
A B1, B2 or B3 drag over several lines took the same columns from every row, a block. acme's sweep is a stream (libframe/frselect.c:115-140, frselectpaint; acme text.c:881, textselect): the first row from the press to its end, the rows between whole, the last from its start to the release. edit.sweepCols says which columns of a row a sweep takes; the captured spans, the text a sweep yields (copy, chords, B2 and B3 operands, a terminal's automatic yank) and the highlight all follow it, in bodies, multi-row tags, terminals and text-rendered PDFs; a row the sweep runs through is highlighted to the edge, and rows after the first leave out the line numbers. Native PDF selection was already MuPDF's stream. No pane keeps a rectangle. exec-bigdrag's golden changes: its middle sweep now runs whole lines.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/edit.zig')
| -rw-r--r-- | src/edit.zig | 72 |
1 files changed, 42 insertions, 30 deletions
diff --git a/src/edit.zig b/src/edit.zig index 84bbb292..3dfe2f20 100644 --- a/src/edit.zig +++ b/src/edit.zig @@ -234,10 +234,13 @@ pub fn capturePointerSelection(p: *Pardes, pane: *Pane, slot: usize) !void { errdefer p.gpa.free(rows); const raw_text = if (raw) try p.gpa.dupe(u8, try selectionText(p, pane, gesture)) else null; const prefix: i32 = if (pane.file != null) panes.File.gutterWidth(pane) else 0; - const left = @max(0, @min(gesture.c0, gesture.c1)); - const right = @max(0, @max(gesture.c0, gesture.c1)); for (rows, 0..) |*row, index| { const visible = first + @as(i32, @intCast(index)); + // A stream: only the first row starts where the press was; the + // others start after the line numbers, and all but the last run on. + const cols = sweepCols(gesture, visible + @as(i32, pane.tag_rows)).?; + const left: i32 = if (cols.first) cols.lo else prefix; + const right: i32 = cols.hi orelse std.math.maxInt(i32) / 2; var source = pane.wrapAt(visible); if (raw) source = .{ .line = panes.Terminal.gridOffset(pane) + visible, .at = 0 }; const line = if (raw) modal.lineSlice(body, @intCast(visible)) else pointerSourceLine(pane, terminal_lines, source.line); @@ -250,7 +253,8 @@ pub fn capturePointerSelection(p: *Pardes, pane: *Pane, slot: usize) !void { row.* = .{ .row = source.line, .lo = lo, - .hi = if (right < prefix) lo else @min(end, modal.nextGrapheme(line, at)), + .hi = if (right < prefix) lo else if (cols.hi == null) end else @min(end, modal.nextGrapheme(line, at)), + .to_edge = cols.hi == null, .raw_terminal = raw, .prompt_bytes = if (raw) panes.Terminal.promptPrefixBytes(pane, source.line, line) else 0, }; @@ -294,40 +298,48 @@ pub fn selectionText(p: *Pardes, pane: *Pane, sl: Pane.Sel) ![]const u8 { } return result; } - const r0 = @min(sl.r0, sl.r1); - const r1 = @max(sl.r0, sl.r1); - const c0: usize = @intCast(@max(0, @min(sl.c0, sl.c1))); - const c1: usize = @intCast(@max(0, @max(sl.c0, sl.c1))); const text = try paneText(p, pane); - var total: usize = 0; - var selected: usize = 0; - var count_it = std.mem.splitAny(u8, text, "\n"); - var count_row: i32 = 0; - while (count_it.next()) |line| : (count_row += 1) { - if (count_row < r0 or count_row > r1) continue; - const b0 = @min(panes.File.renderedLineByteCol(pane, count_row, line, c0), line.len); - const b1 = modal.nextGrapheme(line, @min(panes.File.renderedLineByteCol(pane, count_row, line, c1), line.len)); - total += b1 - b0 + @intFromBool(selected > 0); - selected += 1; - } - const out = try arena.alloc(u8, total); - var at: usize = 0; + var out: std.ArrayList(u8) = .empty; var it = std.mem.splitAny(u8, text, "\n"); var v: i32 = 0; var first = true; while (it.next()) |line| : (v += 1) { - if (v < r0 or v > r1) continue; - if (!first) { - out[at] = '\n'; - at += 1; - } + const cols = sweepCols(sl, v) orelse continue; + if (!first) try out.append(arena, '\n'); first = false; - const b0 = @min(panes.File.renderedLineByteCol(pane, v, line, c0), line.len); - const b1 = modal.nextGrapheme(line, @min(panes.File.renderedLineByteCol(pane, v, line, c1), line.len)); - @memcpy(out[at..][0 .. b1 - b0], line[b0..b1]); - at += b1 - b0; + // Past the first row a body row starts after its line numbers. + const gutter: i32 = if (pane.file != null and v >= pane.tag_rows) panes.File.gutterWidth(pane) else 0; + const lo: usize = @intCast(if (cols.first) cols.lo else gutter); + const b0 = @min(panes.File.renderedLineByteCol(pane, v, line, lo), line.len); + const b1 = if (cols.hi) |hi| + @max(b0, modal.nextGrapheme(line, @min(panes.File.renderedLineByteCol(pane, v, line, @intCast(hi)), line.len))) + else + line.len; + try out.appendSlice(arena, line[b0..b1]); } - return out; + return out.items; +} + +/// A sweep's columns on `row`, in its Sel space: acme's stream, not a +/// rectangle (libframe/frselect.c:115-140, frselectpaint): the first row +/// from the press to its end, the rows between whole, the last from its +/// start to the release. `hi` null runs to the end of the row. +pub const SweepCols = struct { lo: i32, hi: ?i32, first: bool }; + +pub fn sweepCols(sl: Pane.Sel, row: i32) ?SweepCols { + const start, const end = sweepEnds(sl); + if (row < start[0] or row > end[0]) return null; + return .{ + .lo = if (row == start[0]) @max(0, start[1]) else 0, + .hi = if (row == end[0]) @max(0, end[1]) else null, + .first = row == start[0], + }; +} + +/// The sweep's two ends in reading order, each (row, col). +pub fn sweepEnds(sl: Pane.Sel) struct { [2]i32, [2]i32 } { + const fwd = sl.r0 < sl.r1 or (sl.r0 == sl.r1 and sl.c0 <= sl.c1); + return if (fwd) .{ .{ sl.r0, sl.c0 }, .{ sl.r1, sl.c1 } } else .{ .{ sl.r1, sl.c1 }, .{ sl.r0, sl.c0 } }; } pub fn spanHas(r: i32, c: i32, ar: i32, ac: i32, br: i32, bc: i32) bool { |
