diff options
| -rw-r--r-- | docs/tags.md | 12 | ||||
| -rw-r--r-- | docs/themes.md | 4 | ||||
| -rw-r--r-- | src/gui/gui.zig | 41 | ||||
| -rw-r--r-- | src/layout.zig | 18 | ||||
| -rw-r--r-- | src/pardes.zig | 86 | ||||
| -rw-r--r-- | test/column_drag.zig | 89 | ||||
| -rw-r--r-- | test/column_grip.py | 132 | ||||
| -rw-r--r-- | test/snapshots/colgrip.golden | 170 | ||||
| -rw-r--r-- | test/snapshots/colgrip.snap | 35 |
9 files changed, 534 insertions, 53 deletions
diff --git a/docs/tags.md b/docs/tags.md index c4e93bc6..8b1d3bf6 100644 --- a/docs/tags.md +++ b/docs/tags.md @@ -10,10 +10,14 @@ Pane and column command text leave a small gap after their aligned drag grips. GUI pane mode symbols are centered by their visible ink; changing the font or its size refreshes the cached measurements. The column grip has a different -color from a pane grip. It stays blank and muted even when its column is active. -Drag it left or right to move the whole column; the -insertion line previews its destination. Only columns between the old and new -positions shift. Each column keeps its width, panes and command text. +color from a pane grip. It stays blank and muted even when its column is active, +and lights in its full accent while it is held. +Drag it past a neighbour's middle to move the whole column there; dropped short +of that, it moves the column's left edge instead, as a border drag would. While +it is held, a dashed rail in the grip's accent stands where the column's left +edge will land, beside the rule as a border drag's rail does. Only columns +between the old and new positions shift. Each column keeps its width, panes and +command text. When Look opens the first document and its source column is too narrow for a new column, it splits below the originating pane, just like `Tty`, instead diff --git a/docs/themes.md b/docs/themes.md index dac389f5..8cc4ff9b 100644 --- a/docs/themes.md +++ b/docs/themes.md @@ -105,8 +105,8 @@ so existing exported themes remain valid. | `tag_active_bg`, `tag_active_fg` | Focused pane tag surface and text | `tag_bg`, `tag_fg` | | `tag_name_fg` | Filename or terminal `Tty` command tint | The corresponding normal or active tag foreground | | `tag_active_name_fg` | Filename or terminal `Tty` tint in active tags | `tag_name_fg`, then the active tag foreground | -| `column_box` | Base accent for the column grip's fallback color | `num` | -| `column_box_dim` | Column drag grip in every focus state | Equal mix of `column_box` and `tag_bg` | +| `column_box` | Column grip while held, and its drag rail | `num` | +| `column_box_dim` | Column drag grip at rest, in every focus state | Equal mix of `column_box` and `tag_bg` | | `border` | Quiet separators | `scroll_track` | | `lineno_active` | Restrained current line number foreground | `lineno` | | `search_bg`, `search_fg` | Search matches, independent of selection | `sel_bg`, `sel_fg` | diff --git a/src/gui/gui.zig b/src/gui/gui.zig index a4cc7672..989f6b25 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -2876,6 +2876,43 @@ const StdinFeed = struct { var seq_start: usize = 0; while (seq_start < len) { + // ESC]777;mouse;<down|up|motion>;<SDL button, 0 for none>;<x>;<y>BEL + // A mouse event at window pixels, built as SDL would and run + // through dispatch, so a driver exercises the real pointer path + // (pixel to cell, tag and body hits). SGR mouse carries only cells. + const mouse_prefix = "\x1b]777;mouse;"; + if (std.mem.startsWith(u8, f.buf[seq_start..len], mouse_prefix)) { + const body = f.buf[seq_start + mouse_prefix.len .. len]; + const end = std.mem.indexOfScalar(u8, body, 0x07) orelse { + std.mem.copyForwards(u8, f.buf[0 .. len - seq_start], f.buf[seq_start..len]); + f.fill = len - seq_start; + break; + }; + if (g) |gp| { + var parts = std.mem.splitScalar(u8, body[0..end], ';'); + const kind = parts.next() orelse ""; + const button = std.fmt.parseInt(u8, parts.next() orelse "", 10) catch 0; + const x = std.fmt.parseFloat(f32, parts.next() orelse "") catch 0; + const y = std.fmt.parseFloat(f32, parts.next() orelse "") catch 0; + var sev = std.mem.zeroes(c.SDL_Event); + if (std.mem.eql(u8, kind, "motion")) { + sev.type = c.SDL_EVENT_MOUSE_MOTION; + sev.motion.x = x; + sev.motion.y = y; + sev.motion.state = if (button == 0 or button > 5) 0 else @as(u32, 1) << @intCast(button - 1); + } else { + sev.type = if (std.mem.eql(u8, kind, "down")) c.SDL_EVENT_MOUSE_BUTTON_DOWN else c.SDL_EVENT_MOUSE_BUTTON_UP; + sev.button.button = button; + sev.button.down = std.mem.eql(u8, kind, "down"); + sev.button.x = x; + sev.button.y = y; + } + dispatch(gp, in, &sev); + out.n_events += 1; + } + seq_start += mouse_prefix.len + end + 1; + continue; + } const prefix = "\x1b]777;finger;"; if (std.mem.startsWith(u8, f.buf[seq_start..len], prefix)) { const body = f.buf[seq_start + prefix.len .. len]; @@ -2909,7 +2946,9 @@ const StdinFeed = struct { seq_start += prefix.len + end + 1; continue; } - if (len - seq_start < prefix.len and std.mem.startsWith(u8, prefix, f.buf[seq_start..len])) { + if ((len - seq_start < prefix.len and std.mem.startsWith(u8, prefix, f.buf[seq_start..len])) or + (len - seq_start < mouse_prefix.len and std.mem.startsWith(u8, mouse_prefix, f.buf[seq_start..len]))) + { std.mem.copyForwards(u8, f.buf[0 .. len - seq_start], f.buf[seq_start..len]); f.fill = len - seq_start; break; diff --git a/src/layout.zig b/src/layout.zig index 8db05a07..3637f30e 100644 --- a/src/layout.zig +++ b/src/layout.zig @@ -713,14 +713,26 @@ pub fn columnBySerial(p: *const Pardes, serial: u32) ?usize { return null; } -pub fn columnMoveTarget(p: *const Pardes, source: usize, x: u16) usize { - if (source >= p.ncol) return source; +/// What releasing column `source`'s grip at `x` does: past a neighbour's +/// middle it takes that column's place; short of it, as in acme, the +/// column's left edge moves to `x - grab`, clamped as setColumnPairWidth +/// clamps it. Still on the grip, or at the leftmost column, nothing moves. +/// The drag preview and the release both ask here, so they cannot disagree. +pub const ColumnDrop = union(enum) { stay, reorder: usize, edge: u16 }; + +pub fn columnDrop(p: *const Pardes, source: usize, x: u16, grab: u16) ColumnDrop { + if (source >= p.ncol) return .stay; var insertion: usize = 0; for (0..p.ncol) |column| { if (column == source) continue; if (x >= p.col_x[column] + p.col_w[column] / 2) insertion += 1; } - return insertion; + if (insertion != source) return .{ .reorder = insertion }; + if (source == 0 or (x >= p.col_x[source] and x < p.col_x[source] + config.GUTTER)) return .stay; + if (p.col_w[source - 1] + p.col_w[source] < 2 * config.MINW) return .stay; + const lo = p.col_x[source - 1] + config.MINW; + const hi = (p.col_x[source] + p.col_w[source]) -| config.MINW; + return .{ .edge = std.math.clamp(x -| grab, lo, @max(lo, hi)) }; } /// Share out the width of columns `left` and `left + 1` so the left one is diff --git a/src/pardes.zig b/src/pardes.zig index f968511a..4786bd70 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -5155,7 +5155,7 @@ const Drag = union(enum) { move: struct { id: usize, cur_x: u16, cur_y: u16 }, /// `grab`: how far into the grip it was pressed, so the column's left /// edge lands at `cur_x - grab`. A release still on the grip moves nothing. - column_move: struct { column: usize, serial: u32, cur_x: u16, grab: u16 = 0 }, + column_move: struct { serial: u32, cur_x: u16, grab: u16 = 0 }, tag: struct { id: usize }, /// chorded: a 1-2/1-3 cut/paste chord fired during this left drag — /// the drag's own release is then inert @@ -7793,7 +7793,7 @@ pub const Pardes = struct { .key => |key| p.handleKey(key), .mouse => |m| { if (m.kind == .release) switch (p.drag) { - .border_v, .border_h, .move => p.presentation.snap_once = true, + .border_v, .border_h, .move, .column_move => p.presentation.snap_once = true, else => {}, }; p.handleMouse(m); @@ -12017,7 +12017,7 @@ pub const Pardes = struct { if (mcol < p.col_x[column] or mcol >= p.col_x[column] + @min(config.GUTTER, p.col_w[column])) continue; if (m.button == config.select_button) { p.header_drag = false; - p.drag = .{ .column_move = .{ .column = column, .serial = p.col_serial[column], .cur_x = mcol, .grab = mcol - p.col_x[column] } }; + p.drag = .{ .column_move = .{ .serial = p.col_serial[column], .cur_x = mcol, .grab = mcol - p.col_x[column] } }; } return; } @@ -12488,15 +12488,10 @@ pub const Pardes = struct { }, .border_h => |d| if (d.cur_y != seamRowOf(p, d.col, d.top_idx)) layout.applyRowSplit(p, d.col, d.top_idx, d.cur_y), - .column_move => |d| if (layout.columnBySerial(p, d.serial)) |column| { - const target = layout.columnMoveTarget(p, column, d.cur_x); - if (target != column) { - layout.reorderColumn(p, column, target); - } else if (column > 0 and (d.cur_x < p.col_x[column] or d.cur_x >= p.col_x[column] + config.GUTTER)) { - // Dropped short of another column's place: as in acme, - // the column's left edge moves to where it was dropped. - layout.setColumnPairWidth(p, column - 1, (d.cur_x -| d.grab) -| p.col_x[column - 1]); - } + .column_move => |d| if (layout.columnBySerial(p, d.serial)) |column| switch (layout.columnDrop(p, column, d.cur_x, d.grab)) { + .stay => {}, + .reorder => |target| layout.reorderColumn(p, column, target), + .edge => |x| layout.setColumnPairWidth(p, column - 1, x - p.col_x[column - 1]), }, .move => |d| { layout.movePane(p, d.id, d.cur_x, d.cur_y); @@ -14091,7 +14086,10 @@ pub const Pardes = struct { const active_col = if (layout.findPane(p, p.active)) |f| f.col == c else false; const bg = if (active_col and p.settings.focus_tint) chrome.tag_active_bg else chrome.tag_bg; const fg = if (active_col and p.settings.focus_tint) chrome.tag_active_fg else chrome.tag_fg; - const grip_bg = chrome.column_box_dim; + // A grip is muted until it is grabbed; then it lights, as a pane's + // box does for the active pane, so the drag shows what it holds. + const grabbed = p.drag == .column_move and layout.columnBySerial(p, p.drag.column_move.serial) == c; + const grip_bg = if (grabbed) chrome.column_box else chrome.column_box_dim; const lum = (@as(u16, grip_bg[0]) * 3 + @as(u16, grip_bg[1]) * 6 + @as(u16, grip_bg[2])) / 10; const ink: [3]u8 = if (lum > 140) .{ 0, 0, 0 } else .{ 255, 255, 255 }; const grip_style: CellStyle = .{ .bg = .{ .rgb = grip_bg }, .fg = .{ .rgb = ink }, .font_role = .tagline }; @@ -14106,7 +14104,8 @@ pub const Pardes = struct { .bg = .{ .rgb = bg }, .font_role = .tagline, }); - if (p.pointer_inside and p.hover_row == p.topBarHeight() and p.hover_col >= x and p.hover_col < x + w) { + // A carried column's pointer is aiming at a place, not a word. + if (p.pointer_inside and p.drag != .column_move and p.hover_row == p.topBarHeight() and p.hover_col >= x and p.hover_col < x + w) { const bar = p.columnTag(c); if (wordBoundsAtCol(bar, panes.File.rawAtDisplay(bar, p.hover_col - x + bar_scroll))) |bounds| { var col = panes.File.rawDisplayCol(bar, bounds.lo) -| bar_scroll; @@ -14154,33 +14153,38 @@ pub const Pardes = struct { var col = p.col_x[d.col]; while (col < p.col_x[d.col] + p.col_w[d.col]) : (col += 1) s.overlayDash(col, d.cur_y, "╌"); }, - .column_move => |d| { - const source = layout.columnBySerial(p, d.serial) orelse d.column; - const target = layout.columnMoveTarget(p, source, d.cur_x); - if (source < p.ncol and target != source) { - const boundary = if (target < source) p.col_x[target] else p.col_x[target] + p.col_w[target]; - const x = @min(boundary, s.cols -| 1); - // Leave the outside insertion rail visible above compact - // text layers. This clips presentation only, never reflows. - if (boundary == s.cols) { - for (&s.body_layers) |*layer| if (layer.rows > 0 and layer.viewport.x + layer.viewport.w == s.cols) { - layer.viewport.w -|= 1; - }; - for (&s.tag_layers) |*layer| if (layer.cols > 0 and layer.viewport.x + layer.viewport.w == s.cols and layer.viewport.y >= p.topBarHeight()) { - layer.viewport.w -|= 1; - }; - } - var row: u16 = p.topBarHeight(); - while (row < s.rows) : (row += 1) s.overlayDash(x, row, "╎"); - } else if (source > 0 and source < p.ncol and (d.cur_x < p.col_x[source] or d.cur_x >= p.col_x[source] + config.GUTTER) and - p.col_w[source - 1] + p.col_w[source] >= 2 * config.MINW) - { - // Where the left edge would land: the border drag's rail. - const lo = p.col_x[source - 1] + config.MINW; - const hi = (p.col_x[source] + p.col_w[source]) -| config.MINW; - const x = @min(std.math.clamp(d.cur_x -| d.grab, lo, @max(lo, hi)), s.cols -| 1); - var row: u16 = p.topBarHeight(); - while (row < s.rows) : (row += 1) s.overlayDash(x, row, "╎"); + .column_move => |d| rail: { + const source = layout.columnBySerial(p, d.serial) orelse break :rail; + // Where the column's left edge would land, reordered or not. + const edge: u16 = switch (layout.columnDrop(p, source, d.cur_x, d.grab)) { + .stay => break :rail, + .reorder => |target| if (target < source) p.col_x[target] else p.col_x[target] + p.col_w[target], + .edge => |x| x, + }; + // Leave the outside insertion rail visible above compact + // text layers. This clips presentation only, never reflows. + if (edge == s.cols) { + for (&s.body_layers) |*layer| if (layer.rows > 0 and layer.viewport.x + layer.viewport.w == s.cols) { + layer.viewport.w -|= 1; + }; + for (&s.tag_layers) |*layer| if (layer.cols > 0 and layer.viewport.x + layer.viewport.w == s.cols and layer.viewport.y >= p.topBarHeight()) { + layer.viewport.w -|= 1; + }; + } + // The rail runs in the seam cell left of the edge, where a + // border drag's does: the edge cell itself is a gutter, which + // the GUI paints as a scroll rail that swallowed the dashes. + // At the window's left edge there is no seam, so it runs just + // past that scroll rail instead. It takes the lit grip's + // color: this is where the grabbed column lands. + const x = @min(if (edge == 0) config.GUTTER - 1 else edge - 1, s.cols -| 1); + var row: u16 = p.topBarHeight(); + while (row < s.rows) : (row += 1) { + // Past the left edge's scroll rail the rail would cut + // through column 0's grip and pane boxes: leave them whole. + if (edge == 0 and s.at(x, row).style.font_role == .tagline) continue; + s.overlayDash(x, row, "╎"); + s.at(x, row).style.fg = .{ .rgb = chrome.column_box }; } }, .move => |d| if (d.cur_x < s.cols) { diff --git a/test/column_drag.zig b/test/column_drag.zig index 85dca66b..b2b8f35d 100644 --- a/test/column_drag.zig +++ b/test/column_drag.zig @@ -176,7 +176,7 @@ test "column grip is separate from compact text and the resize seam" { p.update(.{ .mouse = .{ .button = .left, .kind = .release, .col = seam, .row = body_row } }); } -test "column grips stay blank and muted across focus hover and drag" { +test "column grips stay blank, and muted across focus and hover until grabbed" { const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 80, .rows = 20 }); defer p.deinit(); _ = try p.setTestFile("left\n"); @@ -198,11 +198,12 @@ test "column grips stay blank and muted across focus hover and drag" { _ = frame.reset(.retain_capacity); const surface = try p.render(frame.allocator()); const muted: pardes.Color = .{ .rgb = p.chromeTheme().column_box_dim }; + const lit: pardes.Color = .{ .rgb = p.chromeTheme().column_box }; for (0..p.ncol) |column| { for (0..config.GUTTER) |offset| { const cell = surface.at(p.col_x[column] + @as(u16, @intCast(offset)), pardes.TOPBAR_H); try std.testing.expectEqualStrings(" ", cell.grapheme()); - try std.testing.expectEqual(muted, cell.style.bg); + try std.testing.expectEqual(if (dragging and column == active_column) lit else muted, cell.style.bg); try std.testing.expectEqual(pardes.FontRole.tagline, cell.style.font_role); } try std.testing.expectEqual(p.col_x[column] + pardes.TAG_TEXT_INSET, (try tagLayer(p, column)).viewport.x); @@ -252,3 +253,87 @@ test "column grip dropped in its own place moves the column's left edge" { try std.testing.expectEqual(serials[1], p.col_serial[0]); try std.testing.expectEqual(serials[0], p.col_serial[1]); } + +/// The column of the drag preview's rail, checked to run the full height +/// below the workspace tag in the grabbed grip's color, except through the +/// grip and pane boxes at the window's left edge. +fn previewRail(p: *Pardes, frame: *std.heap.ArenaAllocator) !?u16 { + _ = frame.reset(.retain_capacity); + const surface = try p.render(frame.allocator()); + const lit: pardes.Color = .{ .rgb = p.chromeTheme().column_box }; + var found: ?u16 = null; + for (0..surface.cols) |x| { + if (!std.mem.eql(u8, surface.at(@intCast(x), surface.rows - 1).grapheme(), "╎")) continue; + try std.testing.expect(found == null); + found = @intCast(x); + for (p.topBarHeight()..surface.rows) |row| { + const cell = surface.at(@intCast(x), @intCast(row)); + if (x < config.GUTTER and cell.style.font_role == .tagline) { + try std.testing.expect(!std.mem.eql(u8, cell.grapheme(), "╎")); + continue; + } + try std.testing.expectEqualStrings("╎", cell.grapheme()); + try std.testing.expectEqual(lit, cell.style.fg); + } + } + return found; +} + +test "column grip drag previews where the column lands, in the seam beside its rule" { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 120, .rows = 24 }); + defer p.deinit(); + _ = try p.setTestFile("body\n"); + p.presentation.enabled = false; + p.settings.column_tags = true; + for (0..2) |_| { + try std.testing.expect(p.executeBuiltinLine(p.active, "New")); + try std.testing.expect(layout.splitColumn(p, p.active, p.active, false)); + } + try std.testing.expectEqual(@as(usize, 3), p.ncol); + p.update(.tick); + var frame = std.heap.ArenaAllocator.init(p.gpa); + defer frame.deinit(); + _ = try p.render(frame.allocator()); + const serials = .{ p.col_serial[0], p.col_serial[1], p.col_serial[2] }; + + // Held on its own grip it lands nowhere new: no rail. + mouse(p, .press, p.col_x[1]); + mouse(p, .drag, p.col_x[1] + 1); + try std.testing.expectEqual(@as(?u16, null), try previewRail(p, &frame)); + // Over the first column's left half it goes first. The window edge has + // no seam, and the gutter there is the GUI's scroll rail, so the rail + // runs in the gutter's last cell. + mouse(p, .drag, 3); + try std.testing.expectEqual(@as(?u16, config.GUTTER - 1), try previewRail(p, &frame)); + // The pointer is over the first column's "New", but a carried column + // aims at a place: no word lights under it. + for (0..p.screen_w) |x| try std.testing.expect(!std.meta.eql(p.surface.at(@intCast(x), pardes.TOPBAR_H).style.bg, pardes.Color{ .rgb = p.theme().sel_bg })); + mouse(p, .release, 3); + try std.testing.expectEqual(serials[1], p.col_serial[0]); + try std.testing.expectEqual(serials[0], p.col_serial[1]); + + // Past the middle column's middle it lands after it: the rail is in the + // seam cell left of the last column's rule, where a border drag's is. + mouse(p, .press, p.col_x[0]); + const past = p.col_x[1] + p.col_w[1] - 2; + mouse(p, .drag, past); + try std.testing.expectEqual(@as(?u16, p.col_x[2] - 1), try previewRail(p, &frame)); + // Past the last column's middle: the window's last cell. + mouse(p, .drag, p.col_x[2] + p.col_w[2] - 1); + try std.testing.expectEqual(@as(?u16, p.screen_w - 1), try previewRail(p, &frame)); + mouse(p, .release, p.col_x[2] + p.col_w[2] - 1); + try std.testing.expectEqual(serials[0], p.col_serial[0]); + try std.testing.expectEqual(serials[2], p.col_serial[1]); + try std.testing.expectEqual(serials[1], p.col_serial[2]); + + // Short of a neighbour's middle the left edge moves, and the preview + // is the seam that edge will have: exactly where the release puts it. + const edge = p.col_x[2]; + mouse(p, .press, edge + 1); + mouse(p, .drag, edge + 1 - 6); + try std.testing.expectEqual(@as(?u16, edge - 6 - 1), try previewRail(p, &frame)); + mouse(p, .release, edge + 1 - 6); + _ = try p.render(frame.allocator()); + try std.testing.expectEqual(edge - 6, p.col_x[2]); + try std.testing.expectEqual(@as(?u16, null), try previewRail(p, &frame)); +} diff --git a/test/column_grip.py b/test/column_grip.py new file mode 100644 index 00000000..ac0341c3 --- /dev/null +++ b/test/column_grip.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +"""Drag a column grip in the real SDL window with pixel mouse events. + +WAYLAND_DISPLAY=/run/user/1000/wayland-1 \\ + python3 -B test/column_grip.py zig-out/bin/pardes-gui /tmp/pardes-column-grip [--pad 7] + +The window is hidden (PARDES_TEST). Mouse events enter as ESC]777;mouse +sequences on stdin, which the GUI builds into SDL events and dispatches like +its own, so pixel-to-cell and tag hit testing run as they do under a hand. +9P observes the grid; each step leaves a PNG of the GPU frame in the output +directory: grip-rest, grip-resize-drag, grip-reorder-drag, grip-done. +""" +import argparse +import os +from pathlib import Path +import shutil +import subprocess +import time + +from fs import session +from ui_review import require, wait_for + + +def rows(client): + frame = client.screen() + cols = frame['cols'] + return [''.join(cell[0] for cell in frame['cells'][at:at + cols]) for at in range(0, len(frame['cells']), cols)] + + +def rail(client): + """The one column the drag preview's rail runs down, if any.""" + bottom = rows(client)[-1] + found = [x for x, ch in enumerate(bottom) if ch == '╎'] + require(len(found) <= 1, f'more than one rail: {found}') + return found[0] if found else None + + +def grab(root, label): + time.sleep(.5) + def convert(): + shutil.copyfile(root / 'latest.ppm', root / (label + '.ppm')) + return subprocess.run(['magick', str(root / (label + '.ppm')), str(root / (label + '.png'))], + stderr=subprocess.DEVNULL).returncode == 0 + require(wait_for(convert, 8), 'GPU capture did not contain a complete image') + return root / (label + '.png') + + +def pixel(path, x, y): + out = subprocess.run(['magick', str(path), '-format', f'%[pixel:p{{{x},{y}}}]', 'info:'], + capture_output=True, text=True, check=True) + return out.stdout.strip() + + +def run(args): + root = args.output.resolve() + shutil.rmtree(root, ignore_errors=True) + root.mkdir(parents=True, mode=0o700) + (root / 'config/pardes').mkdir(parents=True) + (root / 'config/pardes/init').write_text('Shell /bin/sh\n') + (root / 'w.txt').write_text('alpha\nbeta\n') + name = 'column-grip-' + str(os.getpid()) + launch = ['-c', 'stty cols 100 rows 30; exec "$@"', 'column-grip', + str(args.binary.resolve()), '--9p=' + name, str(root / 'w.txt')] + env = {'PARDES_TEST': '1', 'PARDES_TEST_CAPTURE_DIR': str(root), 'PARDES_TEST_COLS': '100', + 'PARDES_TEST_ROWS': '30', 'PARDES_TEST_PAD': str(args.pad)} + handles = {} + with session('/bin/sh', root, name, socket_name=name, tty=True, launch=launch, + inherited=env, terminal=handles) as (client, _): + def send(data): + os.write(handles['input_fd'], data.encode()) + + def mouse(kind, button, col, row): + # the middle of a grid cell, in window pixels + send(f'\x1b]777;mouse;{kind};{button};{(col + .5) * cw};{(row + .5) * ch}\x07') + time.sleep(.15) + + require(wait_for(lambda: 'Newcol' in rows(client)[0]), 'workspace tag never drew') + send('\x1bn') + time.sleep(.3) + send('\x1bc') + require(wait_for(lambda: rows(client)[1].count('New Tty') == 2), 'no second column') + require(wait_for(lambda: (root / 'latest.ppm').exists(), 8), 'GPU capture missing') + time.sleep(.5) + with open(root / 'latest.ppm', 'rb') as ppm: + ppm.readline() + width, height = map(int, ppm.readline().split()) + frame = client.screen() + cw = (width - args.pad) // frame['cols'] + ch = (height - args.pad) // frame['rows'] + edge = rows(client)[1].index('New Tty', 10) - 3 + + rest = grab(root, 'grip-rest') + mouse('down', 1, edge, 1) + held = grab(root, 'grip-held') + require(pixel(held, int((edge + 1) * cw), int(1.5 * ch)) != pixel(rest, int((edge + 1) * cw), int(1.5 * ch)), + 'a grabbed grip looks the same as a resting one') + require(rail(client) is None, 'a grip held in place previews a move') + + # Short of the left column's middle: the left edge follows, and the + # rail is the seam cell left of where the rule will be. + for col in range(edge, edge - 11, -1): + mouse('motion', 1, col, 1) + require(rail(client) == edge - 11, f'resize rail at {rail(client)}, wanted {edge - 11}') + grab(root, 'grip-resize-drag') + mouse('up', 1, edge - 10, 1) + require(wait_for(lambda: rows(client)[1].index('New Tty', 10) - 3 == edge - 10), 'left edge did not follow the grip') + edge -= 10 + + # Over the left column's left half: it goes first, and the rail runs + # just past the left window edge's scroll rail. + mouse('down', 1, edge, 1) + for col in range(edge, 4, -5): + mouse('motion', 1, col, 10) + require(rail(client) == 1, f'reorder rail at {rail(client)}, wanted 1') + grab(root, 'grip-reorder-drag') + mouse('up', 1, 5, 10) + require(wait_for(lambda: 'alpha' in rows(client)[3][50:]), 'the column did not move first') + require(rail(client) is None, 'the rail outlived the drag') + grab(root, 'grip-done') + print('ok', root) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('binary', type=Path) + parser.add_argument('output', type=Path) + parser.add_argument('--pad', type=int, default=0, help='leftover pixels past the grid (PARDES_TEST_PAD)') + run(parser.parse_args()) + + +if __name__ == '__main__': + main() diff --git a/test/snapshots/colgrip.golden b/test/snapshots/colgrip.golden new file mode 100644 index 00000000..e096a665 --- /dev/null +++ b/test/snapshots/colgrip.golden @@ -0,0 +1,170 @@ +== style rest grid=100x24 +|0: 0-70 #b2c3b8,#1d2824, 71-99 d,#1d2824, +|1: 0-1 #ffffff,#887455, 2-2 d,#1d2824, 3-34 #b2c3b8,#1d2824, 35-49 d,#1d2824, 50-51 #ffffff,#887455, 52-52 d,#26362d, 53-84 #becfc4,#26362d, 85-99 d,#26362d, +|2: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-31 #b2c3b8,#1d2824, 32-36 #d3b0d9,#1d2824, 37-49 #b2c3b8,#1d2824, 50-51 #000000,#add38a, 52-52 d,#26362d, 53-81 #becfc4,#26362d, 82-84 #dfbce5,#26362d, 85-99 #becfc4,#26362d, +|3: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-7 #e5eee5,#0d1410, 8-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|4: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|5: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-49 d,#0d1410, 50-50 d,#608269, 51-99 d,#0d1410, +|6: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|7: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|8: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|9: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|10: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|11: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|12: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|13: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|16: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|17: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|18: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|19: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|20: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|21: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|22: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|23: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +== style held grid=100x24 +|1: 0-1 #ffffff,#887455, 2-2 d,#1d2824, 3-34 #b2c3b8,#1d2824, 35-49 d,#1d2824, 50-51 #000000,#f4c086, 52-52 d,#26362d, 53-84 #becfc4,#26362d, 85-99 d,#26362d, +== snap resize-drag grid=100x24 cursor=54,5 +|Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Kill +| New Tty Find Grep Joincol Delcol ╎ New Tty Find Grep Joincol Delcol +| /tmp/pardes-snap/colgrip/cwd/w.txt S╎ve Tty Col /tmp/pardes-snap/colgrip/cwd Tty Save Mode Filt +| 1 x ╎ ls +| ╎ w.txt +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +== style resize-drag grid=100x24 +|1: 0-1 #ffffff,#887455, 2-2 d,#1d2824, 3-34 #b2c3b8,#1d2824, 35-38 d,#1d2824, 39-39 #f4c086,#1d2824, 40-49 d,#1d2824, 50-51 #000000,#f4c086, 52-52 d,#26362d, 53-84 #becfc4,#26362d, 85-99 d,#26362d, +|2: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-31 #b2c3b8,#1d2824, 32-36 #d3b0d9,#1d2824, 37-38 #b2c3b8,#1d2824, 39-39 #f4c086,#1d2824, 40-49 #b2c3b8,#1d2824, 50-51 #000000,#add38a, 52-52 d,#26362d, 53-81 #becfc4,#26362d, 82-84 #dfbce5,#26362d, 85-99 #becfc4,#26362d, +|3: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-7 #e5eee5,#0d1410, 8-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|4: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|5: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-99 d,#0d1410, +|6: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|7: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|8: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|9: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|10: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|11: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|12: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|13: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|14: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|15: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|16: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|17: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|18: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|19: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|20: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|21: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|22: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +|23: 0-0 d,#608269, 1-1 d,#0d1410, 2-6 #59675c,#0d1410, 7-38 d,#0d1410, 39-39 #f4c086,#0d1410, 40-49 d,#0d1410, 50-50 d,#608269, 51-51 d,#0d1410, 52-99 #e5eee5,#0d1410, +== snap resize-done grid=100x24 cursor=44,5 +|1: New Tty Find Grep Joincol Delcol New Tty Find Grep Joincol Delcol +|2: /tmp/pardes-snap/colgrip/cwd/w.txt Sa /tmp/pardes-snap/colgrip/cwd Tty Save Mode Filter Collaps +|3: 1 x ls +|4: w.txt +|5: +|6: +|7: +|8: +|9: +|10: +|11: +|12: +|13: +|14: +|15: +|16: +|17: +|18: +|19: +|20: +|21: +|22: +|23: +== snap reorder-drag grid=100x24 cursor=44,5 +|3: ╎ 1 x ls +|4: ╎ w.txt +|5: ╎ +|6: ╎ +|7: ╎ +|8: ╎ +|9: ╎ +|10: ╎ +|11: ╎ +|12: ╎ +|13: ╎ +|14: ╎ +|15: ╎ +|16: ╎ +|17: ╎ +|18: ╎ +|19: ╎ +|20: ╎ +|21: ╎ +|22: ╎ +|23: ╎ +== style reorder-drag grid=100x24 +|1: 0-1 #ffffff,#887455, 2-2 d,#1d2824, 3-34 #b2c3b8,#1d2824, 35-39 d,#1d2824, 40-41 #000000,#f4c086, 42-42 d,#26362d, 43-74 #becfc4,#26362d, 75-99 d,#26362d, +|2: 0-1 #ffffff,#496656, 2-2 d,#1d2824, 3-31 #b2c3b8,#1d2824, 32-36 #d3b0d9,#1d2824, 37-39 #b2c3b8,#1d2824, 40-41 #000000,#add38a, 42-42 d,#26362d, 43-71 #becfc4,#26362d, 72-74 #dfbce5,#26362d, 75-99 #becfc4,#26362d, +|3: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-7 #e5eee5,#0d1410, 8-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|4: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|5: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-39 d,#0d1410, 40-40 d,#608269, 41-99 d,#0d1410, +|6: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|7: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|8: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|9: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|10: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|11: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|12: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|13: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|14: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|15: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|16: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|17: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|18: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|19: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|20: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|21: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|22: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +|23: 0-0 d,#608269, 1-1 #f4c086,#0d1410, 2-6 #59675c,#0d1410, 7-39 d,#0d1410, 40-40 d,#608269, 41-41 d,#0d1410, 42-99 #e5eee5,#0d1410, +== snap reorder-done grid=100x24 cursor=4,5 +|1: New Tty Find Grep Joincol Delcol New Tty Find Grep Joincol Delcol +|2: /tmp/pardes-snap/colgrip/cwd Tty Save Mode Filter Collaps /tmp/pardes-snap/colgrip/cwd/w.txt Sa +|3: ls 1 x +|4: w.txt +|5: +|6: +|7: +|8: +|9: +|10: +|11: +|12: +|13: +|14: +|15: +|16: +|17: +|18: +|19: +|20: +|21: +|22: +|23: diff --git a/test/snapshots/colgrip.snap b/test/snapshots/colgrip.snap new file mode 100644 index 00000000..c2b403ed --- /dev/null +++ b/test/snapshots/colgrip.snap @@ -0,0 +1,35 @@ +# Column grips: the two cells left of a column tag carry the column. A grabbed +# grip lights, and a rail in its color stands where the column will land. +# The right column is at x 50; its grip is cells 51-52 (1-based) on row 2. +file w.txt x +start 24 100 w.txt +wait 8000 Newcol Joincol +wait 8000 w.txt +stable 700 20000 +key a-n +key a-c +stable 700 15000 +snapstyle rest +# held on its own grip: lit, and no rail, since it lands where it is +press left 51 2 +stable 400 5000 +snapstyle held +# dropped short of the left column's middle, the left edge moves: the rail is +# in the seam cell left of where the column's rule will be +drag left 41 2 +stable 400 5000 +snap resize-drag +snapstyle resize-drag +release left 41 2 +stable 700 15000 +snap resize-done +# carried over the left column's left half it goes first; the window edge +# has no seam, so the rail runs in the gutter's last cell +press left 41 2 +drag left 10 2 +stable 400 5000 +snap reorder-drag +snapstyle reorder-drag +release left 10 2 +stable 700 15000 +snap reorder-done |
