diff options
Diffstat (limited to 'src/pardes.zig')
| -rw-r--r-- | src/pardes.zig | 69 |
1 files changed, 66 insertions, 3 deletions
diff --git a/src/pardes.zig b/src/pardes.zig index 51442034..e9908d65 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -304,7 +304,7 @@ const pre_tty_first_terminal_pane_tail = " Save Tty Del Togglettymode Filter Col const pre_mode_terminal_pane_tail = " Tty Save Del Togglettymode Filter Collapse"; // ...and the defaults from before the closing word moved to the end of every // tagline, where a click cannot land on it by overshooting the one before. -const pre_close_last_pane_tail = " Tty Collapse Del"; +const pre_close_last_pane_tail = " Tty Del Collapse"; const pre_close_last_file_pane_tail = " Save Tty Del Collapse"; const pre_close_last_pdf_pane_tail = " Tty Del PdfSections PdfTint Collapse"; const pre_close_last_terminal_pane_tail = " Tty Save Del Mode Filter Collapse"; @@ -6099,6 +6099,9 @@ pub const Pardes = struct { fs: filesystem.Namespace = .{}, boot: Boot = .shell, + /// Is there a frame worth drawing? Cleared once one is presented, set by + /// anything that can change what is on screen. The first frame always is. + needs_frame: bool = true, /// Builtins announce themselves on the message row. Off while startup /// configuration is applied: those lines are nobody clicking, and should /// not leave the row holding the name of the last one. @@ -7137,13 +7140,28 @@ pub const Pardes = struct { // A quitting frame has already freed what it would draw. if (p.quit) return; if (v.poll_frame) |f| f(h.ctx); + // Nothing to draw: do not spend a frame, and above all do not block on + // the display for one. This is what keeps the frame rate governing + // rendering and nothing else. + if (!p.needs_frame and !p.animationActive()) return; _ = p.frame_arena.reset(.retain_capacity); const surface = try p.render(p.frame_arena.allocator()); if (v.present) |f| f(h.ctx, surface); if (v.post_present) |f| f(h.ctx); + p.needs_frame = false; } pub fn update(p: *Pardes, ev: Event) void { + // Only two events cannot change the screen: a tick with nothing + // animating, and a filesystem request that only reads. The second is + // what 9P traffic overwhelmingly is, and answering it used to drag a + // whole vsync-blocked frame behind it -- which is why a round trip on + // a local socket cost a frame instead of a few microseconds. + p.needs_frame = p.needs_frame or switch (ev) { + .tick => false, + .fs_req => |req| ctlfs.changesPane(req), + else => true, + }; p.shell_rows.sweep(p.gpa); switch (ev) { .tick => {}, @@ -13953,8 +13971,17 @@ pub const Pardes = struct { if (pane.promptAt()) |at| { if (pane.tagSlice()[@min(at, pane.tag_tail_len)..].len > 0) pane.notices.push(.prompt); } - // Never claim so many rows that the body has none left. - pane.notices.len = @min(pane.notices.len, r.h - BOX_H -| 1); + // A pane only has so many rows to give. When they do not all fit the + // LAST ones survive, because the prompt is last and it is the one + // carrying the cursor: a prompt you cannot see is a prompt you type + // into blind. A two-row pane spends its only body row on one notice, + // which is what the single shared row used to do anyway. + const room: u8 = @intCast(@min(@as(u16, Pane.Notices.max), r.h - BOX_H)); + if (pane.notices.len > room) { + const drop = pane.notices.len - room; + for (0..room) |i| pane.notices.kinds[i] = pane.notices.kinds[i + drop]; + pane.notices.len = room; + } } fn renderBodyLayer(p: *Pardes, arena: std.mem.Allocator, pane: *Pane, r: Rect, id: usize) !void { @@ -14603,6 +14630,42 @@ test "Esc back into a tty leaves its view at the prompt" { try std.testing.expectEqual(live, sp.terminal.?.vt.screens.active.pages.scrollbar().offset); } +test "closing the last pane in a column leaves an empty one in its place" { + if (platform == .web) return; + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 120, .rows = 24 }); + defer p.deinit(); + const shell = p.active; + p.newScratchColumn(shell); + const lone = p.active; + try std.testing.expect(lone != shell); + try std.testing.expectEqual(@as(usize, 2), p.ncol); + const at = layout.findPane(p, lone).?; + try std.testing.expectEqual(@as(usize, 1), p.col_n[at.col]); + const had = try std.testing.allocator.dupe(u8, p.panes[lone].?.cwdSlice()); + defer std.testing.allocator.free(had); + + try p.removePane(lone); + p.sync(); + + // The column outlives the pane: still two columns, and the one that was + // emptied holds a fresh empty pane in the same directory. + try std.testing.expectEqual(@as(usize, 2), p.ncol); + try std.testing.expectEqual(@as(usize, 1), p.col_n[at.col]); + const heir = p.col_panes[at.col][0]; + try std.testing.expect(heir != lone); + try std.testing.expect(p.panes[lone] == null); + const hp = p.panes[heir].?; + try std.testing.expectEqualStrings("", hp.file.?.content); + try std.testing.expectEqualStrings(had, hp.cwdSlice()); + try std.testing.expectEqual(heir, p.active); + try std.testing.expect(!p.quit); + + // ...and Delcol is what actually takes the column away. + try p.removeColumn(heir); + p.sync(); + try std.testing.expectEqual(@as(usize, 1), p.ncol); +} + test "the bare tty startup opens a text pane under the shell, unfocused" { if (platform == .web) return; const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .boot = .tty }); |
