diff --git a/src/Project.zig b/src/Project.zig index 92ba4d4..22fd94d 100644 --- a/src/Project.zig +++ b/src/Project.zig @@ -320,6 +320,18 @@ fn make_URI(self: *Self, file_path: ?[]const u8) LspError![]const u8 { return buf.toOwnedSlice(); } +fn make_absolute_file_path(self: *Self, file_path: ?[]const u8) LspError![]const u8 { + var buf = std.ArrayList(u8).init(self.allocator); + if (file_path) |path| { + if (std.fs.path.isAbsolute(path)) { + try buf.writer().print("{s}", .{path}); + } else { + try buf.writer().print("{s}{c}{s}", .{ self.name, std.fs.path.sep, path }); + } + } else try buf.writer().print("{s}", .{self.name}); + return buf.toOwnedSlice(); +} + fn sort_files_by_mtime(self: *Self) void { sort_by_mtime(File, self.files.items); } @@ -835,6 +847,102 @@ fn navigate_to_location_link(from: tp.pid_ref, location_link: []const u8) (Clien } } +pub fn goto_definition_omnisharp(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, col: usize) SendGotoRequestError!void { + return self.send_goto_request_omnisharp(from, file_path, row, col, "o#/v2/gotodefinition"); +} + +fn send_goto_request_omnisharp(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, col: usize, method: []const u8) SendGotoRequestError!void { + const lsp = try self.get_language_server(file_path); + const abs_file_path = try self.make_absolute_file_path(file_path); + defer self.allocator.free(abs_file_path); + + const handler: struct { + from: tp.pid, + name: []const u8, + project: *Self, + + 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 result: []const u8 = undefined; + if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.null_ })) { + return; + } else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.extract_cbor(&result) })) { + try navigate_to_definitions_omnisharp(self_.from.ref(), result); + } + } + } = .{ + .from = from.clone(), + .name = try std.heap.c_allocator.dupe(u8, self.name), + .project = self, + }; + + lsp.send_request(self.allocator, method, .{ + .FileName = abs_file_path, + .Line = row, + .Column = col, + .Timeout = 10000, + }, handler) catch return error.LspFailed; +} + +fn navigate_to_definitions_omnisharp(from: tp.pid_ref, result: []const u8) (ClientError || InvalidMessageError || cbor.Error)!void { + var iter = result; + + var result_len = try cbor.decodeMapHeader(&iter); + + while (result_len > 0) : (result_len -= 1) { + if (!(try cbor.matchValue(&iter, "Definitions"))) { + try cbor.skipValue(&iter); + continue; + } + var definitions_len = try cbor.decodeArrayHeader(&iter); + while (definitions_len > 0) : (definitions_len -= 1) { + var file_path: ?[]const u8 = null; + var range: ?Range = null; + var len_ = try cbor.decodeMapHeader(&iter); + while (len_ > 0) : (len_ -= 1) { + if (!(try cbor.matchValue(&iter, "Location"))) { + try cbor.skipValue(&iter); + continue; + } + var len = try cbor.decodeMapHeader(&iter); + 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, "FileName")) { + var value: []const u8 = undefined; + if (!(try cbor.matchValue(&iter, cbor.extract(&value)))) return error.InvalidMessageField; + file_path = value; + } else if (std.mem.eql(u8, field_name, "Range")) { + var value: []const u8 = undefined; + if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&value)))) return error.InvalidMessageField; + range = try read_range(value); + } else { + try cbor.skipValue(&iter); + } + } + } + if (file_path == null or range == null) return error.InvalidMessageField; + if (builtin.os.tag == .windows) { + if (file_path[0] == '/') file_path = file_path[1..]; + for (file_path, 0..) |c, i| if (c == '/') { + file_path[i] = '\\'; + }; + } + from.send(.{ "cmd", "navigate", .{ + .file = file_path, + .goto = .{ + range.?.start.line + 1, + range.?.start.character + 1, + }, + } }) catch return error.ClientFailed; + } + } +} + pub fn references(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, col: usize) SendGotoRequestError!void { const lsp = try self.get_language_server(file_path); const uri = try self.make_URI(file_path); @@ -1500,11 +1608,11 @@ fn read_range(range: []const u8) !Range { 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, "start")) { + if (std.mem.eql(u8, field_name, "start") or std.mem.eql(u8, field_name, "Start")) { var position: []const u8 = undefined; if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&position)))) return error.InvalidMessageField; start = try read_position(position); - } else if (std.mem.eql(u8, field_name, "end")) { + } else if (std.mem.eql(u8, field_name, "end") or std.mem.eql(u8, field_name, "End")) { var position: []const u8 = undefined; if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&position)))) return error.InvalidMessageField; end = try read_position(position); @@ -1525,9 +1633,9 @@ fn read_position(position: []const u8) !Position { 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, "line")) { + if (std.mem.eql(u8, field_name, "line") or std.mem.eql(u8, field_name, "Line")) { if (!(try cbor.matchValue(&iter, cbor.extract(&line)))) return error.InvalidMessageField; - } else if (std.mem.eql(u8, field_name, "character")) { + } else if (std.mem.eql(u8, field_name, "character") or std.mem.eql(u8, field_name, "Column")) { if (!(try cbor.matchValue(&iter, cbor.extract(&character)))) return error.InvalidMessageField; } else { try cbor.skipValue(&iter); diff --git a/src/project_manager.zig b/src/project_manager.zig index 44e2f31..64ffbb8 100644 --- a/src/project_manager.zig +++ b/src/project_manager.zig @@ -199,6 +199,13 @@ pub fn goto_type_definition(file_path: []const u8, row: usize, col: usize) (Proj return send(.{ "goto_type_definition", project, file_path, row, col }); } +pub fn goto_definition_omnisharp(file_path: []const u8, row: usize, col: usize) (ProjectManagerError || ProjectError)!void { + const project = tp.env.get().str("project"); + if (project.len == 0) + return error.NoProject; + return send(.{ "goto_definition_omnisharp", project, file_path, row, col }); +} + pub fn references(file_path: []const u8, row: usize, col: usize) (ProjectManagerError || ProjectError)!void { const project = tp.env.get().str("project"); if (project.len == 0) @@ -397,6 +404,8 @@ const Process = struct { self.goto_implementation(from, project_directory, path, row, col) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed; } else if (try cbor.match(m.buf, .{ "goto_type_definition", tp.extract(&project_directory), tp.extract(&path), tp.extract(&row), tp.extract(&col) })) { self.goto_type_definition(from, project_directory, path, row, col) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed; + } else if (try cbor.match(m.buf, .{ "goto_definition_omnisharp", tp.extract(&project_directory), tp.extract(&path), tp.extract(&row), tp.extract(&col) })) { + self.goto_definition_omnisharp(from, project_directory, path, row, col) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed; } else if (try cbor.match(m.buf, .{ "references", tp.extract(&project_directory), tp.extract(&path), tp.extract(&row), tp.extract(&col) })) { self.references(from, project_directory, path, row, col) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed; } else if (try cbor.match(m.buf, .{ "completion", tp.extract(&project_directory), tp.extract(&path), tp.extract(&row), tp.extract(&col) })) { @@ -569,6 +578,13 @@ const Process = struct { return project.goto_type_definition(from, file_path, row, col); } + fn goto_definition_omnisharp(self: *Process, from: tp.pid_ref, project_directory: []const u8, file_path: []const u8, row: usize, col: usize) (ProjectError || Project.SendGotoRequestError)!void { + const frame = tracy.initZone(@src(), .{ .name = module_name ++ ".goto_definition_omnisharp" }); + defer frame.deinit(); + const project = self.projects.get(project_directory) orelse return error.NoProject; + return project.goto_definition_omnisharp(from, file_path, row, col); + } + fn references(self: *Process, from: tp.pid_ref, project_directory: []const u8, file_path: []const u8, row: usize, col: usize) (ProjectError || Project.SendGotoRequestError)!void { const frame = tracy.initZone(@src(), .{ .name = module_name ++ ".references" }); defer frame.deinit(); diff --git a/src/tui/editor.zig b/src/tui/editor.zig index e64168b..494fe33 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -5480,7 +5480,7 @@ pub const Editor = struct { pub fn goto_definition(self: *Self, _: Context) Result { const file_path = self.file_path orelse return; const primary = self.get_primary(); - return project_manager.goto_definition(file_path, primary.cursor.row, primary.cursor.col); + return project_manager.goto_definition_omnisharp(file_path, primary.cursor.row, primary.cursor.col); } pub const goto_definition_meta: Meta = .{ .description = "Language: Goto definition" }; diff --git a/src/tui/mode/overlay/completion_palette.zig b/src/tui/mode/overlay/completion_palette.zig index eac3856..defc212 100644 --- a/src/tui/mode/overlay/completion_palette.zig +++ b/src/tui/mode/overlay/completion_palette.zig @@ -117,6 +117,7 @@ fn select(menu: **Type.MenuState, button: *Type.ButtonState) void { } const CompletionItemKind = enum(u8) { + None = 0, Text = 1, Method = 2, Function = 3, @@ -146,6 +147,7 @@ const CompletionItemKind = enum(u8) { fn kind_icon(kind: CompletionItemKind) []const u8 { return switch (kind) { + .None => " ", .Text => "󰊄", .Method => "îȘŒ", .Function => "󰊕",