diff options
Diffstat (limited to 'src/pardes.zig')
| -rw-r--r-- | src/pardes.zig | 91 |
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; } |
