summaryrefslogtreecommitdiff
path: root/src/pardes.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/pardes.zig')
-rw-r--r--src/pardes.zig54
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;