summaryrefslogtreecommitdiff
path: root/src/pardes.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 12:19:39 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:16 -0300
commit4f57043e39396bc958a103f87466870cf8dafe00 (patch)
tree89ec869d2e3b3eb231f14f9298bb56afa9e0f6ae /src/pardes.zig
parentab220b89afcfda848cf84c18cc135a2e5c392b79 (diff)
downloadpardes-4f57043e39396bc958a103f87466870cf8dafe00.tar.gz
pardes-4f57043e39396bc958a103f87466870cf8dafe00.zip
A language server question that finds nothing, cannot be answered or has no room fails, and so does a write to an image's or PDF's body
Callers or Callees with nothing found, Hover or Lspwhy with no room for its pane, and a question asked of no file's pane said nothing, the 9P write succeeding; a body write to an image or PDF pane went nowhere. A write that asks a language server now waits for its answer (turn.lsp_answers), and what fails there -- nothing found, no answer, a backend's @none why, a placement with no room (ENOSPC) -- fails the write and logs its err. No diagnostics is said, not failed. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/pardes.zig')
-rw-r--r--src/pardes.zig91
1 files changed, 83 insertions, 8 deletions
diff --git a/src/pardes.zig b/src/pardes.zig
index ba9a1f90..135823e4 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -117,7 +117,10 @@ pub const Turn = struct {
/// (`takeRestore`), which lives after `pump` because it swaps the core
/// out from under it; a request that asked for one waits for this.
restores: u64 = 0,
- /// Both of the above, and `out` reaching zero.
+ /// Bumped each time a language server's answer (or its lack) is taken
+ /// in (`lspResponse`): a write that asked one waits for this.
+ lsp_answers: u64 = 0,
+ /// All of the above, and `out` reaching zero.
settled: std.Io.Condition = .init,
/// The editor's thread takes the turn for good; it has it until `stop`.
@@ -204,6 +207,16 @@ pub const Turn = struct {
t.settled.broadcast(io);
}
+ pub fn lspAnswered(t: *Turn) void {
+ const io = t.io orelse return;
+ t.lsp_answers +%= 1;
+ t.settled.broadcast(io);
+ }
+
+ pub fn awaitLsp(t: *Turn, answers: u64) void {
+ while (t.lsp_answers == answers) t.settled.wait(t.io.?, &t.mutex) catch return;
+ }
+
pub fn awaitRestored(t: *Turn, restores: u64) void {
while (t.restores == restores) t.settled.wait(t.io.?, &t.mutex) catch return;
}
@@ -5634,6 +5647,7 @@ pub const Pardes = struct {
p.raw_hover_intent = false;
look.cancelLookHover(p);
p.fs.serving = true;
+ p.fs.lsp_answer_at = null;
const reply = ctlfs.handle(p, req);
p.fs.serving = false;
if (reply.status == .err and (req.op == .write or req.op == .setattr or (req.op == .open and reply.errno == ctlfs.E.NOSPC) or (req.op == .release and req.remove))) ctlfs.events.noteError(p, req, reply);
@@ -6310,23 +6324,30 @@ pub const Pardes = struct {
pub fn lspRequest(p: *Pardes, id: usize, kind: lsp.Kind, arg: []const u8) void {
if (!normal.multiOnce(p)) return; // one question per keystroke, from the primary
- if (!lsp.supports.contains(kind)) return;
+ 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");
const pane = p.panes[id] orelse return;
if (kind != .status) {
- const f = pane.file orelse return;
- if (f.output != null) return;
+ const f = pane.file orelse
+ return p.reportFailure(id, std.fmt.bufPrint(&said, "{s}: only a file's pane asks a language server", .{lspWord(kind)}) catch "only a file's pane asks");
+ if (f.output != null)
+ return p.reportFailure(id, std.fmt.bufPrint(&said, "{s}: only a file's pane asks a language server", .{lspWord(kind)}) catch "only a file's pane asks");
}
- if (arg.len > 128) return; // the effect's arg is a Buf(128)
+ if (arg.len > 128) return p.reportFailure(id, std.fmt.bufPrint(&said, "{s}: its argument is at most 128 bytes", .{lspWord(kind)}) catch "argument too long");
if (kind == .rename) {
const zig_buf = if (pane.file) |f| std.mem.endsWith(u8, f.path, ".zig") else true;
- if (zig_buf and (!std.zig.isValidId(arg) or std.zig.isUnderscore(arg))) return;
- if (!zig_buf and std.mem.indexOfAny(u8, arg, " \t\r\n\"\\") != null) return;
+ if ((zig_buf and (!std.zig.isValidId(arg) or std.zig.isUnderscore(arg))) or
+ (!zig_buf and std.mem.indexOfAny(u8, arg, " \t\r\n\"\\") != null))
+ return p.reportFailure(id, std.fmt.bufPrint(&said, "Rename: {s} is no name", .{arg}) catch "Rename: no name");
}
const off = if (pane.file) |f| modal.offsetAt(f.content, .{
.row = @intCast(@max(0, pane.body.cur_row)),
.col = @intCast(@max(0, pane.body.cur_col)),
}) else 0;
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;
p.lsp_wait = .{
.id = p.lsp_seq,
.kind = kind,
@@ -6466,13 +6487,56 @@ pub const Pardes = struct {
return true;
}
+ /// The builtin word that asks `kind`, for what it says.
+ fn lspWord(kind: lsp.Kind) []const u8 {
+ return switch (kind) {
+ .hover => "Hover",
+ .explain => "Lspwhy",
+ .incoming_calls => "Callers",
+ .outgoing_calls => "Callees",
+ .document_symbols => "Symbols",
+ .workspace_symbols => "WsSymbols",
+ .diagnostics => "Diagnostics",
+ .workspace_diagnostics => "WsDiagnostics",
+ .code_action => "CodeAction",
+ .select_refs => "SelectRefs",
+ .supertypes => "Supertypes",
+ .subtypes => "Subtypes",
+ .rename => "Rename",
+ .format => "Format",
+ else => @tagName(kind),
+ };
+ }
+
+ /// 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];
+ 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);
+ }
+
pub fn lspResponse(p: *Pardes, id: u32, response: ?[]const u8) void {
+ defer turn.lspAnswered();
const w = p.lsp_wait orelse return;
if (w.id != id) return; // superseded by a newer press, or the pane died
p.lsp_wait = null;
- const rows = response orelse return;
const pane = p.panes[w.pane] orelse return;
if (pane.serial != w.serial) return;
+ // Whatever fails from here -- no answer, nothing found, no room for
+ // its pane -- fails a 9P write that asked it too.
+ const failures = p.fs.failures;
+ defer if (p.fs.failures != failures) p.noteLateFailure(w.pane);
+ var said: [256]u8 = undefined;
+ const rows = response orelse
+ return p.reportFailure(w.pane, std.fmt.bufPrint(&said, "{s}: the language server did not answer", .{lspWord(w.kind)}) catch "no answer");
+ // A backend that cannot answer it says why (`@none <why>`).
+ if (std.mem.startsWith(u8, rows, "@none ")) {
+ const why = std.mem.trimEnd(u8, rows["@none ".len..], "\n");
+ return p.reportFailure(w.pane, std.fmt.bufPrint(&said, "{s}: {s}", .{ lspWord(w.kind), why[0..@min(why.len, 200)] }) catch "no answer");
+ }
if (w.kind == .rename or w.kind == .format) {
const fallback: ?[]const u8 = if (w.kind == .rename) w.arg.slice() else null;
if (p.parseLspEdits(rows, fallback)) |edits| {
@@ -6492,6 +6556,17 @@ pub const Pardes = struct {
if (rows.len == 0) {
if (w.kind == .completion and pane.body.mode == .insert and pane.body.nsel == 0 and
pane.body.cur_row == w.row and pane.body.cur_col == w.col) edit.insertTab(p, &pane.body);
+ // Nothing found fails, but for a file with no diagnostics,
+ // which is no failure, and a completion's Tab.
+ const none: ?[]const u8 = switch (w.kind) {
+ .completion, .format, .rename => null,
+ .diagnostics, .workspace_diagnostics => return p.setMessage(w.pane, std.fmt.bufPrint(&said, "{s}: none", .{lspWord(w.kind)}) catch "none"),
+ .incoming_calls => "no callers, not found",
+ .outgoing_calls => "no callees, not found",
+ .hover => "nothing to show, not found",
+ else => "nothing found, not found",
+ };
+ if (none) |text| p.reportFailure(w.pane, std.fmt.bufPrint(&said, "{s}: {s}", .{ lspWord(w.kind), text }) catch "nothing found");
return;
}