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