diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/pardes.zig | 54 |
1 files changed, 53 insertions, 1 deletions
diff --git a/src/pardes.zig b/src/pardes.zig index 8c1e22f4..76f3bcd2 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -5245,7 +5245,7 @@ pub const Pardes = struct { fn enterTty(p: *Pardes, id: usize) void { const pane = p.panes[id] orelse return; const screen = pane.vt.screens.active; - if (pane.vt.cursorIsAtPrompt()) { + if (pane.cur_pinned and pane.vt.cursorIsAtPrompt()) { // the pin is a GRID position: convert the modal cursor's surface // row back through the edit buffer const goff: i32 = @intCast(screen.pages.scrollbar().offset); @@ -5270,6 +5270,15 @@ pub const Pardes = struct { pane.msel.active = false; pane.vsel.active = false; pane.nsel = 0; // raw tty: the program owns the pane, and its cursor is the shell's + // ...so hand the CURSOR over too. A pin is a row of the surface, and a + // shell's rows scroll out from under one: pinned at the prompt, fifty + // lines of output later the pin names scrollback nobody is looking at. + // Unpinned it tracks the shell again, which is the only thing that + // stays right — and it is what trackJump already calls "a place, not a + // spot", so Last stops dragging the view back to that stale row on the + // way in (it sat there until the next keystroke's echo scrolled it + // down). Leaving tty re-pins wherever the shell left the cursor. + pane.cur_pinned = false; pane.select = false; pane.append_at = null; pane.sticky_col = -1; @@ -12703,6 +12712,49 @@ test "Esc alternates between two panes of the SAME kind" { } } +test "Esc back into a tty leaves its view at the prompt" { + if (platform == .web) return; + const gpa = std.testing.allocator; + const p = try Pardes.init(gpa, .{ .cols = 80, .rows = 24, .file = "mise.toml" }); + defer p.deinit(); + p.update(.{ .resize = .{ .cols = 80, .rows = 24 } }); + p.update(.{ .key = .{ .cp = 'n', .alt = true } }); // a shell under the doc + const shell = p.active; + const sp = p.panes[shell].?; + try std.testing.expect(sp.isTerminal()); + + // A click pins the modal cursor — one of the several ordinary ways to get a + // pin, and the pin is what a shell's rows then scroll out from under. + const r = p.rects[shell]; + p.update(.{ .mouse = .{ .button = .left, .kind = .press, .col = r.x + config.GUTTER + 1, .row = r.y + BOX_H } }); + p.update(.{ .mouse = .{ .button = .left, .kind = .release, .col = r.x + config.GUTTER + 1, .row = r.y + BOX_H } }); + try std.testing.expectEqual(shell, p.active); + + p.update(.{ .key = .{ .cp = Key.escape, .shift = true } }); + try std.testing.expectEqual(Mode.tty, sp.mode); + + // tty mode follows output to the bottom, so a screenful and a half of it + // rides the view down and leaves that pin far up in the scrollback. + for (0..60) |i| { + var buf: [32]u8 = undefined; + p.update(.{ .output = .{ .pane = @intCast(shell), .bytes = std.fmt.bufPrint(&buf, "line {d}\r\n", .{i}) catch unreachable } }); + } + p.sync(); + const live = sp.vt.screens.active.pages.scrollbar().offset; + try std.testing.expect(live > 0); + + p.update(.{ .key = .{ .cp = Key.escape, .shift = true } }); // out to the doc + try std.testing.expect(p.active != shell); + p.update(.{ .key = .{ .cp = Key.escape } }); // ...and back in + p.sync(); + try std.testing.expectEqual(shell, p.active); + + // The prompt is still on screen. Restoring the stale pin used to yank the + // view up to scrollback row 0, where it sat until the next keystroke's echo + // scrolled it back down — "type something and the tty jumps to the prompt". + try std.testing.expectEqual(live, sp.vt.screens.active.pages.scrollbar().offset); +} + test "Shift-Esc in tty hops to the doc and leaves the shell in tty" { if (platform == .web) return; const gpa = std.testing.allocator; |
