feat: make LSPs fully async
This commit is contained in:
parent
38c170f876
commit
6d58ecb324
2 changed files with 293 additions and 130 deletions
64
src/LSP.zig
64
src/LSP.zig
|
@ -15,7 +15,7 @@ const debug_lsp = true;
|
||||||
|
|
||||||
const OutOfMemoryError = error{OutOfMemory};
|
const OutOfMemoryError = error{OutOfMemory};
|
||||||
const SendError = error{SendFailed};
|
const SendError = error{SendFailed};
|
||||||
const CallError = tp.CallError;
|
const SpawnError = error{ThespianSpawnFailed};
|
||||||
|
|
||||||
pub fn open(allocator: std.mem.Allocator, project: []const u8, cmd: tp.message) (error{ ThespianSpawnFailed, InvalidLspCommand } || cbor.Error)!Self {
|
pub fn open(allocator: std.mem.Allocator, project: []const u8, cmd: tp.message) (error{ ThespianSpawnFailed, InvalidLspCommand } || cbor.Error)!Self {
|
||||||
return .{ .allocator = allocator, .pid = try Process.create(allocator, project, cmd) };
|
return .{ .allocator = allocator, .pid = try Process.create(allocator, project, cmd) };
|
||||||
|
@ -31,12 +31,18 @@ pub fn term(self: Self) void {
|
||||||
self.pid.deinit();
|
self.pid.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_request(self: Self, allocator: std.mem.Allocator, method: []const u8, m: anytype) CallError!tp.message {
|
pub fn send_request(
|
||||||
|
self: Self,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
method: []const u8,
|
||||||
|
m: anytype,
|
||||||
|
ctx: anytype,
|
||||||
|
) (OutOfMemoryError || SpawnError)!void {
|
||||||
var cb = std.ArrayList(u8).init(self.allocator);
|
var cb = std.ArrayList(u8).init(self.allocator);
|
||||||
defer cb.deinit();
|
defer cb.deinit();
|
||||||
try cbor.writeValue(cb.writer(), m);
|
try cbor.writeValue(cb.writer(), m);
|
||||||
const request_timeout: u64 = @intCast(std.time.ns_per_s * tp.env.get().num("lsp-request-timeout"));
|
const request_timeout: u64 = @intCast(std.time.ns_per_s * tp.env.get().num("lsp-request-timeout"));
|
||||||
return self.pid.call(allocator, request_timeout, .{ "REQ", method, cb.items });
|
return RequestContext(@TypeOf(ctx)).send(allocator, self.pid.ref(), ctx, request_timeout, tp.message.fmt(.{ "REQ", method, cb.items }));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_notification(self: Self, method: []const u8, m: anytype) (OutOfMemoryError || SendError)!void {
|
pub fn send_notification(self: Self, method: []const u8, m: anytype) (OutOfMemoryError || SendError)!void {
|
||||||
|
@ -54,6 +60,58 @@ pub fn close(self: *Self) void {
|
||||||
self.deinit();
|
self.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn RequestContext(T: type) type {
|
||||||
|
return struct {
|
||||||
|
receiver: ReceiverT,
|
||||||
|
ctx: T,
|
||||||
|
to: tp.pid,
|
||||||
|
request: tp.message,
|
||||||
|
response: ?tp.message,
|
||||||
|
a: std.mem.Allocator,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
const ReceiverT = tp.Receiver(*@This());
|
||||||
|
|
||||||
|
fn send(a: std.mem.Allocator, to: tp.pid_ref, ctx: T, timeout_ns: u64, request: tp.message) (OutOfMemoryError || SpawnError)!void {
|
||||||
|
_ = timeout_ns;
|
||||||
|
const self = try a.create(@This());
|
||||||
|
self.* = .{
|
||||||
|
.receiver = undefined,
|
||||||
|
.ctx = if (@hasDecl(T, "clone")) ctx.clone() else ctx,
|
||||||
|
.to = to.clone(),
|
||||||
|
.request = try request.clone(std.heap.c_allocator),
|
||||||
|
.response = null,
|
||||||
|
.a = a,
|
||||||
|
};
|
||||||
|
self.receiver = ReceiverT.init(receive_, self);
|
||||||
|
const proc = try tp.spawn_link(a, self, start, @typeName(@This()));
|
||||||
|
defer proc.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinit(self: *@This()) void {
|
||||||
|
self.ctx.deinit();
|
||||||
|
std.heap.c_allocator.free(self.request.buf);
|
||||||
|
self.to.deinit();
|
||||||
|
self.a.destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start(self: *@This()) tp.result {
|
||||||
|
_ = tp.set_trap(true);
|
||||||
|
if (@hasDecl(T, "link")) try self.ctx.link();
|
||||||
|
errdefer self.deinit();
|
||||||
|
try self.to.link();
|
||||||
|
try self.to.send_raw(self.request);
|
||||||
|
tp.receive(&self.receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive_(self: *@This(), _: tp.pid_ref, m: tp.message) tp.result {
|
||||||
|
defer self.deinit();
|
||||||
|
self.ctx.receive(m) catch |e| return tp.exit_error(e, @errorReturnTrace());
|
||||||
|
return tp.exit_normal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const Process = struct {
|
const Process = struct {
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
cmd: tp.message,
|
cmd: tp.message,
|
||||||
|
|
261
src/Project.zig
261
src/Project.zig
|
@ -25,7 +25,6 @@ persistent: bool = false,
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
const OutOfMemoryError = error{OutOfMemory};
|
const OutOfMemoryError = error{OutOfMemory};
|
||||||
const CallError = tp.CallError;
|
|
||||||
const SpawnError = (OutOfMemoryError || error{ThespianSpawnFailed});
|
const SpawnError = (OutOfMemoryError || error{ThespianSpawnFailed});
|
||||||
pub const InvalidMessageError = error{ InvalidMessage, InvalidMessageField, InvalidTargetURI };
|
pub const InvalidMessageError = error{ InvalidMessage, InvalidMessageField, InvalidTargetURI };
|
||||||
pub const StartLspError = (error{ ThespianSpawnFailed, Timeout, InvalidLspCommand } || LspError || OutOfMemoryError || cbor.Error);
|
pub const StartLspError = (error{ ThespianSpawnFailed, Timeout, InvalidLspCommand } || LspError || OutOfMemoryError || cbor.Error);
|
||||||
|
@ -234,11 +233,8 @@ fn get_language_server_instance(self: *Self, language_server: []const u8) StartL
|
||||||
defer self.allocator.free(uri);
|
defer self.allocator.free(uri);
|
||||||
const basename_begin = std.mem.lastIndexOfScalar(u8, self.name, std.fs.path.sep);
|
const basename_begin = std.mem.lastIndexOfScalar(u8, self.name, std.fs.path.sep);
|
||||||
const basename = if (basename_begin) |begin| self.name[begin + 1 ..] else self.name;
|
const basename = if (basename_begin) |begin| self.name[begin + 1 ..] else self.name;
|
||||||
const response = try self.send_lsp_init_request(lsp, self.name, basename, uri);
|
|
||||||
defer self.allocator.free(response.buf);
|
try self.send_lsp_init_request(lsp, self.name, basename, uri, language_server);
|
||||||
lsp.send_notification("initialized", .{}) catch return error.LspFailed;
|
|
||||||
if (lsp.pid.expired()) return error.LspFailed;
|
|
||||||
log.logger("lsp").print("initialized LSP: {s}", .{fmt_lsp_name_func(language_server)});
|
|
||||||
try self.language_servers.put(try self.allocator.dupe(u8, language_server), lsp);
|
try self.language_servers.put(try self.allocator.dupe(u8, language_server), lsp);
|
||||||
return lsp;
|
return lsp;
|
||||||
}
|
}
|
||||||
|
@ -620,27 +616,43 @@ fn send_goto_request(self: *Self, from: tp.pid_ref, file_path: []const u8, row:
|
||||||
const lsp = try self.get_language_server(file_path);
|
const lsp = try self.get_language_server(file_path);
|
||||||
const uri = try self.make_URI(file_path);
|
const uri = try self.make_URI(file_path);
|
||||||
defer self.allocator.free(uri);
|
defer self.allocator.free(uri);
|
||||||
const response = lsp.send_request(self.allocator, method, .{
|
|
||||||
.textDocument = .{ .uri = uri },
|
const handler: struct {
|
||||||
.position = .{ .line = row, .character = col },
|
from: tp.pid,
|
||||||
}) catch return error.LspFailed;
|
name: []const u8,
|
||||||
defer self.allocator.free(response.buf);
|
|
||||||
|
pub fn deinit(self_: *@This()) void {
|
||||||
|
std.heap.c_allocator.free(self_.name);
|
||||||
|
self_.from.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn receive(self_: @This(), response: tp.message) !void {
|
||||||
var link: []const u8 = undefined;
|
var link: []const u8 = undefined;
|
||||||
var locations: []const u8 = undefined;
|
var locations: []const u8 = undefined;
|
||||||
if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.array })) {
|
if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.array })) {
|
||||||
if (try cbor.match(response.buf, .{ tp.any, tp.any, tp.any, .{tp.extract_cbor(&link)} })) {
|
if (try cbor.match(response.buf, .{ tp.any, tp.any, tp.any, .{tp.extract_cbor(&link)} })) {
|
||||||
try self.navigate_to_location_link(from, link);
|
try navigate_to_location_link(self_.from.ref(), link);
|
||||||
} else if (try cbor.match(response.buf, .{ tp.any, tp.any, tp.any, tp.extract_cbor(&locations) })) {
|
} else if (try cbor.match(response.buf, .{ tp.any, tp.any, tp.any, tp.extract_cbor(&locations) })) {
|
||||||
try self.send_reference_list(from, locations);
|
try send_reference_list(self_.from.ref(), locations, self_.name);
|
||||||
}
|
}
|
||||||
} else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.null_ })) {
|
} else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.null_ })) {
|
||||||
return;
|
return;
|
||||||
} else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.extract_cbor(&link) })) {
|
} else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.extract_cbor(&link) })) {
|
||||||
try self.navigate_to_location_link(from, link);
|
try navigate_to_location_link(self_.from.ref(), link);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} = .{
|
||||||
|
.from = from.clone(),
|
||||||
|
.name = try std.heap.c_allocator.dupe(u8, self.name),
|
||||||
|
};
|
||||||
|
|
||||||
|
lsp.send_request(self.allocator, method, .{
|
||||||
|
.textDocument = .{ .uri = uri },
|
||||||
|
.position = .{ .line = row, .character = col },
|
||||||
|
}, handler) catch return error.LspFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn navigate_to_location_link(_: *Self, from: tp.pid_ref, location_link: []const u8) (ClientError || InvalidMessageError || cbor.Error)!void {
|
fn navigate_to_location_link(from: tp.pid_ref, location_link: []const u8) (ClientError || InvalidMessageError || cbor.Error)!void {
|
||||||
var iter = location_link;
|
var iter = location_link;
|
||||||
var targetUri: ?[]const u8 = null;
|
var targetUri: ?[]const u8 = null;
|
||||||
var targetRange: ?Range = null;
|
var targetRange: ?Range = null;
|
||||||
|
@ -704,21 +716,36 @@ pub fn references(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usi
|
||||||
defer self.allocator.free(uri);
|
defer self.allocator.free(uri);
|
||||||
log.logger("lsp").print("finding references...", .{});
|
log.logger("lsp").print("finding references...", .{});
|
||||||
|
|
||||||
const response = lsp.send_request(self.allocator, "textDocument/references", .{
|
const handler: struct {
|
||||||
.textDocument = .{ .uri = uri },
|
from: tp.pid,
|
||||||
.position = .{ .line = row, .character = col },
|
name: []const u8,
|
||||||
.context = .{ .includeDeclaration = true },
|
|
||||||
}) catch return error.LspFailed;
|
pub fn deinit(self_: *@This()) void {
|
||||||
defer self.allocator.free(response.buf);
|
std.heap.c_allocator.free(self_.name);
|
||||||
|
self_.from.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn receive(self_: @This(), response: tp.message) !void {
|
||||||
var locations: []const u8 = undefined;
|
var locations: []const u8 = undefined;
|
||||||
if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.null_ })) {
|
if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.null_ })) {
|
||||||
return;
|
return;
|
||||||
} else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.extract_cbor(&locations) })) {
|
} else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.extract_cbor(&locations) })) {
|
||||||
try self.send_reference_list(from, locations);
|
try send_reference_list(self_.from.ref(), locations, self_.name);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} = .{
|
||||||
|
.from = from.clone(),
|
||||||
|
.name = try std.heap.c_allocator.dupe(u8, self.name),
|
||||||
|
};
|
||||||
|
|
||||||
|
lsp.send_request(self.allocator, "textDocument/references", .{
|
||||||
|
.textDocument = .{ .uri = uri },
|
||||||
|
.position = .{ .line = row, .character = col },
|
||||||
|
.context = .{ .includeDeclaration = true },
|
||||||
|
}, handler) catch return error.LspFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_reference_list(self: *Self, to: tp.pid_ref, locations: []const u8) (ClientError || InvalidMessageError || GetLineOfFileError || cbor.Error)!void {
|
fn send_reference_list(to: tp.pid_ref, locations: []const u8, name: []const u8) (ClientError || InvalidMessageError || GetLineOfFileError || cbor.Error)!void {
|
||||||
defer to.send(.{ "REF", "done" }) catch {};
|
defer to.send(.{ "REF", "done" }) catch {};
|
||||||
var iter = locations;
|
var iter = locations;
|
||||||
var len = try cbor.decodeArrayHeader(&iter);
|
var len = try cbor.decodeArrayHeader(&iter);
|
||||||
|
@ -726,13 +753,14 @@ fn send_reference_list(self: *Self, to: tp.pid_ref, locations: []const u8) (Clie
|
||||||
while (len > 0) : (len -= 1) {
|
while (len > 0) : (len -= 1) {
|
||||||
var location: []const u8 = undefined;
|
var location: []const u8 = undefined;
|
||||||
if (try cbor.matchValue(&iter, cbor.extract_cbor(&location))) {
|
if (try cbor.matchValue(&iter, cbor.extract_cbor(&location))) {
|
||||||
try self.send_reference(to, location);
|
try send_reference(to, location, name);
|
||||||
} else return error.InvalidMessageField;
|
} else return error.InvalidMessageField;
|
||||||
}
|
}
|
||||||
log.logger("lsp").print("found {d} references", .{count});
|
log.logger("lsp").print("found {d} references", .{count});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_reference(self: *Self, to: tp.pid_ref, location: []const u8) (ClientError || InvalidMessageError || GetLineOfFileError || cbor.Error)!void {
|
fn send_reference(to: tp.pid_ref, location: []const u8, name: []const u8) (ClientError || InvalidMessageError || GetLineOfFileError || cbor.Error)!void {
|
||||||
|
const allocator = std.heap.c_allocator;
|
||||||
var iter = location;
|
var iter = location;
|
||||||
var targetUri: ?[]const u8 = null;
|
var targetUri: ?[]const u8 = null;
|
||||||
var targetRange: ?Range = null;
|
var targetRange: ?Range = null;
|
||||||
|
@ -767,10 +795,10 @@ fn send_reference(self: *Self, to: tp.pid_ref, location: []const u8) (ClientErro
|
||||||
file_path[i] = '\\';
|
file_path[i] = '\\';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const line = try self.get_line_of_file(self.allocator, file_path, targetRange.?.start.line);
|
const line = try get_line_of_file(allocator, file_path, targetRange.?.start.line);
|
||||||
defer self.allocator.free(line);
|
defer allocator.free(line);
|
||||||
const file_path_ = if (file_path.len > self.name.len and std.mem.eql(u8, self.name, file_path[0..self.name.len]))
|
const file_path_ = if (file_path.len > name.len and std.mem.eql(u8, name, file_path[0..name.len]))
|
||||||
file_path[self.name.len + 1 ..]
|
file_path[name.len + 1 ..]
|
||||||
else
|
else
|
||||||
file_path;
|
file_path;
|
||||||
to.send(.{
|
to.send(.{
|
||||||
|
@ -788,24 +816,44 @@ pub fn completion(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usi
|
||||||
const lsp = try self.get_language_server(file_path);
|
const lsp = try self.get_language_server(file_path);
|
||||||
const uri = try self.make_URI(file_path);
|
const uri = try self.make_URI(file_path);
|
||||||
defer self.allocator.free(uri);
|
defer self.allocator.free(uri);
|
||||||
const response = lsp.send_request(self.allocator, "textDocument/completion", .{
|
|
||||||
.textDocument = .{ .uri = uri },
|
const handler: struct {
|
||||||
.position = .{ .line = row, .character = col },
|
from: tp.pid,
|
||||||
}) catch return error.LspFailed;
|
file_path: []const u8,
|
||||||
defer self.allocator.free(response.buf);
|
row: usize,
|
||||||
|
col: usize,
|
||||||
|
|
||||||
|
pub fn deinit(self_: *@This()) void {
|
||||||
|
std.heap.c_allocator.free(self_.file_path);
|
||||||
|
self_.from.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn receive(self_: @This(), response: tp.message) !void {
|
||||||
var result: []const u8 = undefined;
|
var result: []const u8 = undefined;
|
||||||
if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.null_ })) {
|
if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.null_ })) {
|
||||||
try send_content_msg_empty(from, "hover", file_path, row, col);
|
try send_content_msg_empty(self_.from.ref(), "hover", self_.file_path, self_.row, self_.col);
|
||||||
} else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.array })) {
|
} else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.array })) {
|
||||||
if (try cbor.match(response.buf, .{ tp.any, tp.any, tp.any, tp.extract_cbor(&result) }))
|
if (try cbor.match(response.buf, .{ tp.any, tp.any, tp.any, tp.extract_cbor(&result) }))
|
||||||
try self.send_completion_items(from, file_path, row, col, result, false);
|
try send_completion_items(self_.from.ref(), self_.file_path, self_.row, self_.col, result, false);
|
||||||
} else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.map })) {
|
} else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.map })) {
|
||||||
if (try cbor.match(response.buf, .{ tp.any, tp.any, tp.any, tp.extract_cbor(&result) }))
|
if (try cbor.match(response.buf, .{ tp.any, tp.any, tp.any, tp.extract_cbor(&result) }))
|
||||||
try self.send_completion_list(from, file_path, row, col, result);
|
try send_completion_list(self_.from.ref(), self_.file_path, self_.row, self_.col, result);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} = .{
|
||||||
|
.from = from.clone(),
|
||||||
|
.file_path = try std.heap.c_allocator.dupe(u8, file_path),
|
||||||
|
.row = row,
|
||||||
|
.col = col,
|
||||||
|
};
|
||||||
|
|
||||||
|
lsp.send_request(self.allocator, "textDocument/completion", .{
|
||||||
|
.textDocument = .{ .uri = uri },
|
||||||
|
.position = .{ .line = row, .character = col },
|
||||||
|
}, handler) catch return error.LspFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_completion_list(self: *Self, to: tp.pid_ref, file_path: []const u8, row: usize, col: usize, result: []const u8) (ClientError || InvalidMessageError || cbor.Error)!void {
|
fn send_completion_list(to: tp.pid_ref, file_path: []const u8, row: usize, col: usize, result: []const u8) (ClientError || InvalidMessageError || cbor.Error)!void {
|
||||||
var iter = result;
|
var iter = result;
|
||||||
var len = cbor.decodeMapHeader(&iter) catch return;
|
var len = cbor.decodeMapHeader(&iter) catch return;
|
||||||
var items: []const u8 = "";
|
var items: []const u8 = "";
|
||||||
|
@ -821,20 +869,20 @@ fn send_completion_list(self: *Self, to: tp.pid_ref, file_path: []const u8, row:
|
||||||
try cbor.skipValue(&iter);
|
try cbor.skipValue(&iter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return self.send_completion_items(to, file_path, row, col, items, is_incomplete) catch error.ClientFailed;
|
return send_completion_items(to, file_path, row, col, items, is_incomplete) catch error.ClientFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_completion_items(self: *Self, to: tp.pid_ref, file_path: []const u8, row: usize, col: usize, items: []const u8, is_incomplete: bool) (ClientError || InvalidMessageError || cbor.Error)!void {
|
fn send_completion_items(to: tp.pid_ref, file_path: []const u8, row: usize, col: usize, items: []const u8, is_incomplete: bool) (ClientError || InvalidMessageError || cbor.Error)!void {
|
||||||
var iter = items;
|
var iter = items;
|
||||||
var len = cbor.decodeArrayHeader(&iter) catch return;
|
var len = cbor.decodeArrayHeader(&iter) catch return;
|
||||||
var item: []const u8 = "";
|
var item: []const u8 = "";
|
||||||
while (len > 0) : (len -= 1) {
|
while (len > 0) : (len -= 1) {
|
||||||
if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&item)))) return error.InvalidMessageField;
|
if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&item)))) return error.InvalidMessageField;
|
||||||
self.send_completion_item(to, file_path, row, col, item, if (len > 1) true else is_incomplete) catch return error.ClientFailed;
|
send_completion_item(to, file_path, row, col, item, if (len > 1) true else is_incomplete) catch return error.ClientFailed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_completion_item(_: *Self, to: tp.pid_ref, file_path: []const u8, row: usize, col: usize, item: []const u8, is_incomplete: bool) (ClientError || InvalidMessageError || cbor.Error)!void {
|
fn send_completion_item(to: tp.pid_ref, file_path: []const u8, row: usize, col: usize, item: []const u8, is_incomplete: bool) (ClientError || InvalidMessageError || cbor.Error)!void {
|
||||||
var label: []const u8 = "";
|
var label: []const u8 = "";
|
||||||
var label_detail: []const u8 = "";
|
var label_detail: []const u8 = "";
|
||||||
var label_description: []const u8 = "";
|
var label_description: []const u8 = "";
|
||||||
|
@ -948,22 +996,28 @@ pub fn rename_symbol(self: *Self, from: tp.pid_ref, file_path: []const u8, row:
|
||||||
const lsp = try self.get_language_server(file_path);
|
const lsp = try self.get_language_server(file_path);
|
||||||
const uri = try self.make_URI(file_path);
|
const uri = try self.make_URI(file_path);
|
||||||
defer self.allocator.free(uri);
|
defer self.allocator.free(uri);
|
||||||
const response = lsp.send_request(self.allocator, "textDocument/rename", .{
|
|
||||||
.textDocument = .{ .uri = uri },
|
const handler: struct {
|
||||||
.position = .{ .line = row, .character = col },
|
from: tp.pid,
|
||||||
.newName = "PLACEHOLDER",
|
file_path: []const u8,
|
||||||
}) catch return error.LspFailed;
|
|
||||||
defer self.allocator.free(response.buf);
|
pub fn deinit(self_: *@This()) void {
|
||||||
|
std.heap.c_allocator.free(self_.file_path);
|
||||||
|
self_.from.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn receive(self_: @This(), response: tp.message) !void {
|
||||||
|
const allocator = std.heap.c_allocator;
|
||||||
var result: []const u8 = undefined;
|
var result: []const u8 = undefined;
|
||||||
// buffer the renames in order to send as a single, atomic message
|
// buffer the renames in order to send as a single, atomic message
|
||||||
var renames = std.ArrayList(Rename).init(self.allocator);
|
var renames = std.ArrayList(Rename).init(allocator);
|
||||||
defer renames.deinit();
|
defer renames.deinit();
|
||||||
|
|
||||||
if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.map })) {
|
if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.map })) {
|
||||||
if (try cbor.match(response.buf, .{ tp.any, tp.any, tp.any, tp.extract_cbor(&result) })) {
|
if (try cbor.match(response.buf, .{ tp.any, tp.any, tp.any, tp.extract_cbor(&result) })) {
|
||||||
try self.decode_rename_symbol_map(result, &renames);
|
try decode_rename_symbol_map(result, &renames);
|
||||||
// write the renames message manually since there doesn't appear to be an array helper
|
// write the renames message manually since there doesn't appear to be an array helper
|
||||||
var msg_buf = std.ArrayList(u8).init(self.allocator);
|
var msg_buf = std.ArrayList(u8).init(allocator);
|
||||||
defer msg_buf.deinit();
|
defer msg_buf.deinit();
|
||||||
const w = msg_buf.writer();
|
const w = msg_buf.writer();
|
||||||
try cbor.writeArrayHeader(w, 3);
|
try cbor.writeArrayHeader(w, 3);
|
||||||
|
@ -980,7 +1034,7 @@ pub fn rename_symbol(self: *Self, from: tp.pid_ref, file_path: []const u8, row:
|
||||||
file_path_[i] = '\\';
|
file_path_[i] = '\\';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const line = try self.get_line_of_file(self.allocator, file_path, rename.range.start.line);
|
const line = try get_line_of_file(allocator, self_.file_path, rename.range.start.line);
|
||||||
try cbor.writeValue(w, .{
|
try cbor.writeValue(w, .{
|
||||||
file_path_,
|
file_path_,
|
||||||
rename.range.start.line,
|
rename.range.start.line,
|
||||||
|
@ -991,14 +1045,25 @@ pub fn rename_symbol(self: *Self, from: tp.pid_ref, file_path: []const u8, row:
|
||||||
line,
|
line,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
from.send_raw(.{ .buf = msg_buf.items }) catch return error.ClientFailed;
|
self_.from.send_raw(.{ .buf = msg_buf.items }) catch return error.ClientFailed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} = .{
|
||||||
|
.from = from.clone(),
|
||||||
|
.file_path = try std.heap.c_allocator.dupe(u8, file_path),
|
||||||
|
};
|
||||||
|
|
||||||
|
lsp.send_request(self.allocator, "textDocument/rename", .{
|
||||||
|
.textDocument = .{ .uri = uri },
|
||||||
|
.position = .{ .line = row, .character = col },
|
||||||
|
.newName = "PLACEHOLDER",
|
||||||
|
}, handler) catch return error.LspFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode a WorkspaceEdit record which may have shape {"changes": {}} or {"documentChanges": []}
|
// decode a WorkspaceEdit record which may have shape {"changes": {}} or {"documentChanges": []}
|
||||||
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspaceEdit
|
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspaceEdit
|
||||||
fn decode_rename_symbol_map(self: *Self, result: []const u8, renames: *std.ArrayList(Rename)) (ClientError || InvalidMessageError || cbor.Error)!void {
|
fn decode_rename_symbol_map(result: []const u8, renames: *std.ArrayList(Rename)) (ClientError || InvalidMessageError || cbor.Error)!void {
|
||||||
var iter = result;
|
var iter = result;
|
||||||
var len = cbor.decodeMapHeader(&iter) catch return error.InvalidMessage;
|
var len = cbor.decodeMapHeader(&iter) catch return error.InvalidMessage;
|
||||||
var changes: []const u8 = "";
|
var changes: []const u8 = "";
|
||||||
|
@ -1007,11 +1072,11 @@ fn decode_rename_symbol_map(self: *Self, result: []const u8, renames: *std.Array
|
||||||
if (!(try cbor.matchString(&iter, &field_name))) return error.InvalidMessage;
|
if (!(try cbor.matchString(&iter, &field_name))) return error.InvalidMessage;
|
||||||
if (std.mem.eql(u8, field_name, "changes")) {
|
if (std.mem.eql(u8, field_name, "changes")) {
|
||||||
if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&changes)))) return error.InvalidMessageField;
|
if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&changes)))) return error.InvalidMessageField;
|
||||||
try self.decode_rename_symbol_changes(changes, renames);
|
try decode_rename_symbol_changes(changes, renames);
|
||||||
return;
|
return;
|
||||||
} else if (std.mem.eql(u8, field_name, "documentChanges")) {
|
} else if (std.mem.eql(u8, field_name, "documentChanges")) {
|
||||||
if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&changes)))) return error.InvalidMessageField;
|
if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&changes)))) return error.InvalidMessageField;
|
||||||
try self.decode_rename_symbol_doc_changes(changes, renames);
|
try decode_rename_symbol_doc_changes(changes, renames);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
try cbor.skipValue(&iter);
|
try cbor.skipValue(&iter);
|
||||||
|
@ -1020,17 +1085,17 @@ fn decode_rename_symbol_map(self: *Self, result: []const u8, renames: *std.Array
|
||||||
return error.ClientFailed;
|
return error.ClientFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_rename_symbol_changes(self: *Self, changes: []const u8, renames: *std.ArrayList(Rename)) (ClientError || InvalidMessageError || cbor.Error)!void {
|
fn decode_rename_symbol_changes(changes: []const u8, renames: *std.ArrayList(Rename)) (ClientError || InvalidMessageError || cbor.Error)!void {
|
||||||
var iter = changes;
|
var iter = changes;
|
||||||
var files_len = cbor.decodeMapHeader(&iter) catch return error.InvalidMessage;
|
var files_len = cbor.decodeMapHeader(&iter) catch return error.InvalidMessage;
|
||||||
while (files_len > 0) : (files_len -= 1) {
|
while (files_len > 0) : (files_len -= 1) {
|
||||||
var file_uri: []const u8 = undefined;
|
var file_uri: []const u8 = undefined;
|
||||||
if (!(try cbor.matchString(&iter, &file_uri))) return error.InvalidMessage;
|
if (!(try cbor.matchString(&iter, &file_uri))) return error.InvalidMessage;
|
||||||
try decode_rename_symbol_item(self, file_uri, &iter, renames);
|
try decode_rename_symbol_item(file_uri, &iter, renames);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_rename_symbol_doc_changes(self: *Self, changes: []const u8, renames: *std.ArrayList(Rename)) (ClientError || InvalidMessageError || cbor.Error)!void {
|
fn decode_rename_symbol_doc_changes(changes: []const u8, renames: *std.ArrayList(Rename)) (ClientError || InvalidMessageError || cbor.Error)!void {
|
||||||
var iter = changes;
|
var iter = changes;
|
||||||
var changes_len = cbor.decodeArrayHeader(&iter) catch return error.InvalidMessage;
|
var changes_len = cbor.decodeArrayHeader(&iter) catch return error.InvalidMessage;
|
||||||
while (changes_len > 0) : (changes_len -= 1) {
|
while (changes_len > 0) : (changes_len -= 1) {
|
||||||
|
@ -1050,14 +1115,14 @@ fn decode_rename_symbol_doc_changes(self: *Self, changes: []const u8, renames: *
|
||||||
}
|
}
|
||||||
} else if (std.mem.eql(u8, field_name, "edits")) {
|
} else if (std.mem.eql(u8, field_name, "edits")) {
|
||||||
if (file_uri.len == 0) return error.InvalidMessage;
|
if (file_uri.len == 0) return error.InvalidMessage;
|
||||||
try decode_rename_symbol_item(self, file_uri, &iter, renames);
|
try decode_rename_symbol_item(file_uri, &iter, renames);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textEdit
|
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textEdit
|
||||||
fn decode_rename_symbol_item(_: *Self, file_uri: []const u8, iter: *[]const u8, renames: *std.ArrayList(Rename)) (ClientError || InvalidMessageError || cbor.Error)!void {
|
fn decode_rename_symbol_item(file_uri: []const u8, iter: *[]const u8, renames: *std.ArrayList(Rename)) (ClientError || InvalidMessageError || cbor.Error)!void {
|
||||||
var text_edits_len = cbor.decodeArrayHeader(iter) catch return error.InvalidMessage;
|
var text_edits_len = cbor.decodeArrayHeader(iter) catch return error.InvalidMessage;
|
||||||
while (text_edits_len > 0) : (text_edits_len -= 1) {
|
while (text_edits_len > 0) : (text_edits_len -= 1) {
|
||||||
var m_range: ?Range = null;
|
var m_range: ?Range = null;
|
||||||
|
@ -1088,20 +1153,39 @@ pub fn hover(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, c
|
||||||
defer self.allocator.free(uri);
|
defer self.allocator.free(uri);
|
||||||
// log.logger("lsp").print("fetching hover information...", .{});
|
// log.logger("lsp").print("fetching hover information...", .{});
|
||||||
|
|
||||||
const response = lsp.send_request(self.allocator, "textDocument/hover", .{
|
const handler: struct {
|
||||||
.textDocument = .{ .uri = uri },
|
from: tp.pid,
|
||||||
.position = .{ .line = row, .character = col },
|
file_path: []const u8,
|
||||||
}) catch return error.LspFailed;
|
row: usize,
|
||||||
defer self.allocator.free(response.buf);
|
col: usize,
|
||||||
|
|
||||||
|
pub fn deinit(self_: *@This()) void {
|
||||||
|
self_.from.deinit();
|
||||||
|
std.heap.c_allocator.free(self_.file_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn receive(self_: @This(), response: tp.message) !void {
|
||||||
var result: []const u8 = undefined;
|
var result: []const u8 = undefined;
|
||||||
if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.null_ })) {
|
if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.null_ })) {
|
||||||
try send_content_msg_empty(from, "hover", file_path, row, col);
|
try send_content_msg_empty(self_.from.ref(), "hover", self_.file_path, self_.row, self_.col);
|
||||||
} else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.extract_cbor(&result) })) {
|
} else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.extract_cbor(&result) })) {
|
||||||
try self.send_hover(from, file_path, row, col, result);
|
try send_hover(self_.from.ref(), self_.file_path, self_.row, self_.col, result);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} = .{
|
||||||
|
.from = from.clone(),
|
||||||
|
.file_path = try std.heap.c_allocator.dupe(u8, file_path),
|
||||||
|
.row = row,
|
||||||
|
.col = col,
|
||||||
|
};
|
||||||
|
|
||||||
|
lsp.send_request(self.allocator, "textDocument/hover", .{
|
||||||
|
.textDocument = .{ .uri = uri },
|
||||||
|
.position = .{ .line = row, .character = col },
|
||||||
|
}, handler) catch return error.LspFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_hover(self: *Self, to: tp.pid_ref, file_path: []const u8, row: usize, col: usize, result: []const u8) (ClientError || InvalidMessageError || cbor.Error)!void {
|
fn send_hover(to: tp.pid_ref, file_path: []const u8, row: usize, col: usize, result: []const u8) (ClientError || InvalidMessageError || cbor.Error)!void {
|
||||||
var iter = result;
|
var iter = result;
|
||||||
var len = cbor.decodeMapHeader(&iter) catch return;
|
var len = cbor.decodeMapHeader(&iter) catch return;
|
||||||
var contents: []const u8 = "";
|
var contents: []const u8 = "";
|
||||||
|
@ -1120,11 +1204,10 @@ fn send_hover(self: *Self, to: tp.pid_ref, file_path: []const u8, row: usize, co
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (contents.len > 0)
|
if (contents.len > 0)
|
||||||
return self.send_contents(to, "hover", file_path, row, col, contents, range);
|
return send_contents(to, "hover", file_path, row, col, contents, range);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_contents(
|
fn send_contents(
|
||||||
self: *Self,
|
|
||||||
to: tp.pid_ref,
|
to: tp.pid_ref,
|
||||||
tag: []const u8,
|
tag: []const u8,
|
||||||
file_path: []const u8,
|
file_path: []const u8,
|
||||||
|
@ -1147,7 +1230,7 @@ fn send_contents(
|
||||||
};
|
};
|
||||||
|
|
||||||
if (is_list) {
|
if (is_list) {
|
||||||
var content = std.ArrayList(u8).init(self.allocator);
|
var content = std.ArrayList(u8).init(std.heap.c_allocator);
|
||||||
defer content.deinit();
|
defer content.deinit();
|
||||||
while (len > 0) : (len -= 1) {
|
while (len > 0) : (len -= 1) {
|
||||||
if (try cbor.matchValue(&iter, cbor.extract(&value))) {
|
if (try cbor.matchValue(&iter, cbor.extract(&value))) {
|
||||||
|
@ -1364,8 +1447,30 @@ pub fn send_lsp_response(self: *Self, from: tp.pid_ref, cbor_id: []const u8, res
|
||||||
from.send_raw(.{ .buf = cb.items }) catch return error.ClientFailed;
|
from.send_raw(.{ .buf = cb.items }) catch return error.ClientFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_lsp_init_request(self: *Self, lsp: LSP, project_path: []const u8, project_basename: []const u8, project_uri: []const u8) CallError!tp.message {
|
fn send_lsp_init_request(self: *Self, lsp: LSP, project_path: []const u8, project_basename: []const u8, project_uri: []const u8, language_server: []const u8) !void {
|
||||||
return lsp.send_request(self.allocator, "initialize", .{
|
const handler: struct {
|
||||||
|
language_server: []const u8,
|
||||||
|
lsp: LSP,
|
||||||
|
|
||||||
|
pub fn deinit(self_: *@This()) void {
|
||||||
|
self_.lsp.pid.deinit();
|
||||||
|
std.heap.c_allocator.free(self_.language_server);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn receive(self_: @This(), _: tp.message) !void {
|
||||||
|
self_.lsp.send_notification("initialized", .{}) catch return error.LspFailed;
|
||||||
|
if (self_.lsp.pid.expired()) return error.LspFailed;
|
||||||
|
log.logger("lsp").print("initialized LSP: {s}", .{fmt_lsp_name_func(self_.language_server)});
|
||||||
|
}
|
||||||
|
} = .{
|
||||||
|
.language_server = try std.heap.c_allocator.dupe(u8, language_server),
|
||||||
|
.lsp = .{
|
||||||
|
.allocator = lsp.allocator,
|
||||||
|
.pid = lsp.pid.clone(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try lsp.send_request(self.allocator, "initialize", .{
|
||||||
.processId = if (builtin.os.tag == .linux) std.os.linux.getpid() else null,
|
.processId = if (builtin.os.tag == .linux) std.os.linux.getpid() else null,
|
||||||
.rootPath = project_path,
|
.rootPath = project_path,
|
||||||
.rootUri = project_uri,
|
.rootUri = project_uri,
|
||||||
|
@ -1667,7 +1772,7 @@ fn send_lsp_init_request(self: *Self, lsp: LSP, project_path: []const u8, projec
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
}, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fmt_lsp_name_func(bytes: []const u8) std.fmt.Formatter(format_lsp_name_func) {
|
fn fmt_lsp_name_func(bytes: []const u8) std.fmt.Formatter(format_lsp_name_func) {
|
||||||
|
@ -1698,7 +1803,7 @@ const eol = '\n';
|
||||||
|
|
||||||
pub const GetLineOfFileError = (OutOfMemoryError || std.fs.File.OpenError || std.fs.File.Reader.Error);
|
pub const GetLineOfFileError = (OutOfMemoryError || std.fs.File.OpenError || std.fs.File.Reader.Error);
|
||||||
|
|
||||||
fn get_line_of_file(self: *Self, allocator: std.mem.Allocator, file_path: []const u8, line_: usize) GetLineOfFileError![]const u8 {
|
fn get_line_of_file(allocator: std.mem.Allocator, file_path: []const u8, line_: usize) GetLineOfFileError![]const u8 {
|
||||||
const line = line_ + 1;
|
const line = line_ + 1;
|
||||||
const file = try std.fs.cwd().openFile(file_path, .{ .mode = .read_only });
|
const file = try std.fs.cwd().openFile(file_path, .{ .mode = .read_only });
|
||||||
defer file.close();
|
defer file.close();
|
||||||
|
@ -1712,13 +1817,13 @@ fn get_line_of_file(self: *Self, allocator: std.mem.Allocator, file_path: []cons
|
||||||
var line_count: usize = 1;
|
var line_count: usize = 1;
|
||||||
for (0..buf.len) |i| {
|
for (0..buf.len) |i| {
|
||||||
if (line_count == line)
|
if (line_count == line)
|
||||||
return self.get_line(allocator, buf[i..]);
|
return get_line(allocator, buf[i..]);
|
||||||
if (buf[i] == eol) line_count += 1;
|
if (buf[i] == eol) line_count += 1;
|
||||||
}
|
}
|
||||||
return allocator.dupe(u8, "");
|
return allocator.dupe(u8, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_line(_: *Self, allocator: std.mem.Allocator, buf: []const u8) ![]const u8 {
|
pub fn get_line(allocator: std.mem.Allocator, buf: []const u8) ![]const u8 {
|
||||||
for (0..buf.len) |i| {
|
for (0..buf.len) |i| {
|
||||||
if (buf[i] == eol) return allocator.dupe(u8, buf[0..i]);
|
if (buf[i] == eol) return allocator.dupe(u8, buf[0..i]);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue