Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
639d7b6fbe
WIP feat: add omnisharp specific LSP requests 2025-09-16 21:38:47 +02:00
4 changed files with 131 additions and 5 deletions

View file

@ -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);

View file

@ -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();

View file

@ -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" };

View file

@ -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 => "󰊕",