summaryrefslogtreecommitdiff
path: root/src/lsp
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-10 09:14:05 -0300
committerGabriel Schneider <[email protected]>2026-08-10 09:17:07 -0300
commit38e9919a9ea9055538409b388d580c4e4c838434 (patch)
treea51ad778189cf007857f0d207f804d59228a023e /src/lsp
parent0d66575a3c888498c7929e2ec345133628c5d63d (diff)
downloadpardes-38e9919a9ea9055538409b388d580c4e4c838434.tar.gz
pardes-38e9919a9ea9055538409b388d580c4e4c838434.zip
fixed lsp rename
Diffstat (limited to 'src/lsp')
-rw-r--r--src/lsp/lsp.zig16
-rw-r--r--src/lsp/lsp_zls.zig26
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));
+ }
}
}