summaryrefslogtreecommitdiff
path: root/src/pardes.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/pardes.zig')
-rw-r--r--src/pardes.zig93
1 files changed, 74 insertions, 19 deletions
diff --git a/src/pardes.zig b/src/pardes.zig
index a63568db..f0d22d57 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -1112,6 +1112,46 @@ test "first document fallback splits like Tty from the originating pane" {
};
}
+test "Lspwhy after a Hover asks to explain that hover, at its place" {
+ if (!lsp.supports.contains(.hover)) return error.SkipZigTest;
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
+ defer p.deinit();
+ const pane = try p.setTestFile("const a = 1;\nconst b = a;\n");
+ const f = &pane.file.?;
+ p.gpa.free(f.path);
+ f.path = try p.gpa.dupe(u8, "/tmp/x.zig");
+ while (p.nextEffect()) |_| {}
+ pane.body.cur_row = 1;
+ pane.body.cur_col = 10;
+ p.lspRequest(0, .hover, "");
+ var hover_at: u32 = 0;
+ while (p.nextEffect()) |e| if (e == .lsp) {
+ hover_at = e.lsp.offset;
+ };
+ pane.body.cur_row = 0;
+ pane.body.cur_col = 0;
+ p.lsp_wait = null;
+ p.lspRequest(0, .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);
+ seen = true;
+ };
+ try std.testing.expect(seen);
+}
+
+test "the Tutor pane is named +Tutor in the session's directory, never a bare path at the root" {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
+ defer p.deinit();
+ const session = "/project/src";
+ @memcpy(p.session_dir[0..session.len], session);
+ p.session_dir_len = session.len;
+ const tutor = try p.openTutorView(1);
+ try std.testing.expectEqualStrings("/project/src/+Tutor", tutor.file.?.path);
+}
+
test "first document keeps its originating shell and only opens a column when it fits" {
for ([_]u16{ 199, 200 }) |width| {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = width, .rows = 32 });
@@ -3543,7 +3583,9 @@ pub const Effect = union(enum) {
write_dump,
set_clipboard,
read_clipboard,
- lsp: struct { id: u32, kind: lsp.Kind, pane: u8, offset: u32, arg: Buf(128) },
+ /// `arg` up to 128 bytes, or for `explain` the query it explains,
+ /// `<kind> <arg>`.
+ lsp: struct { id: u32, kind: lsp.Kind, pane: u8, offset: u32, arg: Buf(160) },
/// Snapshot the matching request with pipeRequest(id), then run it away
/// from the UI/event loop and answer with pipe_resp.
pipe: struct { id: u32 },
@@ -4341,6 +4383,9 @@ pub const Pardes = struct {
can_ask: bool = false,
lsp_seq: u32 = 0,
+ /// The last query asked (Lspwhy explains it): its kind, argument, and
+ /// the pane and place it was asked at.
+ lsp_last: ?struct { kind: lsp.Kind, arg: Effect.Buf(128), serial: u32, offset: u32 } = null,
lsp_wait: ?struct {
id: u32,
kind: lsp.Kind,
@@ -5046,11 +5091,14 @@ pub const Pardes = struct {
// is this process's directory, a rename to undo. That answer, then,
// is not taken.
if (pane.isTerminal() and !pane.shell_spoke and pane.cwd == .owned and std.mem.eql(u8, cwd, p.sessionDir())) return;
+ // One with no directory yet is named by the session's (nameOf): its
+ // shell saying it is there is no new name.
+ const same_name = cur.len == 0 and std.mem.eql(u8, cwd, p.sessionDir());
pane.setOwnedCwd(cwd) catch |err| return p.reportError(id, "directory", err);
if (look.lookHoverPane(p) == id) look.cancelLookHover(p);
// A terminal is named by its directory, so /log hears it renamed; one
// not announced yet is announced under the new name.
- if (pane.isTerminal() and !pane.fs.unannounced) ctlfs.events.noteLog(p, .rename, pane);
+ if (pane.isTerminal() and !same_name) ctlfs.events.noteLog(p, .rename, pane);
}
pub fn shellBin(p: *const Pardes) []const u8 {
@@ -5338,10 +5386,7 @@ pub const Pardes = struct {
}
// A 9P write that asked for the shell (pty/ctl's exec), waiting on
// it, fails with what was said, as a failed Save's does.
- const said = pane.msg[0..pane.msg_len];
- const kept = @import("Messages.zig").clip(said, p.fs.late_failure.len);
- @memcpy(p.fs.late_failure[0..kept.len], kept);
- p.fs.late_failure_len = @intCast(kept.len);
+ p.noteLateFailure(id);
}
pub fn saveFailed(p: *Pardes, id: u8, path: []const u8, err: anyerror) void {
@@ -5359,10 +5404,7 @@ pub const Pardes = struct {
p.reportFailure(id, std.fmt.bufPrint(&what, "Save {s}: no such directory", .{path}) catch "Save: no such directory")
else
p.reportError(id, std.fmt.bufPrint(&what, "Save {s}", .{path}) catch "Save", err);
- const said = if (p.panes[id]) |pane| pane.msg[0..pane.msg_len] else "Save failed";
- const kept = @import("Messages.zig").clip(said, p.fs.late_failure.len);
- @memcpy(p.fs.late_failure[0..kept.len], kept);
- p.fs.late_failure_len = @intCast(kept.len);
+ p.noteLateFailure(id);
}
/// A Dump the host could not write: said on the message row and in the
@@ -5385,10 +5427,7 @@ pub const Pardes = struct {
} else p.reportError(p.active, operation, err);
// In the words the message row has, so the write's err is the one
// record of it (its msg goes: 9p_io, dropMessage).
- const said = if (p.panes[p.active]) |pane| pane.msg[0..pane.msg_len] else "Dump failed";
- const kept = @import("Messages.zig").clip(said, p.fs.late_failure.len);
- @memcpy(p.fs.late_failure[0..kept.len], kept);
- p.fs.late_failure_len = @intCast(kept.len);
+ p.noteLateFailure(p.active);
}
/// The path a watch is about: a real file's, or a PDF's.
@@ -6291,6 +6330,17 @@ pub const Pardes = struct {
.row = @intCast(@max(0, pane.body.cur_row)),
.col = @intCast(@max(0, pane.body.cur_col)),
}) else 0;
+ // Lspwhy asks the last query again, narrated: its kind and argument,
+ // at its place when asked from its pane. None yet: a definition.
+ var explained: [160]u8 = undefined;
+ var sent = arg;
+ var at = off;
+ 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;
+ }
+ } else if (kind != .status) p.lsp_last = .{ .kind = kind, .arg = .from(arg), .serial = pane.serial, .offset = @intCast(off) };
p.lsp_seq +%= 1;
// A 9P write that asked it is answered once the answer is in.
if (p.fs.serving) p.fs.lsp_answer_at = turn.lsp_answers;
@@ -6308,8 +6358,8 @@ pub const Pardes = struct {
.id = p.lsp_seq,
.kind = kind,
.pane = @intCast(id),
- .offset = @intCast(off),
- .arg = .from(arg),
+ .offset = @intCast(at),
+ .arg = .from(sent),
} });
}
@@ -6457,8 +6507,11 @@ pub const Pardes = struct {
/// What was said of a failure just now on pane `id` is also the waiting
/// 9P write's (9p_io, `fs.late_failure`).
pub fn noteLateFailure(p: *Pardes, id: usize) void {
- const pane = p.panes[id] orelse return;
- const said = pane.msg[0..pane.msg_len];
+ // Said by reportFailure just now: its words fitted whole, the
+ // reason kept; else what the message row has.
+ const said = if (p.fs.said_at == p.fs.failures and p.fs.said_len > 0)
+ p.fs.said[0..p.fs.said_len]
+ else if (p.panes[id]) |pane| pane.msg[0..pane.msg_len] else return;
const kept = @import("Messages.zig").clip(said, p.fs.late_failure.len);
@memcpy(p.fs.late_failure[0..kept.len], kept);
p.fs.late_failure_len = @intCast(kept.len);
@@ -6576,7 +6629,9 @@ pub const Pardes = struct {
pub fn openTutorView(p: *Pardes, id: usize) !*Pane {
const content = try p.gpa.dupe(u8, tutor_text);
errdefer p.gpa.free(content);
- const path = try p.gpa.dupe(u8, "/Tutor");
+ // A generated pane, named as every one is: `+` in the session's
+ // directory, never a bare path Save could write at the root.
+ const path = try std.fs.path.join(p.gpa, &.{ p.sessionDir(), "+Tutor" });
errdefer p.gpa.free(path);
const history = try panes.File.History.create(p.gpa);
errdefer p.gpa.destroy(history);