From c894ae6deacada6a7f07e70ea1ddf6deb8c3664a Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 10 Sep 2024 20:22:31 +0200 Subject: [PATCH] feat: add support for LSP hover requests --- src/Project.zig | 87 +++++++++++++++++++++++++++++++++++++++++ src/project_manager.zig | 16 ++++++++ 2 files changed, 103 insertions(+) diff --git a/src/Project.zig b/src/Project.zig index 4dac62e..2f2f7c0 100644 --- a/src/Project.zig +++ b/src/Project.zig @@ -599,6 +599,93 @@ pub fn completion(self: *Self, _: tp.pid_ref, file_path: []const u8, row: usize, defer self.allocator.free(response.buf); } +pub fn hover(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, col: usize) !void { + const lsp = try self.get_file_lsp(file_path); + const uri = try self.make_URI(file_path); + defer self.allocator.free(uri); + log.logger("lsp").print("fetching hover information...", .{}); + + const response = try lsp.send_request(self.allocator, "textDocument/hover", .{ + .textDocument = .{ .uri = uri }, + .position = .{ .line = row, .character = col }, + }); + defer self.allocator.free(response.buf); + var result: []const u8 = undefined; + if (try response.match(.{ "child", tp.string, "result", tp.null_ })) { + return; + } else if (try response.match(.{ "child", tp.string, "result", tp.extract_cbor(&result) })) { + try self.send_hover(from, file_path, row, col, result); + } +} + +fn send_hover(self: *Self, to: tp.pid_ref, file_path: []const u8, row: usize, col: usize, result: []const u8) !void { + var iter = result; + var len = cbor.decodeMapHeader(&iter) catch return; + while (len > 0) : (len -= 1) { + var field_name: []const u8 = undefined; + if (!(try cbor.matchString(&iter, &field_name))) return error.InvalidMessage; + if (std.mem.eql(u8, field_name, "contents")) { + var value: []const u8 = ""; + if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&value)))) return error.InvalidMessageField; + return self.send_contents(to, "hover", file_path, row, col, value); + } else { + try cbor.skipValue(&iter); + } + } +} + +fn send_contents(self: *Self, to: tp.pid_ref, tag: []const u8, file_path: []const u8, row: usize, col: usize, result: []const u8) !void { + var iter = result; + var kind: []const u8 = "plaintext"; + var value: []const u8 = ""; + if (try cbor.matchValue(&iter, cbor.extract(&value))) + return send_content_msg(to, tag, file_path, row, col, kind, value); + + var is_list = true; + var len = cbor.decodeArrayHeader(&iter) catch blk: { + is_list = false; + iter = result; + break :blk cbor.decodeMapHeader(&iter) catch return; + }; + + if (is_list) { + var content = std.ArrayList(u8).init(self.allocator); + defer content.deinit(); + while (len > 0) : (len -= 1) { + if (try cbor.matchValue(&iter, cbor.extract(&value))) { + try content.appendSlice(value); + if (len > 1) try content.appendSlice("\n"); + } + } + return send_content_msg(to, tag, file_path, row, col, kind, content.items); + } + + while (len > 0) : (len -= 1) { + var field_name: []const u8 = undefined; + if (!(try cbor.matchString(&iter, &field_name))) return error.InvalidMessage; + if (std.mem.eql(u8, field_name, "kind")) { + if (!(try cbor.matchValue(&iter, cbor.extract(&kind)))) return error.InvalidMessageField; + } else if (std.mem.eql(u8, field_name, "value")) { + if (!(try cbor.matchValue(&iter, cbor.extract(&value)))) return error.InvalidMessageField; + } else { + try cbor.skipValue(&iter); + } + } + return send_content_msg(to, tag, file_path, row, col, kind, value); +} + +fn send_content_msg( + to: tp.pid_ref, + tag: []const u8, + file_path: []const u8, + row: usize, + col: usize, + kind: []const u8, + content: []const u8, +) !void { + return try to.send(.{ tag, file_path, row, col, kind, content }); +} + pub fn publish_diagnostics(self: *Self, to: tp.pid_ref, params_cb: []const u8) !void { var uri: ?[]const u8 = null; var diagnostics: []const u8 = &.{}; diff --git a/src/project_manager.zig b/src/project_manager.zig index 42a811c..ede4734 100644 --- a/src/project_manager.zig +++ b/src/project_manager.zig @@ -152,6 +152,13 @@ pub fn completion(file_path: []const u8, row: usize, col: usize) !void { return (try get()).pid.send(.{ "completion", project, file_path, row, col }); } +pub fn hover(file_path: []const u8, row: usize, col: usize) !void { + const project = tp.env.get().str("project"); + if (project.len == 0) + return tp.exit("No project"); + return (try get()).pid.send(.{ "hover", project, file_path, row, col }); +} + pub fn update_mru(file_path: []const u8, row: usize, col: usize) !void { const project = tp.env.get().str("project"); if (project.len == 0) @@ -294,6 +301,8 @@ const Process = struct { self.references(from, project_directory, path, row, col) catch |e| return from.forward_error(e, @errorReturnTrace()); } else if (try m.match(.{ "completion", tp.extract(&project_directory), tp.extract(&path), tp.extract(&row), tp.extract(&col) })) { self.completion(from, project_directory, path, row, col) catch |e| return from.forward_error(e, @errorReturnTrace()); + } else if (try m.match(.{ "hover", tp.extract(&project_directory), tp.extract(&path), tp.extract(&row), tp.extract(&col) })) { + self.hover(from, project_directory, path, row, col) catch |e| return from.forward_error(e, @errorReturnTrace()); } else if (try m.match(.{ "get_mru_position", tp.extract(&project_directory), tp.extract(&path) })) { self.get_mru_position(from, project_directory, path) catch |e| return from.forward_error(e, @errorReturnTrace()); } else if (try m.match(.{"shutdown"})) { @@ -443,6 +452,13 @@ const Process = struct { return project.completion(from, file_path, row, col); } + fn hover(self: *Process, from: tp.pid_ref, project_directory: []const u8, file_path: []const u8, row: usize, col: usize) !void { + const frame = tracy.initZone(@src(), .{ .name = module_name ++ ".hover" }); + defer frame.deinit(); + const project = self.projects.get(project_directory) orelse return tp.exit("No project"); + return project.hover(from, file_path, row, col); + } + fn get_mru_position(self: *Process, from: tp.pid_ref, project_directory: []const u8, file_path: []const u8) !void { const frame = tracy.initZone(@src(), .{ .name = module_name ++ ".get_mru_position" }); defer frame.deinit();