diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/pdf_scroll_bench.zig | 567 | ||||
| -rw-r--r-- | test/snapshots/cornerdrag-left.golden | 155 | ||||
| -rw-r--r-- | test/snapshots/cornerdrag-left.snap | 41 | ||||
| -rw-r--r-- | test/snapshots/grep.golden | 2 | ||||
| -rw-r--r-- | test/snapshots/grep.snap | 13 | ||||
| -rw-r--r-- | test/snapshots/jumps.snap | 35 | ||||
| -rw-r--r-- | test/snapshots/stepgrain.golden | 310 | ||||
| -rw-r--r-- | test/snapshots/stepgrain.snap | 77 | ||||
| -rw-r--r-- | test/snapshots/ttytaken.golden | 124 | ||||
| -rw-r--r-- | test/snapshots/ttytaken.snap | 43 |
10 files changed, 1341 insertions, 26 deletions
diff --git a/test/pdf_scroll_bench.zig b/test/pdf_scroll_bench.zig new file mode 100644 index 00000000..7bd644e4 --- /dev/null +++ b/test/pdf_scroll_bench.zig @@ -0,0 +1,567 @@ +//! ReleaseFast scoreboard for FAST scrolling of the continuous PDF strip. +//! +//! zig build pdf-scroll-bench -Doptimize=ReleaseFast +//! zig build pdf-scroll-bench -Doptimize=ReleaseFast -- --json +//! zig build pdf-scroll-bench -Doptimize=ReleaseFast -- --warmup 1 --reps 15 +//! zig build pdf-scroll-bench -Doptimize=ReleaseFast -- --path other.pdf +//! +//! What a fling IS, and why one-tick-per-frame benchmarks miss it: a shell +//! pump drains every input the OS queued since the last present and then calls +//! render() once. A trackpad fling therefore arrives as a BATCH of wheel +//! notches inside one frame, and the frame budget is spent on the batch plus +//! the single render — not on one notch. Every timed region below is exactly +//! that: `ticks_per_frame` real `Mouse.wheel_down`/`wheel_up` presses through +//! `Pardes.update`, one `render()` into a reset frame arena, and the effect +//! drain, which is the loop src/tty/tty.zig and src/gui/gui.zig both run. +//! +//! Rows are per-FRAME latency, not per-notch: a dropped frame is what a reader +//! feels, and the batch is indivisible from the reader's point of view. +//! +//! Two checksums, because they answer two different questions: +//! +//! `visual` is the no-regression proof. Frame count, emitted image count, +//! every placement rectangle, the presented RGBA (strided every +//! frame, in full every `full_rgba_stride`-th frame), and the +//! final scroll offset and page. Any optimization that changes +//! ONE presented pixel or ONE placement changes this. It must be +//! bit-identical across builds. +//! `transport` is the texture-upload trace: the (page, raster generation) +//! pair per emitted image, with generations rebased on the +//! counter's value at run start. Caching work legitimately +//! changes this while `visual` stays fixed — reusing a retained +//! raster keeps its old generation, which is precisely the +//! retransmit the revision mechanism exists to suppress. +//! +//! Setup, teardown, checksumming, and reporting are outside every timer. +const std = @import("std"); +const libc = std.c; +const pardes = @import("pardes"); +const pdf = @import("mupdf"); +const bench_config = @import("pdf_scroll_bench_config"); + +pub const std_options: std.Options = .{ .log_level = .err }; + +const gpa = std.heap.smp_allocator; + +// A reading-sized window. Wide enough that fit-width pages are taller than the +// viewport (so a fling genuinely crosses page boundaries) and small enough +// that the Kitty raster policy's 1200px cap is the binding quality term, which +// is what the tty shell actually ships. +const bench_cols: u16 = 120; +const bench_rows: u16 = 40; +const bench_cell_pixels: pardes.CellPixels = .{ .w = 8, .h = 16 }; + +/// config.wheel_rows is one cell per notch, so a notch is `cell_pixels.h` +/// display pixels. 192 notches is ~3072px — between two and three fit-width +/// pages of travel inside a single frame, i.e. a hard flick whose momentum the +/// OS delivers as one burst. This is the number that makes the benchmark about +/// flinging rather than about nudging. +const fling_ticks_per_frame: usize = 192; +const slow_ticks_per_frame: usize = 1; +/// The control row is a fixed wall of frames rather than a whole-document +/// traversal: one notch per frame over a real document is tens of thousands of +/// frames, and 240 already crosses several page boundaries at reading speed. +const slow_frames: usize = 240; +/// Guard against a pathological document turning one row into a multi-minute +/// run. Deterministic either way: the frame count is derived from layout. +const max_frames: usize = 2048; +/// Full-RGBA hashing is O(document) per frame, so it samples the run instead of +/// every frame; the strided hash below runs on every frame and every image. +const full_rgba_stride: usize = 8; +const checksum_seed: u64 = 0x7061_7264_6573_5343; + +const Config = struct { + path: []const u8 = "docs/design.pdf", + json: bool = false, + warmup: usize = 2, + reps: usize = 25, +}; + +/// The bit-exact description of one scrolling run. `visual` is the invariant; +/// see the file header for why `transport` is reported beside it rather than +/// folded into it. +const Identity = struct { + frames: u64, + images: u64, + visual: u64, + transport: u64, + + fn eql(a: Identity, b: Identity) bool { + return a.frames == b.frames and a.images == b.images and + a.visual == b.visual and a.transport == b.transport; + } +}; + +const Result = struct { + name: []const u8, + ticks_per_frame: usize, + frames: usize, + min_ns: u64, + median_ns: u64, + p90_ns: u64, + p99_ns: u64, + max_ns: u64, + total_ns: u64, + rasterizations: u64, + rasters_retained: u64, + identity: Identity, +}; + +const Scenario = enum { + /// Sustained high-velocity travel from page zero to the last page. + fling_down, + /// Down to the end and straight back up. Worst case for any cache that + /// only retains what the current frame shows: the way back finds nothing. + fling_reverse, + /// One notch per frame. The control: it must not regress. + slow_scroll, + + fn label(scenario: Scenario) []const u8 { + return switch (scenario) { + .fling_down => "fling_down", + .fling_reverse => "fling_reverse", + .slow_scroll => "slow_scroll", + }; + } + + fn ticks(scenario: Scenario) usize { + return switch (scenario) { + .fling_down, .fling_reverse => fling_ticks_per_frame, + .slow_scroll => slow_ticks_per_frame, + }; + } +}; + +const Plan = struct { + scenario: Scenario, + frames: usize, + /// Frames of downward travel before `fling_reverse` turns around. + down_frames: usize, + + fn button(plan: Plan, frame: usize) pardes.Mouse.Button { + return switch (plan.scenario) { + .fling_reverse => if (frame < plan.down_frames) .wheel_down else .wheel_up, + .fling_down, .slow_scroll => .wheel_down, + }; + } +}; + +pub fn main(init: std.process.Init) !void { + if (!bench_config.release_fast_core) + fatal("the production core must be ReleaseFast; add -Doptimize=ReleaseFast", .{}); + const args = try init.minimal.args.toSlice(init.arena.allocator()); + const config = parseArgs(args); + + // One untimed probe boot supplies the strip height every plan is sized + // from, so the frame counts below are a property of the document and the + // viewport rather than of the machine. + const travel = probeTravel(config.path); + + const results = [_]Result{ + try measure(config, planFor(.fling_down, travel)), + try measure(config, planFor(.fling_reverse, travel)), + try measure(config, planFor(.slow_scroll, travel)), + }; + + if (config.json) + reportJson(init.io, config, &results) + else + reportText(config, &results); +} + +fn parseArgs(args: []const []const u8) Config { + var config: Config = .{}; + var i: usize = 1; + while (i < args.len) : (i += 1) { + if (std.mem.eql(u8, args[i], "--json")) { + config.json = true; + } else if (std.mem.eql(u8, args[i], "--warmup") and i + 1 < args.len) { + i += 1; + config.warmup = parseCount("--warmup", args[i]); + } else if (std.mem.eql(u8, args[i], "--reps") and i + 1 < args.len) { + i += 1; + config.reps = parseCount("--reps", args[i]); + } else if (std.mem.eql(u8, args[i], "--path") and i + 1 < args.len) { + i += 1; + config.path = args[i]; + } else fatal( + "usage: pardes-pdf-scroll-bench [--json] [--warmup N] [--reps N] [--path FILE]", + .{}, + ); + } + return config; +} + +fn parseCount(flag: []const u8, text: []const u8) usize { + const value = std.fmt.parseUnsigned(usize, text, 10) catch + fatal("{s} expects a positive integer", .{flag}); + if (value == 0 or value > 10_000) + fatal("{s} must be in 1..10000", .{flag}); + return value; +} + +const Travel = struct { + /// Scrollable display pixels: the whole strip minus one screenful. + max_scroll: f64, + /// Display pixels one fling frame's notch batch covers. + per_frame: f64, +}; + +fn probeTravel(path: []const u8) Travel { + const core = bootCore(path) catch |err| + fatal("cannot boot a PDF pane on {s}: {t}", .{ path, err }); + defer core.deinit(); + var frame_arena = std.heap.ArenaAllocator.init(gpa); + defer frame_arena.deinit(); + _ = core.render(frame_arena.allocator()) catch |err| + fatal("first render failed: {t}", .{err}); + drainEffects(core); + const pane = core.panes[0] orelse fatal("PDF pane disappeared", .{}); + if (pane.pdf == null) fatal("{s} did not open as a PDF", .{path}); + const pv = &pane.pdf.?; + if (pv.page_count < 4) + fatal("{s} has {d} pages; a fling benchmark needs at least 4", .{ path, pv.page_count }); + if (!pv.layout_valid or pv.document_height == 0) + fatal("the pixel strip never laid out; is native_images/cell_pixels wired?", .{}); + // Exactly the clamp scrollPdfDocument enforces, so the last fling frame + // lands on the document end instead of adding a no-op frame to the row. + const viewport_h: u64 = @as(u64, bench_rows -| pardes.BOX_H) * bench_cell_pixels.h; + const notch: f64 = @floatFromInt(bench_cell_pixels.h); + return .{ + .max_scroll = @max(notch, @as(f64, @floatFromInt(pv.document_height -| viewport_h))), + .per_frame = notch * @as(f64, @floatFromInt(fling_ticks_per_frame)), + }; +} + +fn planFor(scenario: Scenario, travel: Travel) Plan { + const down = @min( + max_frames, + @as(usize, @intFromFloat(@ceil(travel.max_scroll / travel.per_frame))), + ); + return switch (scenario) { + .fling_down => .{ .scenario = scenario, .frames = down, .down_frames = down }, + .fling_reverse => .{ .scenario = scenario, .frames = down * 2, .down_frames = down }, + .slow_scroll => .{ .scenario = scenario, .frames = slow_frames, .down_frames = slow_frames }, + }; +} + +/// A pane on a real multi-page document with the native pixel-strip path live +/// and a genuine pixel viewport delivered the way a shell delivers one. +fn bootCore(path: []const u8) !*pardes.Pardes { + const core = try pardes.Pardes.init(gpa, .{ + .file = path, + .cols = bench_cols, + .rows = bench_rows, + }); + errdefer core.deinit(); + core.native_images = true; + core.update(.{ .resize = .{ + .cols = bench_cols, + .rows = bench_rows, + .cell_pixels = bench_cell_pixels, + } }); + drainEffects(core); + return core; +} + +fn measure(config: Config, plan: Plan) !Result { + const name = plan.scenario.label(); + const ticks = plan.scenario.ticks(); + const totals = try gpa.alloc(u64, config.reps); + defer gpa.free(totals); + const pool = try gpa.alloc(u64, config.reps * plan.frames); + defer gpa.free(pool); + var pool_len: usize = 0; + const recorder = try gpa.create(Recorder); + defer gpa.destroy(recorder); + var expected: ?Identity = null; + var rasterizations: u64 = 0; + var rasters_retained: u64 = 0; + + for (0..config.warmup + config.reps) |round| { + const core = try bootCore(config.path); + defer core.deinit(); + var frame_arena = std.heap.ArenaAllocator.init(gpa); + defer frame_arena.deinit(); + // Untimed: the steady state a reader flings FROM already has page zero + // resident and the strip laid out. + _ = try core.render(frame_arena.allocator()); + drainEffects(core); + const pane = core.panes[0] orelse fatal("PDF pane disappeared", .{}); + const pv = &pane.pdf.?; + const rect = core.rects[0]; + // Anywhere inside the pane body resolves to this pane's hover, which + // is what routes the notch; the exact cell is irrelevant to a wheel. + const wheel: pardes.Mouse = .{ + .button = .wheel_down, + .kind = .press, + .col = rect.x + rect.w / 2, + .row = rect.y + rect.h / 2, + }; + const base_revision = pv.next_raster_revision; + recorder.reset(base_revision); + + var total: u64 = 0; + for (0..plan.frames) |frame| { + var event = wheel; + event.button = plan.button(frame); + const started = nowNs(); + for (0..ticks) |_| core.update(.{ .mouse = event }); + _ = frame_arena.reset(.retain_capacity); + const surface = core.render(frame_arena.allocator()) catch |err| + fatal("render failed in {s} frame {d}: {t}", .{ name, frame, err }); + drainEffects(core); + const elapsed = nowNs() -| started; + total += elapsed; + recorder.observe(frame, surface); + if (round >= config.warmup) { + pool[pool_len] = @max(1, elapsed); + pool_len += 1; + } + } + + const identity = recorder.finish(pv); + try verifyIdentity(name, &expected, identity, round); + if (round >= config.warmup) { + totals[round - config.warmup] = total; + rasterizations = pv.next_raster_revision -% base_revision; + rasters_retained = pv.rasters_len; + } + } + + std.debug.assert(pool_len == pool.len); + std.mem.sort(u64, pool, {}, std.sort.asc(u64)); + std.mem.sort(u64, totals, {}, std.sort.asc(u64)); + return .{ + .name = name, + .ticks_per_frame = ticks, + .frames = plan.frames, + .min_ns = pool[0], + .median_ns = pool[pool.len / 2], + .p90_ns = pool[(pool.len * 9) / 10 -| 1], + .p99_ns = pool[(pool.len * 99) / 100 -| 1], + .max_ns = pool[pool.len - 1], + .total_ns = totals[totals.len / 2], + .rasterizations = rasterizations, + .rasters_retained = rasters_retained, + .identity = expected.?, + }; +} + +/// Accumulates the two checksums across one run. Raster generations are +/// rebased on the counter's value at run start so the trace describes the +/// ORDER textures were produced in rather than absolute counter values, which +/// depend on how much the untimed boot happened to rasterize. +const Recorder = struct { + frames: u64, + images: u64, + visual: u64, + transport: u64, + base_revision: u32, + + fn reset(self: *Recorder, base_revision: u32) void { + self.* = .{ + .frames = 0, + .images = 0, + .visual = checksum_seed, + .transport = checksum_seed, + .base_revision = base_revision, + }; + } + + fn observe(self: *Recorder, frame: usize, surface: *const pardes.Surface) void { + self.frames += 1; + self.visual = mix(self.visual, surface.nimages); + self.transport = mix(self.transport, surface.nimages); + const full = frame % full_rgba_stride == 0; + for (surface.images[0..surface.nimages]) |maybe| { + const place = maybe orelse { + self.visual = mix(self.visual, std.math.maxInt(u64)); + continue; + }; + self.images += 1; + self.visual = mix(self.visual, place.pane); + self.visual = mix(self.visual, place.x); + self.visual = mix(self.visual, place.y); + self.visual = mix(self.visual, place.w); + self.visual = mix(self.visual, place.h); + self.visual = mix(self.visual, place.iw); + self.visual = mix(self.visual, place.ih); + self.visual = mix(self.visual, place.rgba.len); + self.visual = mix(self.visual, place.native.page); + self.visual = mix(self.visual, @intFromEnum(place.native.fit)); + self.visual = mix(self.visual, place.native.pan_x); + self.visual = mix(self.visual, place.native.pan_y); + self.visual = mix(self.visual, @as(u32, @bitCast(place.native.pixel_offset_y))); + if (place.native.geometry) |geometry| { + inline for (.{ geometry.src, geometry.dst }) |rect| { + self.visual = mix(self.visual, rect.x); + self.visual = mix(self.visual, rect.y); + self.visual = mix(self.visual, rect.w); + self.visual = mix(self.visual, rect.h); + } + } else self.visual = mix(self.visual, std.math.maxInt(u64)); + self.visual = if (full) + std.hash.Wyhash.hash(self.visual, place.rgba) + else + stridedHash(self.visual, place.rgba); + self.transport = mix(self.transport, place.native.page); + self.transport = mix(self.transport, place.native.revision -% self.base_revision); + } + } + + fn finish(self: *Recorder, pv: anytype) Identity { + var visual = mix(self.visual, @as(u64, @bitCast(pv.document_scroll_y))); + visual = mix(visual, pv.page); + visual = mix(visual, pv.page_count); + visual = mix(visual, pv.document_height); + return .{ + .frames = self.frames, + .images = self.images, + .visual = visual, + .transport = self.transport, + }; + } +}; + +/// 256 fixed-stride RGBA words. Cheap enough for every image of every frame +/// while still failing on any change that touches a band of the page, and the +/// full hash above closes the gap on sampled frames. +fn stridedHash(seed: u64, rgba: []const u8) u64 { + if (rgba.len < 4) return mix(seed, rgba.len); + const stride = @max(@as(usize, 4), (rgba.len / 256) & ~@as(usize, 3)); + var hash = seed; + var at: usize = 0; + while (at + 4 <= rgba.len) : (at += stride) + hash = std.hash.Wyhash.hash(hash, rgba[at..][0..4]); + return hash; +} + +fn mix(seed: u64, value: anytype) u64 { + const Value = @TypeOf(value); + const Stable = switch (@typeInfo(Value)) { + .comptime_int => u64, + else => Value, + }; + var stable: Stable = value; + return std.hash.Wyhash.hash(seed, std.mem.asBytes(&stable)); +} + +fn drainEffects(core: *pardes.Pardes) void { + while (core.nextEffect()) |_| {} +} + +fn verifyIdentity( + name: []const u8, + expected: *?Identity, + got: Identity, + round: usize, +) !void { + if (expected.*) |want| { + if (!want.eql(got)) { + std.debug.print( + "pdf-scroll-bench: unstable identity in {s} round {d}:\n" ++ + " expected frames={d} images={d} visual={x:0>16} transport={x:0>16}\n" ++ + " got frames={d} images={d} visual={x:0>16} transport={x:0>16}\n", + .{ + name, round, want.frames, want.images, + want.visual, want.transport, got.frames, got.images, + got.visual, got.transport, + }, + ); + return error.UnstableIdentity; + } + } else expected.* = got; +} + +fn nowNs() u64 { + var ts: std.c.timespec = undefined; + _ = std.c.clock_gettime(.MONOTONIC, &ts); + return @as(u64, @intCast(ts.sec)) *| 1_000_000_000 +| + @as(u64, @intCast(ts.nsec)); +} + +fn reportText(config: Config, results: []const Result) void { + std.debug.print("pardes PDF fast-scroll benchmark (ReleaseFast)\n", .{}); + std.debug.print( + "warmup: {d}, sampled reps: {d}; every number is ONE FRAME (notch batch + render + drain)\n\n", + .{ config.warmup, config.reps }, + ); + std.debug.print("{s:<16} {s:>6} {s:>7} {s:>10} {s:>10} {s:>10} {s:>10} {s:>10} {s:>11} {s:>7} {s:>5} {s:<16} {s}\n", .{ + "scenario", "frames", "notches", "min", + "median", "p90", "p99", "max", + "total_us", "rasters", "kept", "visual", + "transport", + }); + std.debug.print("{s}\n", .{"-" ** 160}); + for (results) |result| std.debug.print( + "{s:<16} {d:>6} {d:>7} {d:>10} {d:>10} {d:>10} {d:>10} {d:>10} {d:>11} {d:>7} {d:>5} {x:0>16} {x:0>16}\n", + .{ + result.name, + result.frames, + result.ticks_per_frame, + result.min_ns, + result.median_ns, + result.p90_ns, + result.p99_ns, + result.max_ns, + result.total_ns / 1000, + result.rasterizations, + result.rasters_retained, + result.identity.visual, + result.identity.transport, + }, + ); + std.debug.print( + "\n`rasters` counts MuPDF page rasterizations over one run; `kept` is the retained raster count at the end.\n" ++ + "`visual` pins presented pixels and placement and must never change; `transport` traces texture uploads.\n", + .{}, + ); +} + +const json_report_max_bytes = 16 * 1024; + +fn reportJson(io: std.Io, config: Config, results: []const Result) void { + var storage: [json_report_max_bytes]u8 = undefined; + var out: std.Io.Writer = .fixed(&storage); + out.print( + "{{\"benchmark\":\"pardes-pdf-scroll\",\"build\":\"ReleaseFast\",\"warmup\":{d},\"reps\":{d},\"results\":[", + .{ config.warmup, config.reps }, + ) catch return; + for (results, 0..) |result, i| out.print( + "{s}{{\"scenario\":\"{s}\",\"frames\":{d},\"notches_per_frame\":{d}," ++ + "\"min_ns\":{d},\"median_ns\":{d},\"p90_ns\":{d},\"p99_ns\":{d},\"max_ns\":{d},\"total_ns\":{d}," ++ + "\"rasterizations\":{d},\"rasters_retained\":{d},\"images\":{d}," ++ + "\"visual\":\"{x:0>16}\",\"transport\":\"{x:0>16}\"}}", + .{ + if (i == 0) "" else ",", + result.name, + result.frames, + result.ticks_per_frame, + result.min_ns, + result.median_ns, + result.p90_ns, + result.p99_ns, + result.max_ns, + result.total_ns, + result.rasterizations, + result.rasters_retained, + result.identity.images, + result.identity.visual, + result.identity.transport, + }, + ) catch return; + out.writeAll("]}\n") catch return; + std.Io.File.stdout().writeStreamingAll(io, out.buffered()) catch {}; +} + +fn fatal(comptime format: []const u8, args: anytype) noreturn { + std.debug.print("pdf-scroll-bench: " ++ format ++ "\n", args); + std.process.exit(1); +} + +comptime { + // The bench links the real MuPDF wrapper the core uses; keep the import + // load-bearing so a build that silently dropped it fails here. + _ = pdf.Document; +} diff --git a/test/snapshots/cornerdrag-left.golden b/test/snapshots/cornerdrag-left.golden new file mode 100644 index 00000000..b87651b1 --- /dev/null +++ b/test/snapshots/cornerdrag-left.golden @@ -0,0 +1,155 @@ +== snap right-split grid=100x30 cursor=54,8 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| /tmp/pardes-snap/cornerdrag-left/cwd New Del /tmp/pardes-snap/cornerdrag-left/cwd New Del +| ls ls +| w.txt w.txt +| +| /tmp/pardes-snap/cornerdrag-left/cwd New Del +| ls +| w.txt +| +| +| +| +| +| +| +| +| /tmp/pardes-snap/cornerdrag-left/cwd New Del +| ls +| w.txt +| +| +| +| +| +| +| +| +| +| +| +== snap hover-corner grid=100x30 cursor=54,8 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| /tmp/pardes-snap/cornerdrag-left/cwd New Del ╎ /tmp/pardes-snap/cornerdrag-left/cwd New Del +| ls ╎ ls +| w.txt ╎ w.txt +| ╎╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ +| ╎ /tmp/pardes-snap/cornerdrag-left/cwd New Del +| ╎ ls +| ╎ w.txt +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| /tmp/pardes-snap/cornerdrag-left/cwd New Del ╎ +| ls ╎ +| w.txt ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +== snap hover-left-corner grid=100x30 cursor=54,8 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| /tmp/pardes-snap/cornerdrag-left/cwd New Del ╎ /tmp/pardes-snap/cornerdrag-left/cwd New Del +| ls ╎ ls +| w.txt ╎ w.txt +| ╎ +| ╎ /tmp/pardes-snap/cornerdrag-left/cwd New Del +| ╎ ls +| ╎ w.txt +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +|╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ +| /tmp/pardes-snap/cornerdrag-left/cwd New Del ╎ +| ls ╎ +| w.txt ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +== snap corner-drag grid=100x30 cursor=54,8 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| /tmp/pardes-snap/cornerdrag╎left/cwd New Del /tmp/pardes-snap/cornerdrag-left/cwd New Del +| ls ╎ ls +| w.txt ╎ w.txt +| ╎ +| ╎ /tmp/pardes-snap/cornerdrag-left/cwd New Del +| ╎ ls +| ╎ w.txt +| ╎ +| ╎ +| ╎ +| ╎╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ +| ╎ +| ╎ +| ╎ +| ╎ +| /tmp/pardes-snap/cornerdrag╎left/cwd New Del +| ls ╎ +| w.txt ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +| ╎ +== snap corner-done grid=100x30 cursor=34,15 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| /tmp/pardes-snap/cornerdrag- /tmp/pardes-snap/cornerdrag-left/cwd New Del +| ls ls +| w.txt w.txt +| +| +| +| +| +| +| +| +| /tmp/pardes-snap/cornerdrag-left/cwd New Del +| ls +| w.txt +| +| /tmp/pardes-snap/cornerdrag- +| ls +| w.txt +| +| +| +| +| +| +| +| +| +| +| diff --git a/test/snapshots/cornerdrag-left.snap b/test/snapshots/cornerdrag-left.snap new file mode 100644 index 00000000..c90dbc76 --- /dev/null +++ b/test/snapshots/cornerdrag-left.snap @@ -0,0 +1,41 @@ +# corner grabs on the RIGHT column's side of the v border. The v handle is the +# LEFT column's last cell, so that column's own h hint runs THROUGH it while the +# right column's hints start one cell further right — the crossing there reads as +# a T butting into the junction rather than a full cross, and it is grabbable all +# the same: one drag moves the column edge and the RIGHT column's pane edge. +# Layout: windowops' -n 3 (col 0 x 0 w 50, panes on rows 1-15 and 16-29; col 1 +# x 50 w 50), then focus right and Alt-n so col 1 holds two panes as well, split +# at ITS own row. The two corners on col 49 are then row 16 (col 0's seam, the +# one that already worked) and row 5 (col 1's, the one this script is about). +file w.txt x +start 30 100 -n 3 +wait 8000 w.txt +stable 700 20000 +# a second pane in the RIGHT column +key c-w l +stable 400 5000 +key a-n +stable 700 15000 +snap right-split +# hover col 1's corner: the v hint runs full height AND col 1's h hint lights, +# from col 51 rightward. Before this that row was dark on the handle — only the +# hovered column's seam lit, and the hovered cell belongs to col 0 +motion 50 5 +stable 400 5000 +snap hover-corner +# CONTROL: col 0's own seam is still a left-column corner, its h hint drawn +# through the handle from col 1 to the handle itself, unchanged +motion 50 16 +stable 400 5000 +snap hover-left-corner +# grab col 1's corner and drag down-left: the column preview and col 1's row +# preview both follow the one mouse, the row dashes lying to the RIGHT of the v +# line because they are the right column's +press left 50 5 +drag left 30 12 +stable 400 5000 +snap corner-drag +release left 30 12 +motion 45 25 +stable 500 8000 +snap corner-done diff --git a/test/snapshots/grep.golden b/test/snapshots/grep.golden index 846ab703..af4c4708 100644 --- a/test/snapshots/grep.golden +++ b/test/snapshots/grep.golden @@ -153,7 +153,7 @@ | 2 a.txt:3:6-11 last TARGET line | 3 sub/b.txt:2:1-6 TARGET inside sub | 4 -== snap nomatch grid=180x30 cursor=37,28 +== snap nomatch grid=180x30 cursor=62,3 |New Newcol Find Grep Help Tutor Dump NextColor Debug Kill | /tmp/pardes-snap/grep/cwd/a.txt Save New Del /tmp/pardes-snap/grep/cwd New Del /tmp/pardes-snap/grep/cwd New Del | 1 hello TARGET one ls ls diff --git a/test/snapshots/grep.snap b/test/snapshots/grep.snap index 1d3efc9e..ab708a25 100644 --- a/test/snapshots/grep.snap +++ b/test/snapshots/grep.snap @@ -6,7 +6,8 @@ # looking it SELECTS the hit — with the path RELATIVE to the directory # of the pane that asked — the same directory its results buffer is named in, # so the short name resolves; only a hit from outside that tree stays absolute. -# n/N SELECT the next/prev row like any other result list and open nothing; +# n/N SELECT the next/prev row like any other result list and open nothing — +# one press per row, the location at its head, never the matched text after it; # Enter looks what they leave selected, and a row pointing into a file nothing # has open yet opens it. dirmk sub @@ -62,10 +63,12 @@ wait 10000 TARGET inside sub stable 700 15000 snap opened-hit # a pattern nothing holds: an empty list of its own, with nothing in it for n -# to stop on — so the ring carries that step back into the TARGET list. The -# look above is why both of those read the way they do: it left the cursor in -# sub/b.txt, so the empty list is named in sub/ rather than cwd/, and it left -# the hit SELECTED there (a ranged result row is one a look selects). +# to stop on — so the ring carries that step out of it and on round. It lands +# in the SHELL, whose `ls` row is free text and steps word by word: the TARGET +# list is standing on its last row, and a row-grained walk has nothing left in +# it to offer. The look above is why these read the way they do: it left the +# cursor in sub/b.txt, so the empty list is named in sub/ rather than cwd/, and +# it left the hit SELECTED there (a ranged result row is one a look selects). # Armed from the LEADER, not the topbar word, because of that selection: a # topbar execute chords whatever is selected into its argument, so a # middle-click here would run `Grep TARGET` and never ask for a pattern at diff --git a/test/snapshots/jumps.snap b/test/snapshots/jumps.snap index db131bd4..d7d44967 100644 --- a/test/snapshots/jumps.snap +++ b/test/snapshots/jumps.snap @@ -64,19 +64,20 @@ snap last-back key space j l stable 700 15000 snap jumplist -# n/N select the next/prev look-able span and Enter looks it, exactly as they -# do a +Search: the stepped span is selected in the buffer (snapstyle) and the -# Enter after it moves the pane the row names. Row 1 is the shell (no path, so +# n/N select the next/prev row and Enter looks it, exactly as they do a +# +Search: the stepped span is selected in the buffer (snapstyle) and the Enter +# after it moves the pane the row names. Row 1 is the shell (no path, so # `@pN`), row 2 the file at line 1, row 4 the file at line 100 — so the body # visibly rides the list. # -# SPANS, not rows, and this buffer is the one place in the suite where the two -# differ: an `@pN` row's trailing text is the pane's DIRECTORY, which is a -# look-able word of its own, so row 1 and row 3 each hold TWO stops and -# reaching the next row takes two steps. A file row's text is its content line -# (`line 1`), which resolves to nothing, so those hold one. Stepping words -# rather than rows is what makes the walk the same motion in a results buffer -# and in the middle of a paragraph, and this is the price of that. +# ONE PRESS PER ROW, and this buffer is where that rule earns itself: an `@pN` +# row's trailing text is the pane's DIRECTORY, which is a look-able word of its +# own, so rows 1 and 3 used to hold TWO stops each and reaching the next row +# took two steps. A results buffer is a LIST — its rows are locations, and the +# text after one says what is THERE rather than naming another place to go — so +# the walk takes the location at the head of a row and moves on to the next row +# (output_pane.Grain.line). Free text still steps every word: that is the +# terminal walk in ttylook, and it is why the two goldens differ. # # An `@pN` row moves focus like any other, which it did not use to: the STEP # only selects now, and the look Enter runs goes to the pane by the same route @@ -86,26 +87,20 @@ key enter stable 700 15000 snap step1-shell snapstyle step1-shell -# past the directory on row 1, then onto row 2 -key n -stable 400 5000 +# on to row 2 — one press, the directory on row 1 is no longer a stop of its own key n key enter stable 700 15000 snap step2-file -# row 3 is the shell again, both its spans, and then row 4 -key n -stable 400 5000 +# row 3 is the shell again, then row 4 key n stable 400 5000 key n key enter stable 700 15000 snap step4-line100 -# N is the exact inverse: the same two spans of row 3 in the other order, so -# two presses land back on its location and Enter follows it to the shell -key N -stable 400 5000 +# N is the exact inverse: one press back onto row 3's location, and Enter +# follows it to the shell key N key enter stable 700 15000 diff --git a/test/snapshots/stepgrain.golden b/test/snapshots/stepgrain.golden new file mode 100644 index 00000000..f269c865 --- /dev/null +++ b/test/snapshots/stepgrain.golden @@ -0,0 +1,310 @@ +== snap results grid=150x30 cursor=7,2 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| /tmp/pardes-snap/stepgrain/cwd/a.txt Save New Del +| 1 hello ZQMARK one +| 2 see also sub/b.txt for ZQMARK two +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| /tmp/pardes-snap/stepgrain/cwd/+Search New Del +| 1 a.txt:1:7-12 hello ZQMARK one +| 2 a.txt:2:24-29 see also sub/b.txt for ZQMARK two +| 3 sub/b.txt:1:1-6 ZQMARK inside sub +== snap step-row1 grid=150x30 cursor=7,27 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| /tmp/pardes-snap/stepgrain/cwd/a.txt Save New Del +| 1 hello ZQMARK one +| 2 see also sub/b.txt for ZQMARK two +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| /tmp/pardes-snap/stepgrain/cwd/+Search New Del +| 1 a.txt:1:7-12 hello ZQMARK one +| 2 a.txt:2:24-29 see also sub/b.txt for ZQMARK two +| 3 sub/b.txt:1:1-6 ZQMARK inside sub +== style step-row1 +|0: 0-56 #949494,#121212, 57-149 d,#121212, +|1: 0-1 #ffffff,#262626, 2-129 #949494,#121212, 130-149 d,#121212, +|2: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-22 #bdbdbd,#080808, 23-149 d,#080808, +|3: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-39 #bdbdbd,#080808, 40-149 d,#080808, +|4: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|5: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|6: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|7: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|8: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|9: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|10: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|11: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|12: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|13: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|14: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|15: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|16: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|17: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|18: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|19: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|20: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|21: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|22: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|23: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|24: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|25: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|26: 0-1 #ffffff,#626262, 2-129 #949494,#121212, 130-149 d,#121212, +|27: 0-0 d,#444444, 1-1 d,#080808, 2-6 #949494,#121212, 7-18 #080808,#80a0ff, 19-35 #bdbdbd,#080808, 36-149 d,#080808, +|28: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-53 #bdbdbd,#080808, 54-149 d,#080808, +|29: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-39 #bdbdbd,#080808, 40-149 d,#080808, +== snap step-row2 grid=150x30 cursor=7,28 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| /tmp/pardes-snap/stepgrain/cwd/a.txt Save New Del +| 1 hello ZQMARK one +| 2 see also sub/b.txt for ZQMARK two +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| /tmp/pardes-snap/stepgrain/cwd/+Search New Del +| 1 a.txt:1:7-12 hello ZQMARK one +| 2 a.txt:2:24-29 see also sub/b.txt for ZQMARK two +| 3 sub/b.txt:1:1-6 ZQMARK inside sub +== style step-row2 +|0: 0-56 #949494,#121212, 57-149 d,#121212, +|1: 0-1 #ffffff,#262626, 2-129 #949494,#121212, 130-149 d,#121212, +|2: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-22 #bdbdbd,#080808, 23-149 d,#080808, +|3: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-39 #bdbdbd,#080808, 40-149 d,#080808, +|4: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|5: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|6: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|7: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|8: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|9: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|10: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|11: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|12: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|13: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|14: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|15: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|16: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|17: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|18: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|19: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|20: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|21: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|22: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|23: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|24: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|25: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|26: 0-1 #ffffff,#626262, 2-129 #949494,#121212, 130-149 d,#121212, +|27: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-35 #bdbdbd,#080808, 36-149 d,#080808, +|28: 0-0 d,#444444, 1-1 d,#080808, 2-6 #949494,#121212, 7-19 #080808,#80a0ff, 20-53 #bdbdbd,#080808, 54-149 d,#080808, +|29: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-39 #bdbdbd,#080808, 40-149 d,#080808, +== snap step-row3 grid=150x30 cursor=7,28 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| /tmp/pardes-snap/stepgrain/cwd/a.txt Save New Del +| 1 hello ZQMARK one +| 2 see also sub/b.txt for ZQMARK two +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| /tmp/pardes-snap/stepgrain/cwd/+Search New Del +| 2 a.txt:2:24-29 see also sub/b.txt for ZQMARK two +| 3 sub/b.txt:1:1-6 ZQMARK inside sub +| 4 +== snap step-wrap grid=150x30 cursor=7,27 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| /tmp/pardes-snap/stepgrain/cwd/a.txt Save New Del +| 1 hello ZQMARK one +| 2 see also sub/b.txt for ZQMARK two +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| /tmp/pardes-snap/stepgrain/cwd/+Search New Del +| 1 a.txt:1:7-12 hello ZQMARK one +| 2 a.txt:2:24-29 see also sub/b.txt for ZQMARK two +| 3 sub/b.txt:1:1-6 ZQMARK inside sub +== snap blank-in-path grid=150x30 cursor=7,25 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| /tmp/pardes-snap/stepgrain/cwd/a.txt Save New Del +| 1 hello ZQMARK one +| 2 see also sub/b.txt for ZQMARK two +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| /tmp/pardes-snap/stepgrain/cwd/+Search New Del +| 1 two words.txt +| /tmp/pardes-snap/stepgrain/cwd/+Search New Del +| 1 a.txt:1:7-12 hello ZQMARK one +| 2 a.txt:2:24-29 see also sub/b.txt for ZQMARK two +| 3 sub/b.txt:1:1-6 ZQMARK inside sub +== style blank-in-path +|0: 0-56 #949494,#121212, 57-149 d,#121212, +|1: 0-1 #ffffff,#262626, 2-129 #949494,#121212, 130-149 d,#121212, +|2: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-22 #bdbdbd,#080808, 23-149 d,#080808, +|3: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-39 #bdbdbd,#080808, 40-149 d,#080808, +|4: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|5: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|6: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|7: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|8: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|9: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|10: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|11: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|12: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|13: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|14: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|15: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|16: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|17: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|18: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|19: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|20: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|21: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|22: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|23: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-149 d,#080808, +|24: 0-1 #ffffff,#626262, 2-129 #949494,#121212, 130-149 d,#121212, +|25: 0-0 d,#444444, 1-1 d,#080808, 2-6 #949494,#121212, 7-19 #080808,#80a0ff, 20-149 d,#080808, +|26: 0-1 #ffffff,#262626, 2-129 #949494,#121212, 130-149 d,#121212, +|27: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-18 #080808,#80a0ff, 19-35 #bdbdbd,#080808, 36-149 d,#080808, +|28: 0-0 d,#444444, 1-1 d,#080808, 2-6 #626262,#080808, 7-53 #bdbdbd,#080808, 54-149 d,#080808, +|29: 0-0 d,#121212, 1-1 d,#080808, 2-6 #626262,#080808, 7-39 #bdbdbd,#080808, 40-149 d,#080808, +== snap blank-opened grid=150x30 cursor=7,14 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| /tmp/pardes-snap/stepgrain/cwd/a.txt Save New Del +| 1 hello ZQMARK one +| 2 see also sub/b.txt for ZQMARK two +| +| +| +| +| +| +| +| +| +| /tmp/pardes-snap/stepgrain/cwd/two words.txt Save New Del +| 1 +| +| +| +| +| +| +| +| +| +| /tmp/pardes-snap/stepgrain/cwd/+Search New Del +| 1 two words.txt +| /tmp/pardes-snap/stepgrain/cwd/+Search New Del +| 1 a.txt:1:7-12 hello ZQMARK one +| 2 a.txt:2:24-29 see also sub/b.txt for ZQMARK two +| 3 sub/b.txt:1:1-6 ZQMARK inside sub diff --git a/test/snapshots/stepgrain.snap b/test/snapshots/stepgrain.snap new file mode 100644 index 00000000..a12d7bbd --- /dev/null +++ b/test/snapshots/stepgrain.snap @@ -0,0 +1,77 @@ +# WHAT ONE PRESS OF `n` TAKES, and why that is not one answer. A pane's rows +# decide it (output_pane.Grain, read in Pardes.lookSpanIn): +# +# free text — a terminal, a file, a PDF — is stepped WORD by word, several +# stops to a line, because a line of it may name several places at once (the +# `ls` row in ttylook.snap hops big.txt -> plain.txt -> sub); +# +# a RESULTS BUFFER is stepped ROW by row, on the location at the head of the +# row, because a row there IS one location and the words after it are the +# MATCHED TEXT — the same hit, spelled out. That text is full of look-able +# words, and stopping on them was stopping on the same result twice. +# +# This script is the second rule. Row 2 below matched on a line that mentions +# `sub/b.txt`, so the old word-grained walk stopped there before it ever +# reached row 3; now `n` presses and rows correspond one to one, and the fourth +# press wraps the ring back to row 1. +# +# LARGEST from the head, so a path with a BLANK in it is one span rather than +# the word in front of it: `two words.txt` selects whole and opens. +dirmk sub +file a.txt hello ZQMARK one\nsee also sub/b.txt for ZQMARK two +file sub/b.txt ZQMARK inside sub +run touch 'two words.txt' +start 30 150 a.txt +wait 8000 hello ZQMARK +stable 700 20000 +# Grep from the leader: three hits, sorted by path, the empty file contributing +# none. Row 2 is the one whose TEXT holds a path. +key space +key f +key g +stable 400 5000 +text ZQMARK +key enter +wait 10000 sub/b.txt:1:1 +stable 700 15000 +snap results +# one press, one row: the head of row 1... +key n +stable 400 5000 +snap step-row1 +snapstyle step-row1 +# ...the head of row 2, and NOT the `sub/b.txt` sitting in its matched text — +# the cursor column is the assertion +key n +stable 400 5000 +snap step-row2 +snapstyle step-row2 +# ...row 3, and then the ring comes round to row 1 again +key n +stable 400 5000 +snap step-row3 +key n +stable 400 5000 +snap step-wrap +# Back into the file, so the Find below is asked from a pane that is not a +# results buffer (fillResults never refills the buffer it was asked from). +press left 8 3 +release left 8 3 +stable 400 5000 +# A path with a blank in it: the row is one span from its first cell, because +# the walk asks about the LONGEST run of the row first and the filesystem is +# what vouches for it. Enter then opens exactly what is selected. +key space +key f +key f +text words +key enter +wait 10000 two words.txt +stable 700 15000 +key n +stable 400 5000 +snap blank-in-path +snapstyle blank-in-path +key enter +stable 700 15000 +snap blank-opened diff --git a/test/snapshots/ttytaken.golden b/test/snapshots/ttytaken.golden new file mode 100644 index 00000000..960081d1 --- /dev/null +++ b/test/snapshots/ttytaken.golden @@ -0,0 +1,124 @@ +== snap opened grid=100x30 cursor=7,2 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| /tmp/pardes-snap/ttytaken/cwd/cmd.txt Save New D /tmp/pardes-snap/ttytaken/cwd New Del +| 1 echo TAKEN''ONE ls +| 2 echo TAKEN''TWO big.txt cmd.txt +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +== snap at-prompt grid=100x30 cursor=7,2 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| /tmp/pardes-snap/ttytaken/cwd/cmd.txt Save New D /tmp/pardes-snap/ttytaken/cwd New Del +| 1 echo TAKEN''ONE ls +| 2 echo TAKEN''TWO big.txt cmd.txt +| echo TAKEN''ONE +| TAKENONE +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +== snap paging grid=100x30 cursor=59,29 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| /tmp/pardes-snap/ttytaken/cwd/cmd.txt Save New D$ /tmp/pardes-snap/ttytaken/cwd New Del +| 1 echo TAKEN''ONE line 1 +| 2 echo TAKEN''TWO line 2 +| line 3 +| line 4 +| line 5 +| line 6 +| line 7 +| line 8 +| line 9 +| line 10 +| line 11 +| line 12 +| line 13 +| line 14 +| line 15 +| line 16 +| line 17 +| line 18 +| line 19 +| line 20 +| line 21 +| line 22 +| line 23 +| line 24 +| line 25 +| line 26 +| line 27 +| big.txt +== snap spawned grid=100x30 cursor=53,25 +|New Newcol Find Grep Help Tutor Dump NextColor Debug Kill +| /tmp/pardes-snap/ttytaken/cwd/cmd.txt Save New D$ /tmp/pardes-snap/ttytaken/cwd New Del +| 1 echo TAKEN''ONE line 1 +| 2 echo TAKEN''TWO line 2 +| line 3 +| line 4 +| line 5 +| line 6 +| line 7 +| line 8 +| line 9 +| line 10 +| line 11 +| line 12 +| line 13 +| line 14 +| line 15 +| line 16 +| line 17 +| line 18 +| line 19 +| line 20 +| line 21 +| line 22 +| line 23 +| : +| /tmp/pardes-snap/ttytaken/cwd New Del +| echo TAKEN''TWO +| TAKENTWO +| diff --git a/test/snapshots/ttytaken.snap b/test/snapshots/ttytaken.snap new file mode 100644 index 00000000..6505fc63 --- /dev/null +++ b/test/snapshots/ttytaken.snap @@ -0,0 +1,43 @@ +# Process monitoring, end to end. A pane whose tty is held by a full-screen +# program is not somewhere a command line may be typed: the SAME middle-drag +# Exec goes into the shell while it sits at its prompt, and into a NEW shell +# beside it once `less` owns the terminal — where it runs, instead of becoming +# keystrokes the pager would eat. Nothing about the pager's screen changes. +file cmd.txt echo TAKEN''ONE\necho TAKEN''TWO +lines big.txt 40 +start 30 100 -n 1 +wait 8000 cmd.txt +stable 700 20000 +# open the command file by looking its name in the greeting `ls` +press right 14 4 +release right 14 4 +wait 10000 echo TAKEN +stable 700 20000 +snap opened +# at the prompt: the drag runs in the shell that is already on this directory +press middle 8 3 +drag middle 22 3 +release middle 22 3 +wait 10000 TAKENONE +stable 700 20000 +snap at-prompt +# ...and now a pager owns that terminal. Ctrl-b is raw tty (config.tty_toggle), +# which is how a script types AT the shell instead of into the pane's own edit +# buffer — the same way exec.snap runs a command line. +press left 60 8 +release left 60 8 +stable 400 5000 +key c-b +stable 400 5000 +text env LESS= less big.txt +key enter +wait 10000 line 1 +stable 700 20000 +snap paging +# the same gesture on the second line: a new shell, not `less` keystrokes +press middle 8 4 +drag middle 22 4 +release middle 22 4 +wait 10000 TAKENTWO +stable 700 20000 +snap spawned |
