diff options
Diffstat (limited to 'src/lsp')
| -rw-r--r-- | src/lsp/lsp.zig | 16 | ||||
| -rw-r--r-- | src/lsp/lsp_zls.zig | 26 |
2 files changed, 25 insertions, 17 deletions
diff --git a/src/lsp/lsp.zig b/src/lsp/lsp.zig index 90d4fc6b..29e82968 100644 --- a/src/lsp/lsp.zig +++ b/src/lsp/lsp.zig @@ -6,14 +6,14 @@ //! execution model — the same shape the pty readers already use, because a //! language query is just another thing that answers later. //! -//! Every backend renders into ONE format: `+Search` rows. A location is +//! Location answers render as `+Search` rows. A location is //! `path:LINE:COL text` — or `path:LINE:COL-ENDCOL text` where the protocol //! answered with a real range, which a look then SELECTS — and that is what //! look.zig already resolves and what n/N already steps, so a multi-result //! answer IS helix's picker and a single result IS a jump, with no picker UI -//! written for it. Free text (hover, a rename's diff) rides the same buffer as -//! plain lines. A path under `Req.root` is written relative to it and any -//! other keeps its full absolute self — see `rel`. +//! written for it. Free text (hover, formatting) rides the same buffer. Rename +//! is the one mutating answer: it emits byte ranges through `edit`, and the core +//! applies them atomically only while the source revision is still current. //! //! `query` is the ONLY thing an implementation supplies. Swapping backends is //! swapping this one function, which is also how the three competing @@ -162,6 +162,14 @@ pub fn spanRow( }) catch {}; } +/// Emit one half-open byte range for a mutating response. Rename is the only +/// current user: every other answer remains human-readable rows. Byte offsets +/// avoid converting the displayed 1-based locations back into source offsets +/// in the core, and the prefix makes malformed or mixed responses fail closed. +pub fn edit(out: *std.Io.Writer, start: usize, end: usize) void { + out.print("@edit {d} {d}\n", .{ start, end }) catch {}; +} + /// Byte offset -> (line, column), both 0-based. Every backend needs it to turn /// an AST token into a row, so it lives here rather than three times over. pub fn lineCol(source: []const u8, offset: usize) struct { line: usize, col: usize } { diff --git a/src/lsp/lsp_zls.zig b/src/lsp/lsp_zls.zig index 76c5594c..bf220c47 100644 --- a/src/lsp/lsp_zls.zig +++ b/src/lsp/lsp_zls.zig @@ -1123,9 +1123,9 @@ fn containsIgnoreCase(hay: []const u8, needle: []const u8) bool { /// milliseconds, on every keypress, and the store's own workspace iteration /// has the same restriction (it can only see handles that were loaded). /// -/// `new_name` non-null makes it a rename PREVIEW: the same rows, annotated -/// with the replacement. The seam returns rows, not edits, so `SPC r` shows -/// what would change and changes nothing — an honest half of rename. +/// `new_name` non-null makes it a rename EDIT: the same resolved tokens become +/// half-open byte ranges. The core owns the replacement text and applies every +/// range in one undo transaction after checking the source revision. fn references( arena: std.mem.Allocator, analyser: *Analyser, @@ -1149,10 +1149,10 @@ fn references( const want = offsets.identifierTokenToNameSlice(decl_tree, name_tok); if (want.len == 0) return; - const lines: Lines = try .build(arena, tree.source); - // every row names THIS file (the walk is this file's tokens), so the path - // is spelled once rather than per hit - const path = lsp.rel(base, handle.uri.toFsPath(arena) catch return); + // Rename consumes exact byte ranges. Reference rows need the source line + // and displayed path; avoid building either for the mutating response. + const lines: ?Lines = if (new_name == null) try .build(arena, tree.source) else null; + const path = if (new_name == null) lsp.rel(base, handle.uri.toFsPath(arena) catch return) else ""; var n: usize = 0; for (0..tree.tokens.len) |i| { if (n >= max_rows) return; @@ -1163,12 +1163,12 @@ fn references( const d = (declAt(arena, analyser, handle, at) catch continue) orelse continue; if (!d.eql(target)) continue; n += 1; - const r = offsets.tokenToRange(tree, tok, enc); - const text = if (new_name) |nn| - try std.fmt.allocPrint(arena, "{s} -> {s} {s}", .{ want, nn, std.mem.trim(u8, lines.line(r.start.line), " \t") }) - else - lines.line(r.start.line); - lsp.spanRow(out, path, r.start.line, r.start.character, r.end.line, r.end.character, text); + if (new_name != null) { + lsp.edit(out, at, at + want.len); + } else { + const r = offsets.tokenToRange(tree, tok, enc); + lsp.spanRow(out, path, r.start.line, r.start.character, r.end.line, r.end.character, lines.?.line(r.start.line)); + } } } |
