feat: first (mostly) working version of goto_definition command
Tested only with zls and clangd so far.
This commit is contained in:
parent
7e17f713e7
commit
fc15c8bda6
6 changed files with 186 additions and 56 deletions
149
src/Project.zig
149
src/Project.zig
|
@ -9,8 +9,8 @@ a: std.mem.Allocator,
|
|||
name: []const u8,
|
||||
files: std.ArrayList(File),
|
||||
open_time: i64,
|
||||
lsp: ?LSP = null,
|
||||
lsp_name: [:0]const u8,
|
||||
language_servers: std.StringHashMap(LSP),
|
||||
file_language_server: std.StringHashMap(LSP),
|
||||
|
||||
const Self = @This();
|
||||
|
||||
|
@ -25,27 +25,44 @@ pub fn init(a: std.mem.Allocator, name: []const u8) error{OutOfMemory}!Self {
|
|||
.name = try a.dupe(u8, name),
|
||||
.files = std.ArrayList(File).init(a),
|
||||
.open_time = std.time.milliTimestamp(),
|
||||
.lsp_name = "zls",
|
||||
.language_servers = std.StringHashMap(LSP).init(a),
|
||||
.file_language_server = std.StringHashMap(LSP).init(a),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
var i_ = self.file_language_server.iterator();
|
||||
while (i_.next()) |p| {
|
||||
self.a.free(p.key_ptr.*);
|
||||
}
|
||||
var i = self.language_servers.iterator();
|
||||
while (i.next()) |p| {
|
||||
self.a.free(p.key_ptr.*);
|
||||
p.value_ptr.*.deinit();
|
||||
}
|
||||
for (self.files.items) |file| self.a.free(file.path);
|
||||
self.files.deinit();
|
||||
if (self.lsp) |*lsp| lsp.deinit();
|
||||
self.a.free(self.name);
|
||||
}
|
||||
|
||||
fn get_lsp(self: *Self) !LSP {
|
||||
if (self.lsp) |lsp| return lsp;
|
||||
self.lsp = try LSP.open(self.a, tp.message.fmt(.{self.lsp_name}), self.lsp_name);
|
||||
fn get_lsp(self: *Self, language_server: []const u8) !LSP {
|
||||
if (self.language_servers.get(language_server)) |lsp| return lsp;
|
||||
const lsp = try LSP.open(self.a, .{ .buf = language_server });
|
||||
try self.language_servers.put(try self.a.dupe(u8, language_server), lsp);
|
||||
const uri = try self.make_URI(null);
|
||||
defer self.a.free(uri);
|
||||
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 response = try self.send_lsp_init_request(self.name, basename, uri);
|
||||
const response = try self.send_lsp_init_request(lsp, self.name, basename, uri);
|
||||
defer self.a.free(response.buf);
|
||||
return self.lsp.?;
|
||||
try lsp.send_notification("initialized", .{});
|
||||
return lsp;
|
||||
}
|
||||
|
||||
fn get_file_lsp(self: *Self, file_path: []const u8) !LSP {
|
||||
const lsp = self.file_language_server.get(file_path) orelse return tp.exit("no language server");
|
||||
if (lsp.pid.expired()) return tp.exit("no language server");
|
||||
return lsp;
|
||||
}
|
||||
|
||||
fn make_URI(self: *Self, file_path: ?[]const u8) ![]const u8 {
|
||||
|
@ -92,9 +109,12 @@ pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []co
|
|||
return i;
|
||||
}
|
||||
|
||||
pub fn did_open(self: *Self, from: tp.pid_ref, file_path: []const u8, file_type: []const u8, version: usize, text: []const u8) tp.result {
|
||||
_ = from; // autofix
|
||||
const lsp = self.get_lsp() catch |e| return tp.exit_error(e);
|
||||
pub fn did_open(self: *Self, file_path: []const u8, file_type: []const u8, language_server: []const u8, version: usize, text: []const u8) tp.result {
|
||||
const lsp = self.get_lsp(language_server) catch |e| return tp.exit_error(e);
|
||||
if (!self.file_language_server.contains(file_path)) {
|
||||
const key = self.a.dupe(u8, file_path) catch |e| return tp.exit_error(e);
|
||||
self.file_language_server.put(key, lsp) catch |e| return tp.exit_error(e);
|
||||
}
|
||||
const uri = self.make_URI(file_path) catch |e| return tp.exit_error(e);
|
||||
defer self.a.free(uri);
|
||||
try lsp.send_notification("textDocument/didOpen", .{
|
||||
|
@ -102,8 +122,8 @@ pub fn did_open(self: *Self, from: tp.pid_ref, file_path: []const u8, file_type:
|
|||
});
|
||||
}
|
||||
|
||||
pub fn goto_definition(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, col: usize) tp.result {
|
||||
const lsp = self.get_lsp() catch |e| return tp.exit_error(e);
|
||||
pub fn goto_definition(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 = self.make_URI(file_path) catch |e| return tp.exit_error(e);
|
||||
defer self.a.free(uri);
|
||||
const response = try lsp.send_request(self.a, "textDocument/definition", .{
|
||||
|
@ -111,11 +131,100 @@ pub fn goto_definition(self: *Self, from: tp.pid_ref, file_path: []const u8, row
|
|||
.position = .{ .line = row, .character = col },
|
||||
});
|
||||
defer self.a.free(response.buf);
|
||||
try from.send_raw(response);
|
||||
var link: []const u8 = undefined;
|
||||
if (try response.match(.{ "child", tp.string, "result", tp.array })) {
|
||||
if (try response.match(.{ tp.any, tp.any, tp.any, .{ tp.extract_cbor(&link), tp.more } })) {
|
||||
try self.navigate_to_location_link(from, link);
|
||||
} else if (try response.match(.{ tp.any, tp.any, tp.any, .{tp.extract_cbor(&link)} })) {
|
||||
try self.navigate_to_location_link(from, link);
|
||||
}
|
||||
} else if (try response.match(.{ "child", tp.string, "result", tp.null_ })) {
|
||||
return;
|
||||
} else if (try response.match(.{ "child", tp.string, "result", tp.extract_cbor(&link) })) {
|
||||
try self.navigate_to_location_link(from, link);
|
||||
}
|
||||
}
|
||||
|
||||
fn send_lsp_init_request(self: *Self, project_path: []const u8, project_basename: []const u8, project_uri: []const u8) error{Exit}!tp.message {
|
||||
return self.lsp.?.send_request(self.a, "initialize", .{
|
||||
fn navigate_to_location_link(self: *Self, from: tp.pid_ref, location_link: []const u8) !void {
|
||||
var iter = location_link;
|
||||
var targetUri: ?[]const u8 = null;
|
||||
var targetRange: ?Range = null;
|
||||
var targetSelectionRange: ?Range = null;
|
||||
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, "targetUri") or std.mem.eql(u8, field_name, "uri")) {
|
||||
var value: []const u8 = undefined;
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&value)))) return error.InvalidMessageField;
|
||||
targetUri = value;
|
||||
} else if (std.mem.eql(u8, field_name, "targetRange") or std.mem.eql(u8, field_name, "range")) {
|
||||
var range: []const u8 = undefined;
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&range)))) return error.InvalidMessageField;
|
||||
targetRange = try read_range(range);
|
||||
} else if (std.mem.eql(u8, field_name, "targetSelectionRange")) {
|
||||
var range: []const u8 = undefined;
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&range)))) return error.InvalidMessageField;
|
||||
targetSelectionRange = try read_range(range);
|
||||
} else {
|
||||
try cbor.skipValue(&iter);
|
||||
}
|
||||
}
|
||||
if (targetUri == null or targetRange == null) return error.InvalidMessageField;
|
||||
if (!std.mem.eql(u8, targetUri.?[0..7], "file://")) return error.InvalidTargetURI;
|
||||
const file_path = try std.Uri.unescapeString(self.a, targetUri.?[7..]);
|
||||
defer self.a.free(file_path);
|
||||
try from.send(.{ "cmd", "navigate", .{ .file = file_path, .line = targetRange.?.start.line + 1, .column = targetRange.?.start.character + 1 } });
|
||||
}
|
||||
|
||||
const Range = struct { start: Position, end: Position };
|
||||
fn read_range(range: []const u8) !Range {
|
||||
var iter = range;
|
||||
var start: ?Position = null;
|
||||
var end: ?Position = null;
|
||||
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, "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")) {
|
||||
var position: []const u8 = undefined;
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&position)))) return error.InvalidMessageField;
|
||||
end = try read_position(position);
|
||||
} else {
|
||||
try cbor.skipValue(&iter);
|
||||
}
|
||||
}
|
||||
if (start == null or end == null) return error.InvalidMessageField;
|
||||
return .{ .start = start.?, .end = end.? };
|
||||
}
|
||||
|
||||
const Position = struct { line: usize, character: usize };
|
||||
fn read_position(position: []const u8) !Position {
|
||||
var iter = position;
|
||||
var line: ?usize = 0;
|
||||
var character: ?usize = 0;
|
||||
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, "line")) {
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&line)))) return error.InvalidMessageField;
|
||||
} else if (std.mem.eql(u8, field_name, "character")) {
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&character)))) return error.InvalidMessageField;
|
||||
} else {
|
||||
try cbor.skipValue(&iter);
|
||||
}
|
||||
}
|
||||
if (line == null or character == null) return error.InvalidMessageField;
|
||||
return .{ .line = line.?, .character = character.? };
|
||||
}
|
||||
|
||||
fn send_lsp_init_request(self: *Self, lsp: LSP, project_path: []const u8, project_basename: []const u8, project_uri: []const u8) error{Exit}!tp.message {
|
||||
return lsp.send_request(self.a, "initialize", .{
|
||||
.processId = std.os.linux.getpid(),
|
||||
.rootPath = project_path,
|
||||
.rootUri = project_uri,
|
||||
|
@ -145,7 +254,7 @@ fn send_lsp_init_request(self: *Self, project_path: []const u8, project_basename
|
|||
.normalizesLineEndings = true,
|
||||
.changeAnnotationSupport = .{ .groupsOnLabel = true },
|
||||
},
|
||||
.configuration = true,
|
||||
// .configuration = true,
|
||||
.didChangeWatchedFiles = .{
|
||||
.dynamicRegistration = true,
|
||||
.relativePatternSupport = true,
|
||||
|
@ -160,7 +269,7 @@ fn send_lsp_init_request(self: *Self, project_path: []const u8, project_basename
|
|||
},
|
||||
.codeLens = .{ .refreshSupport = true },
|
||||
.executeCommand = .{ .dynamicRegistration = true },
|
||||
.didChangeConfiguration = .{ .dynamicRegistration = true },
|
||||
// .didChangeConfiguration = .{ .dynamicRegistration = true },
|
||||
.workspaceFolders = true,
|
||||
.semanticTokens = .{ .refreshSupport = true },
|
||||
.fileOperations = .{
|
||||
|
@ -402,7 +511,7 @@ fn send_lsp_init_request(self: *Self, project_path: []const u8, project_basename
|
|||
.parser = "marked",
|
||||
.version = "1.1.0",
|
||||
},
|
||||
.positionEncodings = .{"utf-16"},
|
||||
.positionEncodings = .{"utf-8"},
|
||||
},
|
||||
.notebookDocument = .{
|
||||
.synchronization = .{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue