diff options
Diffstat (limited to 'src/pardes.zig')
| -rw-r--r-- | src/pardes.zig | 80 |
1 files changed, 72 insertions, 8 deletions
diff --git a/src/pardes.zig b/src/pardes.zig index 1009d13d..402b3cd8 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -1112,7 +1112,33 @@ test "first document fallback splits like Tty from the originating pane" { }; } -test "Lspwhy after a Hover asks to explain that hover, at its place" { +test "a frame no one watches is not drawn, and is drawn once someone does" { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true }); + defer p.deinit(); + while (p.nextEffect()) |_| {} + const Watch = struct { + var presents: usize = 0; + var seen = false; + const vtable: Host.VTable = .{ .present = present, .watched = watched }; + fn present(_: ?*anyopaque, _: *const Surface) void { + presents += 1; + } + fn watched(_: ?*anyopaque) bool { + return seen; + } + }; + Watch.presents = 0; + Watch.seen = false; + p.needs_frame = true; + try p.pump(.{ .vtable = &Watch.vtable }); + try std.testing.expectEqual(@as(usize, 0), Watch.presents); + try std.testing.expect(p.needs_frame); // still owed + Watch.seen = true; + try p.pump(.{ .vtable = &Watch.vtable }); + try std.testing.expectEqual(@as(usize, 1), Watch.presents); +} + +test "Lspwhy after a Hover asks to explain that hover, in its file at its place, run from any pane" { if (!lsp.supports.contains(.hover)) return error.SkipZigTest; const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true }); defer p.deinit(); @@ -1131,12 +1157,16 @@ test "Lspwhy after a Hover asks to explain that hover, at its place" { pane.body.cur_row = 0; pane.body.cur_col = 0; p.lsp_wait = null; - p.lspRequest(0, .explain, ""); + // Run from another file's pane: still the hover's file and place. + const other = p.freeSlot().?; + _ = try p.openTutorView(other); + p.lspRequest(other, .explain, ""); var seen = false; while (p.nextEffect()) |e| if (e == .lsp) { try std.testing.expectEqual(lsp.Kind.explain, e.lsp.kind); try std.testing.expectEqualStrings("hover ", e.lsp.arg.slice()); try std.testing.expectEqual(hover_at, e.lsp.offset); + try std.testing.expectEqual(@as(u8, 0), e.lsp.pane); seen = true; }; try std.testing.expect(seen); @@ -4343,6 +4373,10 @@ pub const Pardes = struct { chord_hold: bool = false, /// The look just run found nothing (look.zig missed). look_missed: bool = false, + /// The diff line a right click just expanded to, by row: a look at its + /// text goes to the place that row names, not the first equal line + /// (look.zig diffLook). + diff_look: ?struct { serial: u32, row: usize } = null, look_hover_wait: ?look.LookHoverWait = null, /// The place a look-hover last found nothing to open: the pointer /// resting there does not ask again every frame (look.noteLookHover). @@ -4484,6 +4518,9 @@ pub const Pardes = struct { /// the terminal motion surface, memoized against the pane it was built /// for — see panes.terminal.RowsCache for the lifetime rule shell_rows: panes.terminal.RowsCache = .{}, + /// Finished command panes' output, and whether it is a diff + /// (File.DiffOutput). + diff_output: panes.File.DiffOutput = .{}, /// The message row's entry points, called from everywhere as /// `p.setMessage(...)`; they live with the rest of it in Messages.zig. @@ -4734,6 +4771,7 @@ pub const Pardes = struct { p.locations_cache.deinit(p.tree_sitter_gpa); p.fs.deinit(gpa); p.shell_rows.reset(gpa); + p.diff_output.reset(gpa); p.scratch.deinit(); p.frame_arena.deinit(); gpa.free(p.surface.cells); @@ -4983,8 +5021,13 @@ pub const Pardes = struct { // An empty window (Delcol took the last column): its first column. if (layout.firstColumn(p)) |c| return tagline.enterHeader(p, c); const col = p.keyboardColumn(from_id) orelse return; + // A trial: laying it out gives it a serial (layout.compute), which a + // refusal takes back. Every other column has its own already, so + // the counter as it is now is the trial's to return to. + for (0..p.ncol) |k| _ = layout.columnSerial(p, k); + const unspent = p.next_column_serial; // Said, not silently nothing: "no space" is 9ns's word for ENOSPC. - const c = layout.insertColumn(p, col, false) orelse { + const c = layout.insertColumnUnannounced(p, col, false) orelse { var said: [96]u8 = undefined; return p.reportFailure(from_id, if (p.ncol >= MAX_COLS) std.fmt.bufPrint(&said, "Newcol: no space for a column: {d} max", .{MAX_COLS}) catch "Newcol: no space for a column" @@ -4996,10 +5039,13 @@ pub const Pardes = struct { // out again, and where they cannot hold every minimum there is no // new column, as a size too small is refused. for (0..p.ncol) |k| if (!layout.columnAtMinimums(p, k) and !layout.shareColumn(p, k)) { - layout.dropColumn(p, c); + layout.dropColumnUnannounced(p, c); + p.next_column_serial = unspent; layout.compute(p); return p.reportFailure(from_id, "Newcol: no space for a column: the panes' tags would not fit"); }; + // Kept: now it has a serial, and the log hears of it. + layout.noteColumn(p, "newcol", layout.columnSerial(p, c)); tagline.enterHeader(p, c); } @@ -5420,6 +5466,15 @@ pub const Pardes = struct { p.noteLateFailure(id); } + /// A Save makes the directories its file goes in, and any above them, + /// first: one rule, for the pane's own name and `Save path` alike. A + /// directory it cannot make leaves the write to fail and say why. + fn makeParents(path: []const u8) void { + if (comptime !hosted) return; + const local = filesystem.localPath(path) orelse return; + filesystem.makeDirs(std.fs.path.dirname(local) orelse "/"); + } + pub fn saveFailed(p: *Pardes, id: u8, path: []const u8, err: anyerror) void { if (p.panes[id]) |pane| if (pane.file) |*f| { // The `-%` spelling fs.zig already uses for "make this dirty". @@ -5509,8 +5564,7 @@ pub const Pardes = struct { const was = f.saved_revision; const failures = p.fs.failures; f.saved_revision = f.revision; - if (f.make_dirs) if (filesystem.localPath(f.path)) |local| - filesystem.makeDirs(std.fs.path.dirname(local) orelse "/"); + makeParents(f.path); p.hostWriteFile(sf.pane, f.path, f.content); const saved_pane = p.panes[sf.pane] orelse return; if (saved_pane.serial != serial) return; @@ -5539,6 +5593,7 @@ pub const Pardes = struct { if (pane.file) |*f| { const was = f.saved_revision; const failures = p.fs.failures; + makeParents(st.path.slice()); p.hostWriteFile(st.pane, st.path.slice(), f.content); const after = p.panes[st.pane] orelse return; if (after.serial != st.serial) return; @@ -5554,6 +5609,7 @@ pub const Pardes = struct { if (!pane.isTerminal()) return; const text = panes.terminal.screenTextAlloc(pane, p.gpa) catch return; defer p.gpa.free(text); + makeParents(st.path.slice()); p.hostWriteFile(st.pane, st.path.slice(), text); }, .write_dump => { @@ -5634,6 +5690,10 @@ pub const Pardes = struct { // A shell with a clock draws what `advance` stepped; one without // steps its own `.tick`s and draws while anything animates. if (!p.needs_frame and !p.continuous() and (v.now != null or !p.animationActive())) return; + // No one to see it: the frame stays owed (needs_frame), not drawn. + // A pty streaming into an unwatched session spent most of its time + // drawing what nobody looked at. + if (v.watched) |f| if (!f(h.ctx)) return; _ = p.frame_arena.reset(.retain_capacity); const surface = try p.render(p.frame_arena.allocator()); if (v.present) |f| f(h.ctx, surface); @@ -6340,8 +6400,12 @@ pub const Pardes = struct { pub const SavePath = Effect.Buf(effect_path_cap); - pub fn lspRequest(p: *Pardes, id: usize, kind: lsp.Kind, arg: []const u8) void { + pub fn lspRequest(p: *Pardes, asked: usize, kind: lsp.Kind, arg: []const u8) void { if (!normal.multiOnce(p)) return; // one question per keystroke, from the primary + // Lspwhy narrates the last query where it was asked -- that pane's + // file, place and argument -- from wherever it is run. + const last_pane: ?usize = if (kind == .explain) (if (p.lsp_last) |last| p.paneBySerial(last.serial) else null) else null; + const id = last_pane orelse asked; var said: [128]u8 = undefined; if (!lsp.supports.contains(kind)) return p.reportFailure(id, std.fmt.bufPrint(&said, "{s}: no language server here answers it", .{lspWord(kind)}) catch "no language server answers it"); @@ -6371,7 +6435,7 @@ pub const Pardes = struct { if (kind == .explain) { if (p.lsp_last) |last| { sent = std.fmt.bufPrint(&explained, "{s} {s}", .{ @tagName(last.kind), last.arg.slice() }) catch ""; - if (last.serial == pane.serial) at = last.offset; + if (last_pane != null) at = last.offset; } } else if (kind != .status) p.lsp_last = .{ .kind = kind, .arg = .from(arg), .serial = pane.serial, .offset = @intCast(off) }; p.lsp_seq +%= 1; |
