refactor: use explicit error sets in MessageFilter and project_manager

This commit is contained in:
CJ van den Berg 2024-09-19 21:54:18 +02:00
parent 6fac0b1cb4
commit 0542fdc680
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
12 changed files with 396 additions and 341 deletions

View file

@ -18,8 +18,8 @@
.hash = "1220220dbc7fe91c1c54438193ca765cebbcb7d58f35cdcaee404a9d2245a42a4362",
},
.thespian = .{
.url = "https://github.com/neurocyte/thespian/archive/06ff2a148c1e33095d4838579461f31141565958.tar.gz",
.hash = "122064ef6d9da7aa0352da12804ab46dbecdabb033e9d6c1b4208844f9dd3694e373",
.url = "https://github.com/neurocyte/thespian/archive/6e65fa623a45a4925875955aeb45e5cb0b5f7a68.tar.gz",
.hash = "12206feb723c41340acfe574b16f98a90e3af1f2662850ee2904ec0facb9dc5f0eef",
},
.themes = .{
.url = "https://github.com/neurocyte/flow-themes/releases/download/master-803da089c5a0fc3b4513a7c34afe9bdaff83efdc/flow-themes.tar.gz",

View file

@ -14,7 +14,11 @@ const sp_tag = "child";
const debug_lsp = true;
const lsp_request_timeout = std.time.ns_per_s * 30;
pub fn open(allocator: std.mem.Allocator, project: []const u8, cmd: tp.message) !Self {
const OutOfMemoryError = error{OutOfMemory};
const SendError = error{SendFailed};
const CallError = tp.CallError;
pub fn open(allocator: std.mem.Allocator, project: []const u8, cmd: tp.message) (error{ ThespianSpawnFailed, InvalidArgument } || cbor.Error)!Self {
return .{ .allocator = allocator, .pid = try Process.create(allocator, project, cmd) };
}
@ -28,29 +32,29 @@ pub fn term(self: *Self) void {
self.pid.deinit();
}
pub fn send_request(self: Self, allocator: std.mem.Allocator, method: []const u8, m: anytype) !tp.message {
pub fn send_request(self: Self, allocator: std.mem.Allocator, method: []const u8, m: anytype) CallError!tp.message {
var cb = std.ArrayList(u8).init(self.allocator);
defer cb.deinit();
try cbor.writeValue(cb.writer(), m);
return self.pid.call(allocator, lsp_request_timeout, .{ "REQ", method, cb.items });
}
pub fn send_response(self: Self, id: i32, m: anytype) !tp.message {
pub fn send_response(self: Self, id: i32, m: anytype) SendError!tp.message {
var cb = std.ArrayList(u8).init(self.allocator);
defer cb.deinit();
try cbor.writeValue(cb.writer(), m);
return self.pid.send(.{ "RSP", id, cb.items });
}
pub fn send_notification(self: Self, method: []const u8, m: anytype) !void {
pub fn send_notification(self: Self, method: []const u8, m: anytype) (OutOfMemoryError || SendError)!void {
var cb = std.ArrayList(u8).init(self.allocator);
defer cb.deinit();
try cbor.writeValue(cb.writer(), m);
return self.send_notification_raw(method, cb.items);
}
pub fn send_notification_raw(self: Self, method: []const u8, cb: []const u8) !void {
return self.pid.send(.{ "NTFY", method, cb });
pub fn send_notification_raw(self: Self, method: []const u8, cb: []const u8) SendError!void {
self.pid.send(.{ "NTFY", method, cb }) catch return error.SendFailed;
}
pub fn close(self: *Self) void {
@ -73,14 +77,14 @@ const Process = struct {
const Receiver = tp.Receiver(*Process);
pub fn create(allocator: std.mem.Allocator, project: []const u8, cmd: tp.message) !tp.pid {
pub fn create(allocator: std.mem.Allocator, project: []const u8, cmd: tp.message) (error{ ThespianSpawnFailed, InvalidArgument } || OutOfMemoryError || cbor.Error)!tp.pid {
var tag: []const u8 = undefined;
if (try cmd.match(.{tp.extract(&tag)})) {
if (try cbor.match(cmd.buf, .{tp.extract(&tag)})) {
//
} else if (try cmd.match(.{ tp.extract(&tag), tp.more })) {
} else if (try cbor.match(cmd.buf, .{ tp.extract(&tag), tp.more })) {
//
} else {
return tp.exit("no LSP command");
return error.InvalidArgument;
}
const self = try allocator.create(Process);
var sp_tag_ = std.ArrayList(u8).init(allocator);
@ -112,18 +116,18 @@ const Process = struct {
if (self.log_file) |file| file.close();
}
fn close(self: *Process) tp.result {
fn close(self: *Process) error{CloseFailed}!void {
if (self.sp) |*sp| {
defer self.sp = null;
try sp.close();
sp.close() catch return error.CloseFailed;
self.write_log("### closed ###\n", .{});
}
}
fn term(self: *Process) tp.result {
fn term(self: *Process) error{TerminateFailed}!void {
if (self.sp) |*sp| {
defer self.sp = null;
try sp.term();
sp.term() catch return error.TerminateFailed;
self.write_log("### terminated ###\n", .{});
}
}
@ -143,29 +147,27 @@ const Process = struct {
}
fn receive(self: *Process, from: tp.pid_ref, m: tp.message) tp.result {
return self.receive_safe(from, m) catch |e| return tp.exit_error(e, @errorReturnTrace());
return self.receive_safe(from, m) catch |e| switch (e) {
error.ExitNormal => tp.exit_normal(),
error.ExitUnexpected => error.Exit,
else => tp.exit_error(e, @errorReturnTrace()),
};
}
const ReceiveError = error{
OutOfMemory,
NoSpaceLeft,
BufferUnderrun,
CborIntegerTooLarge,
CborIntegerTooSmall,
CborInvalidType,
CborTooShort,
CborUnsupportedType,
SyntaxError,
UnexpectedEndOfInput,
const Error = (cbor.Error || cbor.JsonDecodeError || OutOfMemoryError || SendError || error{
InvalidSyntax,
InvalidMessageField,
InvalidMessage,
InvalidContentLength,
Closed,
Exit,
};
CloseFailed,
TerminateFailed,
UnsupportedType,
ExitNormal,
ExitUnexpected,
});
fn receive_safe(self: *Process, from: tp.pid_ref, m: tp.message) ReceiveError!void {
fn receive_safe(self: *Process, from: tp.pid_ref, m: tp.message) Error!void {
const frame = tracy.initZone(@src(), .{ .name = module_name });
defer frame.deinit();
errdefer self.deinit();
@ -175,34 +177,34 @@ const Process = struct {
var code: u32 = 0;
var id: i32 = 0;
if (try m.match(.{ "REQ", tp.extract(&method), tp.extract(&bytes) })) {
if (try cbor.match(m.buf, .{ "REQ", tp.extract(&method), tp.extract(&bytes) })) {
try self.send_request(from, method, bytes);
} else if (try m.match(.{ "RSP", tp.extract(&id), tp.extract(&bytes) })) {
} else if (try cbor.match(m.buf, .{ "RSP", tp.extract(&id), tp.extract(&bytes) })) {
try self.send_response(id, bytes);
} else if (try m.match(.{ "NTFY", tp.extract(&method), tp.extract(&bytes) })) {
} else if (try cbor.match(m.buf, .{ "NTFY", tp.extract(&method), tp.extract(&bytes) })) {
try self.send_notification(method, bytes);
} else if (try m.match(.{"close"})) {
} else if (try cbor.match(m.buf, .{"close"})) {
self.write_log("### LSP close ###\n", .{});
try self.close();
} else if (try m.match(.{"term"})) {
} else if (try cbor.match(m.buf, .{"term"})) {
self.write_log("### LSP terminated ###\n", .{});
try self.term();
} else if (try m.match(.{ self.sp_tag, "stdout", tp.extract(&bytes) })) {
} else if (try cbor.match(m.buf, .{ self.sp_tag, "stdout", tp.extract(&bytes) })) {
try self.handle_output(bytes);
} else if (try m.match(.{ self.sp_tag, "term", tp.extract(&err), tp.extract(&code) })) {
} else if (try cbor.match(m.buf, .{ self.sp_tag, "term", tp.extract(&err), tp.extract(&code) })) {
try self.handle_terminated(err, code);
} else if (try m.match(.{ self.sp_tag, "stderr", tp.extract(&bytes) })) {
} else if (try cbor.match(m.buf, .{ self.sp_tag, "stderr", tp.extract(&bytes) })) {
self.write_log("{s}\n", .{bytes});
} else if (try m.match(.{ "exit", "normal" })) {
} else if (try cbor.match(m.buf, .{ "exit", "normal" })) {
// self.write_log("### exit normal ###\n", .{});
} else {
const e = tp.unexpected(m);
tp.unexpected(m) catch {};
self.write_log("{s}\n", .{tp.error_text()});
return e;
return error.ExitUnexpected;
}
}
fn receive_lsp_message(self: *Process, cb: []const u8) !void {
fn receive_lsp_message(self: *Process, cb: []const u8) Error!void {
var iter = cb;
const MsgMembers = struct {
@ -252,7 +254,7 @@ const Process = struct {
}
}
fn handle_output(self: *Process, bytes: []u8) ReceiveError!void {
fn handle_output(self: *Process, bytes: []u8) Error!void {
try self.recv_buf.appendSlice(bytes);
self.write_log("### RECV:\n{s}\n###\n", .{bytes});
self.frame_message_recv() catch |e| {
@ -261,15 +263,15 @@ const Process = struct {
};
}
fn handle_terminated(self: *Process, err: []const u8, code: u32) tp.result {
fn handle_terminated(self: *Process, err: []const u8, code: u32) error{ExitNormal}!void {
const logger = log.logger("LSP");
logger.print("terminated: {s} {d}", .{ err, code });
self.write_log("### subprocess terminated {s} {d} ###\n", .{ err, code });
try self.parent.send(.{ sp_tag, self.tag, "done" });
return tp.exit_normal();
self.parent.send(.{ sp_tag, self.tag, "done" }) catch {};
return error.ExitNormal;
}
fn send_request(self: *Process, from: tp.pid_ref, method: []const u8, params_cb: []const u8) ReceiveError!void {
fn send_request(self: *Process, from: tp.pid_ref, method: []const u8, params_cb: []const u8) Error!void {
const sp = if (self.sp) |*sp| sp else return error.Closed;
const id = self.next_id;
@ -299,12 +301,12 @@ const Process = struct {
_ = try writer.write(json);
_ = try writer.write(terminator);
try sp.send(output.items);
sp.send(output.items) catch return error.SendFailed;
self.write_log("### SEND request:\n{s}\n###\n", .{output.items});
try self.requests.put(id, from.clone());
}
fn send_response(self: *Process, id: i32, result_cb: []const u8) !void {
fn send_response(self: *Process, id: i32, result_cb: []const u8) (error{Closed} || SendError || cbor.Error || cbor.JsonEncodeError)!void {
const sp = if (self.sp) |*sp| sp else return error.Closed;
var msg = std.ArrayList(u8).init(self.allocator);
@ -329,11 +331,11 @@ const Process = struct {
_ = try writer.write(json);
_ = try writer.write(terminator);
try sp.send(output.items);
sp.send(output.items) catch return error.SendFailed;
self.write_log("### SEND response:\n{s}\n###\n", .{output.items});
}
fn send_notification(self: *Process, method: []const u8, params_cb: []const u8) !void {
fn send_notification(self: *Process, method: []const u8, params_cb: []const u8) Error!void {
const sp = if (self.sp) |*sp| sp else return error.Closed;
const have_params = !(cbor.match(params_cb, cbor.null_) catch false);
@ -364,11 +366,11 @@ const Process = struct {
_ = try writer.write(json);
_ = try writer.write(terminator);
try sp.send(output.items);
sp.send(output.items) catch return error.SendFailed;
self.write_log("### SEND notification:\n{s}\n###\n", .{output.items});
}
fn frame_message_recv(self: *Process) ReceiveError!void {
fn frame_message_recv(self: *Process) Error!void {
const sep = "\r\n\r\n";
const headers_end = std.mem.indexOf(u8, self.recv_buf.items, sep) orelse return;
const headers_data = self.recv_buf.items[0..headers_end];
@ -386,7 +388,7 @@ const Process = struct {
if (rest.len > 0) return self.frame_message_recv();
}
fn receive_lsp_request(self: *Process, id: i32, method: []const u8, params: ?[]const u8) !void {
fn receive_lsp_request(self: *Process, id: i32, method: []const u8, params: ?[]const u8) Error!void {
const json = if (params) |p| try cbor.toJsonPrettyAlloc(self.allocator, p) else null;
defer if (json) |p| self.allocator.free(p);
self.write_log("### RECV req: {d}\nmethod: {s}\n{s}\n###\n", .{ id, method, json orelse "no params" });
@ -401,10 +403,10 @@ const Process = struct {
try cbor.writeValue(writer, method);
try cbor.writeValue(writer, id);
if (params) |p| _ = try writer.write(p) else try cbor.writeValue(writer, null);
try self.parent.send_raw(.{ .buf = msg.items });
self.parent.send_raw(.{ .buf = msg.items }) catch return error.SendFailed;
}
fn receive_lsp_response(self: *Process, id: i32, result: ?[]const u8, err: ?[]const u8) !void {
fn receive_lsp_response(self: *Process, id: i32, result: ?[]const u8, err: ?[]const u8) Error!void {
const json = if (result) |p| try cbor.toJsonPrettyAlloc(self.allocator, p) else null;
defer if (json) |p| self.allocator.free(p);
const json_err = if (err) |p| try cbor.toJsonPrettyAlloc(self.allocator, p) else null;
@ -424,10 +426,10 @@ const Process = struct {
try cbor.writeValue(writer, "result");
_ = try writer.write(result_);
}
try from.send_raw(.{ .buf = msg.items });
from.send_raw(.{ .buf = msg.items }) catch return error.SendFailed;
}
fn receive_lsp_notification(self: *Process, method: []const u8, params: ?[]const u8) !void {
fn receive_lsp_notification(self: *Process, method: []const u8, params: ?[]const u8) Error!void {
const json = if (params) |p| try cbor.toJsonPrettyAlloc(self.allocator, p) else null;
defer if (json) |p| self.allocator.free(p);
self.write_log("### RECV notify:\nmethod: {s}\n{s}\n###\n", .{ method, json orelse "no params" });
@ -441,7 +443,7 @@ const Process = struct {
try cbor.writeValue(writer, "notify");
try cbor.writeValue(writer, method);
if (params) |p| _ = try writer.write(p) else try cbor.writeValue(writer, null);
try self.parent.send_raw(.{ .buf = msg.items });
self.parent.send_raw(.{ .buf = msg.items }) catch return error.SendFailed;
}
fn write_log(self: *Process, comptime format: []const u8, args: anytype) void {
@ -455,7 +457,7 @@ const Headers = struct {
content_length: usize = 0,
content_type: ?[]const u8 = null,
fn parse(buf_: []const u8) Process.ReceiveError!Headers {
fn parse(buf_: []const u8) Process.Error!Headers {
var buf = buf_;
var ret: Headers = .{};
while (true) {
@ -475,7 +477,7 @@ const Headers = struct {
}
}
fn parse_one(self: *Headers, name: []const u8, value: []const u8) Process.ReceiveError!void {
fn parse_one(self: *Headers, name: []const u8, value: []const u8) Process.Error!void {
if (std.mem.eql(u8, "Content-Length", name)) {
self.content_length = std.fmt.parseInt(@TypeOf(self.content_length), value, 10) catch |e| switch (e) {
error.Overflow => return error.InvalidContentLength,

View file

@ -29,7 +29,7 @@ const File = struct {
visited: bool = false,
};
pub fn init(allocator: std.mem.Allocator, name: []const u8) error{OutOfMemory}!Self {
pub fn init(allocator: std.mem.Allocator, name: []const u8) OutOfMemoryError!Self {
return .{
.allocator = allocator,
.name = try allocator.dupe(u8, name),
@ -83,7 +83,7 @@ pub fn restore_state(self: *Self, data: []const u8) !void {
tp.extract(&row),
tp.extract(&col),
}) catch |e| switch (e) {
error.CborTooShort => return,
error.TooShort => return,
else => return e,
}) {
self.longest_file_path = @max(self.longest_file_path, path.len);
@ -96,7 +96,9 @@ pub fn restore_state(self: *Self, data: []const u8) !void {
}
}
fn get_lsp(self: *Self, language_server: []const u8) !LSP {
pub const GetLspError = (error{ ThespianSpawnFailed, Timeout, InvalidArgument } || OutOfMemoryError || SendError || cbor.Error);
fn get_lsp(self: *Self, language_server: []const u8) GetLspError!LSP {
if (self.language_servers.get(language_server)) |lsp| return lsp;
const logger = log.logger("lsp");
errdefer |e| logger.print_err("get_lsp", "failed to initialize LSP: {s} -> {any}", .{ fmt_lsp_name_func(language_server), e });
@ -113,14 +115,16 @@ fn get_lsp(self: *Self, language_server: []const u8) !LSP {
return lsp;
}
fn get_file_lsp(self: *Self, file_path: []const u8) !LSP {
pub const GetFileLspError = (GetLspError || error{NoLsp});
fn get_file_lsp(self: *Self, file_path: []const u8) GetFileLspError!LSP {
const logger = log.logger("lsp");
errdefer logger.print_err("get_file_lsp", "no LSP found for file: {s} ({s})", .{
std.fmt.fmtSliceEscapeLower(file_path),
self.name,
});
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");
const lsp = self.file_language_server.get(file_path) orelse return error.NoLsp;
if (lsp.pid.expired()) return error.NoLsp;
return lsp;
}
@ -145,20 +149,20 @@ pub fn sort_files_by_mtime(self: *Self) void {
std.mem.sort(File, self.files.items, {}, less_fn);
}
pub fn request_most_recent_file(self: *Self, from: tp.pid_ref) error{ OutOfMemory, Exit }!void {
pub fn request_most_recent_file(self: *Self, from: tp.pid_ref) SendError!void {
const file_path = if (self.files.items.len > 0) self.files.items[0].path else null;
try from.send(.{file_path});
from.send(.{file_path}) catch return error.SendFailed;
}
pub fn request_recent_files(self: *Self, from: tp.pid_ref, max: usize) error{ OutOfMemory, Exit }!void {
pub fn request_recent_files(self: *Self, from: tp.pid_ref, max: usize) SendError!void {
defer from.send(.{ "PRJ", "recent_done", self.longest_file_path, "" }) catch {};
for (self.files.items, 0..) |file, i| {
try from.send(.{ "PRJ", "recent", self.longest_file_path, file.path });
from.send(.{ "PRJ", "recent", self.longest_file_path, file.path }) catch return error.SendFailed;
if (i >= max) return;
}
}
fn simple_query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []const u8) error{ OutOfMemory, Exit }!usize {
fn simple_query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []const u8) (OutOfMemoryError || SendError)!usize {
var i: usize = 0;
defer from.send(.{ "PRJ", "recent_done", self.longest_file_path, query }) catch {};
for (self.files.items) |file| {
@ -168,7 +172,7 @@ fn simple_query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: [
defer self.allocator.free(matches);
var n: usize = 0;
while (n < query.len) : (n += 1) matches[n] = idx + n;
try from.send(.{ "PRJ", "recent", self.longest_file_path, file.path, matches });
from.send(.{ "PRJ", "recent", self.longest_file_path, file.path, matches }) catch return error.SendFailed;
i += 1;
if (i >= max) return i;
}
@ -176,7 +180,7 @@ fn simple_query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: [
return i;
}
pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []const u8) error{ OutOfMemory, Exit }!usize {
pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []const u8) (OutOfMemoryError || SendError)!usize {
if (query.len < 3)
return self.simple_query_recent_files(from, max, query);
defer from.send(.{ "PRJ", "recent_done", self.longest_file_path, query }) catch {};
@ -216,16 +220,16 @@ pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []co
std.mem.sort(Match, matches.items, {}, less_fn);
for (matches.items[0..@min(max, matches.items.len)]) |match|
try from.send(.{ "PRJ", "recent", self.longest_file_path, match.path, match.matches });
from.send(.{ "PRJ", "recent", self.longest_file_path, match.path, match.matches }) catch return error.SendFailed;
return @min(max, matches.items.len);
}
pub fn add_pending_file(self: *Self, file_path: []const u8, mtime: i128) error{OutOfMemory}!void {
pub fn add_pending_file(self: *Self, file_path: []const u8, mtime: i128) OutOfMemoryError!void {
self.longest_file_path = @max(self.longest_file_path, file_path.len);
(try self.pending.addOne()).* = .{ .path = try self.allocator.dupe(u8, file_path), .mtime = mtime };
}
pub fn merge_pending_files(self: *Self) error{OutOfMemory}!void {
pub fn merge_pending_files(self: *Self) OutOfMemoryError!void {
defer self.sort_files_by_mtime();
const existing = try self.files.toOwnedSlice();
self.files = self.pending;
@ -237,12 +241,12 @@ pub fn merge_pending_files(self: *Self) error{OutOfMemory}!void {
self.allocator.free(existing);
}
pub fn update_mru(self: *Self, file_path: []const u8, row: usize, col: usize) !void {
pub fn update_mru(self: *Self, file_path: []const u8, row: usize, col: usize) OutOfMemoryError!void {
defer self.sort_files_by_mtime();
try self.update_mru_internal(file_path, std.time.nanoTimestamp(), row, col);
}
fn update_mru_internal(self: *Self, file_path: []const u8, mtime: i128, row: usize, col: usize) !void {
fn update_mru_internal(self: *Self, file_path: []const u8, mtime: i128, row: usize, col: usize) OutOfMemoryError!void {
for (self.files.items) |*file| {
if (!std.mem.eql(u8, file.path, file_path)) continue;
file.mtime = mtime;
@ -269,16 +273,16 @@ fn update_mru_internal(self: *Self, file_path: []const u8, mtime: i128, row: usi
}
}
pub fn get_mru_position(self: *Self, from: tp.pid_ref, file_path: []const u8) !void {
pub fn get_mru_position(self: *Self, from: tp.pid_ref, file_path: []const u8) SendError!void {
for (self.files.items) |*file| {
if (!std.mem.eql(u8, file.path, file_path)) continue;
if (file.row != 0)
try from.send(.{ "cmd", "goto_line_and_column", .{ file.row + 1, file.col + 1 } });
from.send(.{ "cmd", "goto_line_and_column", .{ file.row + 1, file.col + 1 } }) catch return error.SendFailed;
return;
}
}
pub fn did_open(self: *Self, file_path: []const u8, file_type: []const u8, language_server: []const u8, version: usize, text: []const u8) !void {
pub fn did_open(self: *Self, file_path: []const u8, file_type: []const u8, language_server: []const u8, version: usize, text: []const u8) GetLspError!void {
self.update_mru(file_path, 0, 0) catch {};
const lsp = try self.get_lsp(language_server);
if (!self.file_language_server.contains(file_path)) {
@ -292,7 +296,7 @@ pub fn did_open(self: *Self, file_path: []const u8, file_type: []const u8, langu
});
}
pub fn did_change(self: *Self, file_path: []const u8, version: usize, root_dst_addr: usize, root_src_addr: usize) !void {
pub fn did_change(self: *Self, file_path: []const u8, version: usize, root_dst_addr: usize, root_src_addr: usize) GetFileLspError!void {
const lsp = try self.get_file_lsp(file_path);
const uri = try self.make_URI(file_path);
defer self.allocator.free(uri);
@ -404,23 +408,25 @@ pub fn did_close(self: *Self, file_path: []const u8) !void {
});
}
pub fn goto_definition(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, col: usize) !void {
pub fn goto_definition(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, col: usize) SendGotoRequestError!void {
return self.send_goto_request(from, file_path, row, col, "textDocument/definition");
}
pub fn goto_declaration(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, col: usize) !void {
pub fn goto_declaration(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, col: usize) SendGotoRequestError!void {
return self.send_goto_request(from, file_path, row, col, "textDocument/declaration");
}
pub fn goto_implementation(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, col: usize) !void {
pub fn goto_implementation(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, col: usize) SendGotoRequestError!void {
return self.send_goto_request(from, file_path, row, col, "textDocument/implementation");
}
pub fn goto_type_definition(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, col: usize) !void {
pub fn goto_type_definition(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, col: usize) SendGotoRequestError!void {
return self.send_goto_request(from, file_path, row, col, "textDocument/typeDefinition");
}
fn send_goto_request(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, col: usize, method: []const u8) !void {
pub const SendGotoRequestError = (SendError || InvalidMessageError || GetFileLspError || GetLineOfFileError || cbor.Error);
fn send_goto_request(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, col: usize, method: []const u8) SendGotoRequestError!void {
const lsp = try self.get_file_lsp(file_path);
const uri = try self.make_URI(file_path);
defer self.allocator.free(uri);
@ -431,20 +437,24 @@ fn send_goto_request(self: *Self, from: tp.pid_ref, file_path: []const u8, row:
defer self.allocator.free(response.buf);
var link: []const u8 = undefined;
var locations: []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)} })) {
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)} })) {
try self.navigate_to_location_link(from, link);
} else if (try response.match(.{ 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);
}
} else if (try response.match(.{ "child", tp.string, "result", tp.null_ })) {
} else if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.null_ })) {
return;
} else if (try response.match(.{ "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);
}
}
fn navigate_to_location_link(_: *Self, from: tp.pid_ref, location_link: []const u8) !void {
const OutOfMemoryError = error{OutOfMemory};
pub const SendError = error{SendFailed};
pub const InvalidMessageError = error{ InvalidMessage, InvalidMessageField, InvalidTargetURI };
fn navigate_to_location_link(_: *Self, from: tp.pid_ref, location_link: []const u8) (SendError || InvalidMessageError || cbor.Error)!void {
var iter = location_link;
var targetUri: ?[]const u8 = null;
var targetRange: ?Range = null;
@ -480,7 +490,7 @@ fn navigate_to_location_link(_: *Self, from: tp.pid_ref, location_link: []const
};
}
if (targetSelectionRange) |sel| {
try from.send(.{ "cmd", "navigate", .{
from.send(.{ "cmd", "navigate", .{
.file = file_path,
.goto = .{
targetRange.?.start.line + 1,
@ -490,19 +500,19 @@ fn navigate_to_location_link(_: *Self, from: tp.pid_ref, location_link: []const
sel.end.line,
sel.end.character,
},
} });
} }) catch return error.SendFailed;
} else {
try from.send(.{ "cmd", "navigate", .{
from.send(.{ "cmd", "navigate", .{
.file = file_path,
.goto = .{
targetRange.?.start.line + 1,
targetRange.?.start.character + 1,
},
} });
} }) catch return error.SendFailed;
}
}
pub fn references(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, col: usize) !void {
pub fn references(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, col: usize) SendGotoRequestError!void {
const lsp = try self.get_file_lsp(file_path);
const uri = try self.make_URI(file_path);
defer self.allocator.free(uri);
@ -515,14 +525,14 @@ pub fn references(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usi
});
defer self.allocator.free(response.buf);
var locations: []const u8 = undefined;
if (try response.match(.{ "child", tp.string, "result", tp.null_ })) {
if (try cbor.match(response.buf, .{ "child", tp.string, "result", tp.null_ })) {
return;
} else if (try response.match(.{ "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);
}
}
fn send_reference_list(self: *Self, to: tp.pid_ref, locations: []const u8) !void {
fn send_reference_list(self: *Self, to: tp.pid_ref, locations: []const u8) (InvalidMessageError || SendError || GetLineOfFileError || cbor.Error)!void {
defer to.send(.{ "REF", "done" }) catch {};
var iter = locations;
var len = try cbor.decodeArrayHeader(&iter);
@ -536,7 +546,7 @@ fn send_reference_list(self: *Self, to: tp.pid_ref, locations: []const u8) !void
log.logger("lsp").print("found {d} references", .{count});
}
fn send_reference(self: *Self, to: tp.pid_ref, location: []const u8) !void {
fn send_reference(self: *Self, to: tp.pid_ref, location: []const u8) (InvalidMessageError || SendError || GetLineOfFileError || cbor.Error)!void {
var iter = location;
var targetUri: ?[]const u8 = null;
var targetRange: ?Range = null;
@ -577,7 +587,7 @@ fn send_reference(self: *Self, to: tp.pid_ref, location: []const u8) !void {
file_path[self.name.len + 1 ..]
else
file_path;
try to.send(.{
to.send(.{
"REF",
file_path_,
targetRange.?.start.line + 1,
@ -585,10 +595,10 @@ fn send_reference(self: *Self, to: tp.pid_ref, location: []const u8) !void {
targetRange.?.end.line + 1,
targetRange.?.end.character,
line,
});
}) catch return error.SendFailed;
}
pub fn completion(self: *Self, _: tp.pid_ref, file_path: []const u8, row: usize, col: usize) !void {
pub fn completion(self: *Self, _: tp.pid_ref, file_path: []const u8, row: usize, col: usize) (SendError || GetFileLspError)!void {
const lsp = try self.get_file_lsp(file_path);
const uri = try self.make_URI(file_path);
defer self.allocator.free(uri);
@ -599,7 +609,7 @@ 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 {
pub fn hover(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, col: usize) (SendError || InvalidMessageError || GetFileLspError || cbor.Error)!void {
const lsp = try self.get_file_lsp(file_path);
const uri = try self.make_URI(file_path);
defer self.allocator.free(uri);
@ -611,14 +621,14 @@ pub fn hover(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, c
});
defer self.allocator.free(response.buf);
var result: []const u8 = undefined;
if (try response.match(.{ "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);
} else if (try response.match(.{ "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);
}
}
fn send_hover(self: *Self, to: tp.pid_ref, file_path: []const u8, row: usize, col: usize, result: []const u8) !void {
fn send_hover(self: *Self, to: tp.pid_ref, file_path: []const u8, row: usize, col: usize, result: []const u8) (SendError || InvalidMessageError || cbor.Error)!void {
var iter = result;
var len = cbor.decodeMapHeader(&iter) catch return;
var contents: []const u8 = "";
@ -698,19 +708,19 @@ fn send_content_msg(
kind: []const u8,
content: []const u8,
range: ?Range,
) !void {
) SendError!void {
const r = range orelse Range{
.start = .{ .line = row, .character = col },
.end = .{ .line = row, .character = col },
};
return try to.send(.{ tag, file_path, kind, content, r.start.line, r.start.character, r.end.line, r.end.character });
to.send(.{ tag, file_path, kind, content, r.start.line, r.start.character, r.end.line, r.end.character }) catch return error.SendFailed;
}
fn send_content_msg_empty(to: tp.pid_ref, tag: []const u8, file_path: []const u8, row: usize, col: usize) !void {
fn send_content_msg_empty(to: tp.pid_ref, tag: []const u8, file_path: []const u8, row: usize, col: usize) SendError!void {
return send_content_msg(to, tag, file_path, row, col, "plaintext", "", null);
}
pub fn publish_diagnostics(self: *Self, to: tp.pid_ref, params_cb: []const u8) !void {
pub fn publish_diagnostics(self: *Self, to: tp.pid_ref, params_cb: []const u8) (InvalidMessageError || SendError || cbor.Error)!void {
var uri: ?[]const u8 = null;
var diagnostics: []const u8 = &.{};
var iter = params_cb;
@ -728,7 +738,7 @@ pub fn publish_diagnostics(self: *Self, to: tp.pid_ref, params_cb: []const u8) !
}
if (uri == null) return error.InvalidMessageField;
if (!std.mem.eql(u8, uri.?[0..7], "file://")) return error.InvalidURI;
if (!std.mem.eql(u8, uri.?[0..7], "file://")) return error.InvalidTargetURI;
var file_path_buf: [std.fs.max_path_bytes]u8 = undefined;
const file_path = std.Uri.percentDecodeBackwards(&file_path_buf, uri.?[7..]);
@ -744,7 +754,7 @@ pub fn publish_diagnostics(self: *Self, to: tp.pid_ref, params_cb: []const u8) !
}
}
fn send_diagnostic(_: *Self, to: tp.pid_ref, file_path: []const u8, diagnostic: []const u8) !void {
fn send_diagnostic(_: *Self, to: tp.pid_ref, file_path: []const u8, diagnostic: []const u8) (InvalidMessageError || SendError || cbor.Error)!void {
var source: []const u8 = "unknown";
var code: []const u8 = "none";
var message: []const u8 = "empty";
@ -772,7 +782,7 @@ fn send_diagnostic(_: *Self, to: tp.pid_ref, file_path: []const u8, diagnostic:
}
}
if (range == null) return error.InvalidMessageField;
try to.send(.{ "cmd", "add_diagnostic", .{
to.send(.{ "cmd", "add_diagnostic", .{
file_path,
source,
code,
@ -782,11 +792,11 @@ fn send_diagnostic(_: *Self, to: tp.pid_ref, file_path: []const u8, diagnostic:
range.?.start.character,
range.?.end.line,
range.?.end.character,
} });
} }) catch return error.SendFailed;
}
fn send_clear_diagnostics(_: *Self, to: tp.pid_ref, file_path: []const u8) !void {
try to.send(.{ "cmd", "clear_diagnostics", .{file_path} });
fn send_clear_diagnostics(_: *Self, to: tp.pid_ref, file_path: []const u8) SendError!void {
to.send(.{ "cmd", "clear_diagnostics", .{file_path} }) catch return error.SendFailed;
}
const Range = struct { start: Position, end: Position };
@ -860,19 +870,19 @@ pub fn show_message(_: *Self, _: tp.pid_ref, params_cb: []const u8) !void {
logger.print("{s}", .{msg});
}
pub fn register_capability(self: *Self, from: tp.pid_ref, id: i32, params_cb: []const u8) !void {
pub fn register_capability(self: *Self, from: tp.pid_ref, id: i32, params_cb: []const u8) (OutOfMemoryError || SendError)!void {
_ = params_cb;
return self.send_lsp_response(from, id, null);
}
pub fn send_lsp_response(self: *Self, from: tp.pid_ref, id: i32, result: anytype) !void {
pub fn send_lsp_response(self: *Self, from: tp.pid_ref, id: i32, result: anytype) (OutOfMemoryError || SendError)!void {
var cb = std.ArrayList(u8).init(self.allocator);
defer cb.deinit();
try cbor.writeValue(cb.writer(), result);
return from.send(.{ "RSP", id, cb.items });
from.send(.{ "RSP", id, cb.items }) catch return error.SendFailed;
}
fn send_lsp_init_request(self: *Self, lsp: LSP, project_path: []const u8, project_basename: []const u8, project_uri: []const u8) !tp.message {
fn send_lsp_init_request(self: *Self, lsp: LSP, project_path: []const u8, project_basename: []const u8, project_uri: []const u8) (OutOfMemoryError || SendError)!tp.message {
return lsp.send_request(self.allocator, "initialize", .{
.processId = if (builtin.os.tag == .linux) std.os.linux.getpid() else null,
.rootPath = project_path,
@ -1198,7 +1208,9 @@ fn format_lsp_name_func(
const eol = '\n';
fn get_line_of_file(self: *Self, allocator: std.mem.Allocator, file_path: []const u8, line_: usize) ![]const u8 {
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 {
const line = line_ + 1;
const file = try std.fs.cwd().openFile(file_path, .{ .mode = .read_only });
defer file.close();

View file

@ -5,6 +5,7 @@ const log = @import("log");
const tracy = @import("tracy");
const FileType = @import("syntax").FileType;
const root = @import("root");
const builtin = @import("builtin");
const Project = @import("Project.zig");
@ -14,12 +15,25 @@ const Self = @This();
const module_name = @typeName(Self);
const request_timeout = std.time.ns_per_s * 5;
pub fn get() !Self {
pub const ProjectError = error{NoProject};
const SpawnError = (OutOfMemoryError || error{ThespianSpawnFailed});
const OutOfMemoryError = error{OutOfMemory};
const SendError = (SpawnError || error{SendFailed});
const FileSystemError = error{FileSystem};
const SetCwdError = if (builtin.os.tag == .windows) error{UnrecognizedVolume} else error{};
const CallError = tp.CallError;
pub fn get() SpawnError!Self {
const pid = tp.env.get().proc(module_name);
return if (pid.expired()) create() else .{ .pid = pid };
}
fn create() !Self {
fn send(message: anytype) SendError!void {
return (try get()).pid.send(message) catch error.SendFailed;
}
fn create() SpawnError!Self {
const pid = try Process.create();
defer pid.deinit();
tp.env.get().proc_set(module_name, pid.ref());
@ -35,142 +49,142 @@ pub fn shutdown() void {
pid.send(.{"shutdown"}) catch {};
}
pub fn open(rel_project_directory: []const u8) !void {
pub fn open(rel_project_directory: []const u8) (SpawnError || FileSystemError || SendError || std.fs.File.OpenError || SetCwdError)!void {
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
const project_directory = std.fs.cwd().realpath(rel_project_directory, &path_buf) catch "(none)";
var dir = try std.fs.openDirAbsolute(project_directory, .{});
try dir.setAsCwd();
dir.close();
tp.env.get().str_set("project", project_directory);
return (try get()).pid.send(.{ "open", project_directory });
return send(.{ "open", project_directory });
}
pub fn request_most_recent_file(allocator: std.mem.Allocator) !?[]const u8 {
pub fn request_most_recent_file(allocator: std.mem.Allocator) (CallError || ProjectError || cbor.Error)!?[]const u8 {
const project = tp.env.get().str("project");
if (project.len == 0)
return tp.exit("No project");
return error.NoProject;
const rsp = try (try get()).pid.call(allocator, request_timeout, .{ "request_most_recent_file", project });
defer allocator.free(rsp.buf);
var file_path: []const u8 = undefined;
return if (try rsp.match(.{tp.extract(&file_path)})) try allocator.dupe(u8, file_path) else null;
return if (try cbor.match(rsp.buf, .{tp.extract(&file_path)})) try allocator.dupe(u8, file_path) else null;
}
pub fn request_recent_files(max: usize) !void {
pub fn request_recent_files(max: usize) (ProjectError || SendError)!void {
const project = tp.env.get().str("project");
if (project.len == 0)
return tp.exit("No project");
return (try get()).pid.send(.{ "request_recent_files", project, max });
return error.NoProject;
return send(.{ "request_recent_files", project, max });
}
pub fn request_recent_projects(allocator: std.mem.Allocator) !tp.message {
pub fn request_recent_projects(allocator: std.mem.Allocator) (ProjectError || CallError)!tp.message {
const project = tp.env.get().str("project");
return (try get()).pid.call(allocator, request_timeout, .{ "request_recent_projects", project });
}
pub fn query_recent_files(max: usize, query: []const u8) !void {
pub fn query_recent_files(max: usize, query: []const u8) (ProjectError || SendError)!void {
const project = tp.env.get().str("project");
if (project.len == 0)
return tp.exit("No project");
return (try get()).pid.send(.{ "query_recent_files", project, max, query });
return error.NoProject;
return send(.{ "query_recent_files", project, max, query });
}
pub fn request_path_files(max: usize, path: []const u8) !void {
pub fn request_path_files(max: usize, path: []const u8) (ProjectError || SendError)!void {
const project = tp.env.get().str("project");
if (project.len == 0)
return tp.exit("No project");
return (try get()).pid.send(.{ "request_path_files", project, max, path });
return error.NoProject;
return send(.{ "request_path_files", project, max, path });
}
pub fn did_open(file_path: []const u8, file_type: *const FileType, version: usize, text: []const u8) !void {
pub fn did_open(file_path: []const u8, file_type: *const FileType, version: usize, text: []const u8) (ProjectError || SendError)!void {
const project = tp.env.get().str("project");
if (project.len == 0)
return tp.exit("No project");
return error.NoProject;
const text_ptr: usize = if (text.len > 0) @intFromPtr(text.ptr) else 0;
return (try get()).pid.send(.{ "did_open", project, file_path, file_type.name, file_type.language_server, version, text_ptr, text.len });
return send(.{ "did_open", project, file_path, file_type.name, file_type.language_server, version, text_ptr, text.len });
}
pub fn did_change(file_path: []const u8, version: usize, root_dst: usize, root_src: usize) !void {
pub fn did_change(file_path: []const u8, version: usize, root_dst: usize, root_src: usize) (ProjectError || SendError)!void {
const project = tp.env.get().str("project");
if (project.len == 0)
return tp.exit("No project");
return (try get()).pid.send(.{ "did_change", project, file_path, version, root_dst, root_src });
return error.NoProject;
return send(.{ "did_change", project, file_path, version, root_dst, root_src });
}
pub fn did_save(file_path: []const u8) !void {
pub fn did_save(file_path: []const u8) (ProjectError || SendError)!void {
const project = tp.env.get().str("project");
if (project.len == 0)
return tp.exit("No project");
return (try get()).pid.send(.{ "did_save", project, file_path });
return error.NoProject;
return send(.{ "did_save", project, file_path });
}
pub fn did_close(file_path: []const u8) !void {
pub fn did_close(file_path: []const u8) (ProjectError || SendError)!void {
const project = tp.env.get().str("project");
if (project.len == 0)
return tp.exit("No project");
return (try get()).pid.send(.{ "did_close", project, file_path });
return error.NoProject;
return send(.{ "did_close", project, file_path });
}
pub fn goto_definition(file_path: []const u8, row: usize, col: usize) !void {
pub fn goto_definition(file_path: []const u8, row: usize, col: usize) (ProjectError || SendError)!void {
const project = tp.env.get().str("project");
if (project.len == 0)
return tp.exit("No project");
return (try get()).pid.send(.{ "goto_definition", project, file_path, row, col });
return error.NoProject;
return send(.{ "goto_definition", project, file_path, row, col });
}
pub fn goto_declaration(file_path: []const u8, row: usize, col: usize) !void {
pub fn goto_declaration(file_path: []const u8, row: usize, col: usize) (ProjectError || SendError)!void {
const project = tp.env.get().str("project");
if (project.len == 0)
return tp.exit("No project");
return (try get()).pid.send(.{ "goto_declaration", project, file_path, row, col });
return error.NoProject;
return send(.{ "goto_declaration", project, file_path, row, col });
}
pub fn goto_implementation(file_path: []const u8, row: usize, col: usize) !void {
pub fn goto_implementation(file_path: []const u8, row: usize, col: usize) (ProjectError || SendError)!void {
const project = tp.env.get().str("project");
if (project.len == 0)
return tp.exit("No project");
return (try get()).pid.send(.{ "goto_implementation", project, file_path, row, col });
return error.NoProject;
return send(.{ "goto_implementation", project, file_path, row, col });
}
pub fn goto_type_definition(file_path: []const u8, row: usize, col: usize) !void {
pub fn goto_type_definition(file_path: []const u8, row: usize, col: usize) (ProjectError || SendError)!void {
const project = tp.env.get().str("project");
if (project.len == 0)
return tp.exit("No project");
return (try get()).pid.send(.{ "goto_type_definition", project, file_path, row, col });
return error.NoProject;
return send(.{ "goto_type_definition", project, file_path, row, col });
}
pub fn references(file_path: []const u8, row: usize, col: usize) !void {
pub fn references(file_path: []const u8, row: usize, col: usize) (ProjectError || SendError)!void {
const project = tp.env.get().str("project");
if (project.len == 0)
return tp.exit("No project");
return (try get()).pid.send(.{ "references", project, file_path, row, col });
return error.NoProject;
return send(.{ "references", project, file_path, row, col });
}
pub fn completion(file_path: []const u8, row: usize, col: usize) !void {
pub fn completion(file_path: []const u8, row: usize, col: usize) (ProjectError || SendError)!void {
const project = tp.env.get().str("project");
if (project.len == 0)
return tp.exit("No project");
return (try get()).pid.send(.{ "completion", project, file_path, row, col });
return error.NoProject;
return send(.{ "completion", project, file_path, row, col });
}
pub fn hover(file_path: []const u8, row: usize, col: usize) !void {
pub fn hover(file_path: []const u8, row: usize, col: usize) (ProjectError || SendError)!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 });
return error.NoProject;
return send(.{ "hover", project, file_path, row, col });
}
pub fn update_mru(file_path: []const u8, row: usize, col: usize) !void {
pub fn update_mru(file_path: []const u8, row: usize, col: usize) (ProjectError || SendError)!void {
const project = tp.env.get().str("project");
if (project.len == 0)
return tp.exit("No project");
return (try get()).pid.send(.{ "update_mru", project, file_path, row, col });
return error.NoProject;
return send(.{ "update_mru", project, file_path, row, col });
}
pub fn get_mru_position(file_path: []const u8) !void {
pub fn get_mru_position(file_path: []const u8) (ProjectError || SendError)!void {
const project = tp.env.get().str("project");
if (project.len == 0)
return tp.exit("No project");
return (try get()).pid.send(.{ "get_mru_position", project, file_path });
return error.NoProject;
return send(.{ "get_mru_position", project, file_path });
}
const Process = struct {
@ -181,6 +195,9 @@ const Process = struct {
projects: ProjectsMap,
walker: ?tp.pid = null,
const InvalidArgumentError = error{InvalidArgument};
const UnsupportedError = error{Unsupported};
const Receiver = tp.Receiver(*Process);
const ProjectsMap = std.StringHashMap(*Project);
const RecentProject = struct {
@ -188,7 +205,7 @@ const Process = struct {
last_used: i128,
};
fn create() !tp.pid {
fn create() SpawnError!tp.pid {
const allocator = std.heap.c_allocator;
const self = try allocator.create(Process);
self.* = .{
@ -221,14 +238,17 @@ const Process = struct {
fn receive(self: *Process, from: tp.pid_ref, m: tp.message) tp.result {
errdefer self.deinit();
self.receive_safe(from, m) catch |e| {
if (std.mem.eql(u8, "normal", tp.error_text()))
return e;
self.logger.err("receive", tp.exit_error(e, @errorReturnTrace()));
return self.receive_safe(from, m) catch |e| switch (e) {
error.ExitNormal => tp.exit_normal(),
else => blk: {
const err = tp.exit_error(e, @errorReturnTrace());
self.logger.err("receive", err);
break :blk err;
},
};
}
fn receive_safe(self: *Process, from: tp.pid_ref, m: tp.message) !void {
fn receive_safe(self: *Process, from: tp.pid_ref, m: tp.message) (error{ExitNormal} || SendError || cbor.Error)!void {
var project_directory: []const u8 = undefined;
var path: []const u8 = undefined;
var query: []const u8 = undefined;
@ -249,77 +269,77 @@ const Process = struct {
var root_dst: usize = 0;
var root_src: usize = 0;
if (try m.match(.{ "walk_tree_entry", tp.extract(&project_directory), tp.extract(&path), tp.extract(&high), tp.extract(&low) })) {
if (try cbor.match(m.buf, .{ "walk_tree_entry", tp.extract(&project_directory), tp.extract(&path), tp.extract(&high), tp.extract(&low) })) {
const mtime = (@as(i128, @intCast(high)) << 64) | @as(i128, @intCast(low));
if (self.projects.get(project_directory)) |project|
project.add_pending_file(
path,
mtime,
) catch |e| self.logger.err("walk_tree_entry", e);
} else if (try m.match(.{ "walk_tree_done", tp.extract(&project_directory) })) {
} else if (try cbor.match(m.buf, .{ "walk_tree_done", tp.extract(&project_directory) })) {
if (self.walker) |pid| pid.deinit();
self.walker = null;
self.loaded(project_directory) catch |e| return from.forward_error(e, @errorReturnTrace());
} else if (try m.match(.{ "update_mru", tp.extract(&project_directory), tp.extract(&path), tp.extract(&row), tp.extract(&col) })) {
self.update_mru(project_directory, path, row, col) catch |e| return from.forward_error(e, @errorReturnTrace());
} else if (try m.match(.{ "child", tp.extract(&project_directory), tp.extract(&language_server), "notify", tp.extract(&method), tp.extract_cbor(&params_cb) })) {
self.loaded(project_directory) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.SendFailed;
} else if (try cbor.match(m.buf, .{ "update_mru", tp.extract(&project_directory), tp.extract(&path), tp.extract(&row), tp.extract(&col) })) {
self.update_mru(project_directory, path, row, col) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.SendFailed;
} else if (try cbor.match(m.buf, .{ "child", tp.extract(&project_directory), tp.extract(&language_server), "notify", tp.extract(&method), tp.extract_cbor(&params_cb) })) {
self.dispatch_notify(project_directory, language_server, method, params_cb) catch |e| return self.logger.err("lsp-handling", e);
} else if (try m.match(.{ "child", tp.extract(&project_directory), tp.extract(&language_server), "request", tp.extract(&method), tp.extract(&id), tp.extract_cbor(&params_cb) })) {
} else if (try cbor.match(m.buf, .{ "child", tp.extract(&project_directory), tp.extract(&language_server), "request", tp.extract(&method), tp.extract(&id), tp.extract_cbor(&params_cb) })) {
self.dispatch_request(from, project_directory, language_server, method, id, params_cb) catch |e| return self.logger.err("lsp-handling", e);
} else if (try m.match(.{ "child", tp.extract(&path), "done" })) {
} else if (try cbor.match(m.buf, .{ "child", tp.extract(&path), "done" })) {
self.logger.print_err("lsp-handling", "child '{s}' terminated", .{path});
} else if (try m.match(.{ "open", tp.extract(&project_directory) })) {
self.open(project_directory) catch |e| return from.forward_error(e, @errorReturnTrace());
} else if (try m.match(.{ "request_most_recent_file", tp.extract(&project_directory) })) {
self.request_most_recent_file(from, project_directory) catch |e| return from.forward_error(e, @errorReturnTrace());
} else if (try m.match(.{ "request_recent_files", tp.extract(&project_directory), tp.extract(&max) })) {
self.request_recent_files(from, project_directory, max) catch |e| return from.forward_error(e, @errorReturnTrace());
} else if (try m.match(.{ "request_recent_projects", tp.extract(&project_directory) })) {
self.request_recent_projects(from, project_directory) catch |e| return from.forward_error(e, @errorReturnTrace());
} else if (try m.match(.{ "query_recent_files", tp.extract(&project_directory), tp.extract(&max), tp.extract(&query) })) {
self.query_recent_files(from, project_directory, max, query) catch |e| return from.forward_error(e, @errorReturnTrace());
} else if (try m.match(.{ "request_path_files", tp.extract(&project_directory), tp.extract(&max), tp.extract(&path) })) {
self.request_path_files(from, project_directory, max, path) catch |e| return from.forward_error(e, @errorReturnTrace());
} else if (try m.match(.{ "did_open", tp.extract(&project_directory), tp.extract(&path), tp.extract(&file_type), tp.extract_cbor(&language_server), tp.extract(&version), tp.extract(&text_ptr), tp.extract(&text_len) })) {
} else if (try cbor.match(m.buf, .{ "open", tp.extract(&project_directory) })) {
self.open(project_directory) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.SendFailed;
} else if (try cbor.match(m.buf, .{ "request_most_recent_file", tp.extract(&project_directory) })) {
self.request_most_recent_file(from, project_directory) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.SendFailed;
} else if (try cbor.match(m.buf, .{ "request_recent_files", tp.extract(&project_directory), tp.extract(&max) })) {
self.request_recent_files(from, project_directory, max) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.SendFailed;
} else if (try cbor.match(m.buf, .{ "request_recent_projects", tp.extract(&project_directory) })) {
self.request_recent_projects(from, project_directory) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.SendFailed;
} else if (try cbor.match(m.buf, .{ "query_recent_files", tp.extract(&project_directory), tp.extract(&max), tp.extract(&query) })) {
self.query_recent_files(from, project_directory, max, query) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.SendFailed;
} else if (try cbor.match(m.buf, .{ "request_path_files", tp.extract(&project_directory), tp.extract(&max), tp.extract(&path) })) {
self.request_path_files(from, project_directory, max, path) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.SendFailed;
} else if (try cbor.match(m.buf, .{ "did_open", tp.extract(&project_directory), tp.extract(&path), tp.extract(&file_type), tp.extract_cbor(&language_server), tp.extract(&version), tp.extract(&text_ptr), tp.extract(&text_len) })) {
const text = if (text_len > 0) @as([*]const u8, @ptrFromInt(text_ptr))[0..text_len] else "";
self.did_open(project_directory, path, file_type, language_server, version, text) catch |e| return from.forward_error(e, @errorReturnTrace());
} else if (try m.match(.{ "did_change", tp.extract(&project_directory), tp.extract(&path), tp.extract(&version), tp.extract(&root_dst), tp.extract(&root_src) })) {
self.did_change(project_directory, path, version, root_dst, root_src) catch |e| return from.forward_error(e, @errorReturnTrace());
} else if (try m.match(.{ "did_save", tp.extract(&project_directory), tp.extract(&path) })) {
self.did_save(project_directory, path) catch |e| return from.forward_error(e, @errorReturnTrace());
} else if (try m.match(.{ "did_close", tp.extract(&project_directory), tp.extract(&path) })) {
self.did_close(project_directory, path) catch |e| return from.forward_error(e, @errorReturnTrace());
} else if (try m.match(.{ "goto_definition", tp.extract(&project_directory), tp.extract(&path), tp.extract(&row), tp.extract(&col) })) {
self.goto_definition(from, project_directory, path, row, col) catch |e| return from.forward_error(e, @errorReturnTrace());
} else if (try m.match(.{ "goto_declaration", tp.extract(&project_directory), tp.extract(&path), tp.extract(&row), tp.extract(&col) })) {
self.goto_declaration(from, project_directory, path, row, col) catch |e| return from.forward_error(e, @errorReturnTrace());
} else if (try m.match(.{ "goto_implementation", tp.extract(&project_directory), tp.extract(&path), tp.extract(&row), tp.extract(&col) })) {
self.goto_implementation(from, project_directory, path, row, col) catch |e| return from.forward_error(e, @errorReturnTrace());
} else if (try m.match(.{ "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());
} else if (try m.match(.{ "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());
} 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"})) {
self.did_open(project_directory, path, file_type, language_server, version, text) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.SendFailed;
} else if (try cbor.match(m.buf, .{ "did_change", tp.extract(&project_directory), tp.extract(&path), tp.extract(&version), tp.extract(&root_dst), tp.extract(&root_src) })) {
self.did_change(project_directory, path, version, root_dst, root_src) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.SendFailed;
} else if (try cbor.match(m.buf, .{ "did_save", tp.extract(&project_directory), tp.extract(&path) })) {
self.did_save(project_directory, path) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.SendFailed;
} else if (try cbor.match(m.buf, .{ "did_close", tp.extract(&project_directory), tp.extract(&path) })) {
self.did_close(project_directory, path) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.SendFailed;
} else if (try cbor.match(m.buf, .{ "goto_definition", tp.extract(&project_directory), tp.extract(&path), tp.extract(&row), tp.extract(&col) })) {
self.goto_definition(from, project_directory, path, row, col) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.SendFailed;
} else if (try cbor.match(m.buf, .{ "goto_declaration", tp.extract(&project_directory), tp.extract(&path), tp.extract(&row), tp.extract(&col) })) {
self.goto_declaration(from, project_directory, path, row, col) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.SendFailed;
} else if (try cbor.match(m.buf, .{ "goto_implementation", tp.extract(&project_directory), tp.extract(&path), tp.extract(&row), tp.extract(&col) })) {
self.goto_implementation(from, project_directory, path, row, col) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.SendFailed;
} 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.SendFailed;
} 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.SendFailed;
} else if (try cbor.match(m.buf, .{ "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()) catch error.SendFailed;
} else if (try cbor.match(m.buf, .{ "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()) catch error.SendFailed;
} else if (try cbor.match(m.buf, .{ "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()) catch error.SendFailed;
} else if (try cbor.match(m.buf, .{"shutdown"})) {
if (self.walker) |pid| pid.send(.{"stop"}) catch {};
self.persist_projects();
try from.send(.{ "project_manager", "shutdown" });
return tp.exit_normal();
} else if (try m.match(.{ "exit", "normal" })) {
from.send(.{ "project_manager", "shutdown" }) catch return error.SendFailed;
return error.ExitNormal;
} else if (try cbor.match(m.buf, .{ "exit", "normal" })) {
return;
} else if (try m.match(.{ "exit", "DEADSEND", tp.more })) {
} else if (try cbor.match(m.buf, .{ "exit", "DEADSEND", tp.more })) {
return;
} else {
self.logger.err("receive", tp.unexpected(m));
}
}
fn open(self: *Process, project_directory: []const u8) !void {
fn open(self: *Process, project_directory: []const u8) (SpawnError || std.fs.Dir.OpenError)!void {
if (self.projects.get(project_directory) == null) {
self.logger.print("opening: {s}", .{project_directory});
const project = try self.allocator.create(Project);
@ -333,7 +353,7 @@ const Process = struct {
}
}
fn loaded(self: *Process, project_directory: []const u8) error{ OutOfMemory, Exit }!void {
fn loaded(self: *Process, project_directory: []const u8) OutOfMemoryError!void {
const project = self.projects.get(project_directory) orelse return;
try project.merge_pending_files();
self.logger.print("opened: {s} with {d} files in {d} ms", .{
@ -343,19 +363,19 @@ const Process = struct {
});
}
fn request_most_recent_file(self: *Process, from: tp.pid_ref, project_directory: []const u8) error{ OutOfMemory, Exit }!void {
const project = self.projects.get(project_directory) orelse return tp.exit("No project");
fn request_most_recent_file(self: *Process, from: tp.pid_ref, project_directory: []const u8) (ProjectError || SendError)!void {
const project = self.projects.get(project_directory) orelse return error.NoProject;
project.sort_files_by_mtime();
return project.request_most_recent_file(from);
}
fn request_recent_files(self: *Process, from: tp.pid_ref, project_directory: []const u8, max: usize) error{ OutOfMemory, Exit }!void {
const project = self.projects.get(project_directory) orelse return tp.exit("No project");
fn request_recent_files(self: *Process, from: tp.pid_ref, project_directory: []const u8, max: usize) (ProjectError || SendError)!void {
const project = self.projects.get(project_directory) orelse return error.NoProject;
project.sort_files_by_mtime();
return project.request_recent_files(from, max);
}
fn request_recent_projects(self: *Process, from: tp.pid_ref, project_directory: []const u8) error{ OutOfMemory, Exit }!void {
fn request_recent_projects(self: *Process, from: tp.pid_ref, project_directory: []const u8) (ProjectError || SendError)!void {
var recent_projects = std.ArrayList(RecentProject).init(self.allocator);
defer recent_projects.deinit();
self.load_recent_projects(&recent_projects, project_directory) catch {};
@ -365,11 +385,11 @@ const Process = struct {
try cbor.writeArrayHeader(writer, recent_projects.items.len);
for (recent_projects.items) |project|
try cbor.writeValue(writer, project.name);
try from.send_raw(.{ .buf = message.items });
from.send_raw(.{ .buf = message.items }) catch return error.SendFailed;
}
fn query_recent_files(self: *Process, from: tp.pid_ref, project_directory: []const u8, max: usize, query: []const u8) error{ OutOfMemory, Exit }!void {
const project = self.projects.get(project_directory) orelse return tp.exit("No project");
fn query_recent_files(self: *Process, from: tp.pid_ref, project_directory: []const u8, max: usize, query: []const u8) (ProjectError || SendError)!void {
const project = self.projects.get(project_directory) orelse return error.NoProject;
const start_time = std.time.milliTimestamp();
const matched = try project.query_recent_files(from, max, query);
const query_time = std.time.milliTimestamp() - start_time;
@ -377,103 +397,103 @@ const Process = struct {
self.logger.print("query \"{s}\" matched {d}/{d} in {d} ms", .{ query, matched, project.files.items.len, query_time });
}
fn request_path_files(self: *Process, from: tp.pid_ref, project_directory: []const u8, max: usize, path: []const u8) error{ OutOfMemory, Exit }!void {
const project = self.projects.get(project_directory) orelse return tp.exit("No project");
request_path_files_async(self.allocator, from, project, max, path) catch |e| return tp.exit_error(e, @errorReturnTrace());
fn request_path_files(self: *Process, from: tp.pid_ref, project_directory: []const u8, max: usize, path: []const u8) (ProjectError || SpawnError || std.fs.Dir.OpenError)!void {
const project = self.projects.get(project_directory) orelse return error.NoProject;
try request_path_files_async(self.allocator, from, project, max, path);
}
fn did_open(self: *Process, project_directory: []const u8, file_path: []const u8, file_type: []const u8, language_server: []const u8, version: usize, text: []const u8) !void {
fn did_open(self: *Process, project_directory: []const u8, file_path: []const u8, file_type: []const u8, language_server: []const u8, version: usize, text: []const u8) (ProjectError || InvalidArgumentError || SendError || CallError || cbor.Error)!void {
const frame = tracy.initZone(@src(), .{ .name = module_name ++ ".did_open" });
defer frame.deinit();
const project = self.projects.get(project_directory) orelse return tp.exit("No project");
const project = self.projects.get(project_directory) orelse return error.NoProject;
return project.did_open(file_path, file_type, language_server, version, text);
}
fn did_change(self: *Process, project_directory: []const u8, file_path: []const u8, version: usize, root_dst: usize, root_src: usize) !void {
fn did_change(self: *Process, project_directory: []const u8, file_path: []const u8, version: usize, root_dst: usize, root_src: usize) (ProjectError || Project.GetFileLspError)!void {
const frame = tracy.initZone(@src(), .{ .name = module_name ++ ".did_change" });
defer frame.deinit();
const project = self.projects.get(project_directory) orelse return tp.exit("No project");
const project = self.projects.get(project_directory) orelse return error.NoProject;
return project.did_change(file_path, version, root_dst, root_src);
}
fn did_save(self: *Process, project_directory: []const u8, file_path: []const u8) !void {
fn did_save(self: *Process, project_directory: []const u8, file_path: []const u8) (ProjectError || Project.GetFileLspError)!void {
const frame = tracy.initZone(@src(), .{ .name = module_name ++ ".did_save" });
defer frame.deinit();
const project = self.projects.get(project_directory) orelse return tp.exit("No project");
const project = self.projects.get(project_directory) orelse return error.NoProject;
return project.did_save(file_path);
}
fn did_close(self: *Process, project_directory: []const u8, file_path: []const u8) !void {
fn did_close(self: *Process, project_directory: []const u8, file_path: []const u8) (ProjectError || Project.GetFileLspError)!void {
const frame = tracy.initZone(@src(), .{ .name = module_name ++ ".did_close" });
defer frame.deinit();
const project = self.projects.get(project_directory) orelse return tp.exit("No project");
const project = self.projects.get(project_directory) orelse return error.NoProject;
return project.did_close(file_path);
}
fn goto_definition(self: *Process, from: tp.pid_ref, project_directory: []const u8, file_path: []const u8, row: usize, col: usize) !void {
fn goto_definition(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" });
defer frame.deinit();
const project = self.projects.get(project_directory) orelse return tp.exit("No project");
const project = self.projects.get(project_directory) orelse return error.NoProject;
return project.goto_definition(from, file_path, row, col);
}
fn goto_declaration(self: *Process, from: tp.pid_ref, project_directory: []const u8, file_path: []const u8, row: usize, col: usize) !void {
fn goto_declaration(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_declaration" });
defer frame.deinit();
const project = self.projects.get(project_directory) orelse return tp.exit("No project");
const project = self.projects.get(project_directory) orelse return error.NoProject;
return project.goto_declaration(from, file_path, row, col);
}
fn goto_implementation(self: *Process, from: tp.pid_ref, project_directory: []const u8, file_path: []const u8, row: usize, col: usize) !void {
fn goto_implementation(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_implementation" });
defer frame.deinit();
const project = self.projects.get(project_directory) orelse return tp.exit("No project");
const project = self.projects.get(project_directory) orelse return error.NoProject;
return project.goto_implementation(from, file_path, row, col);
}
fn goto_type_definition(self: *Process, from: tp.pid_ref, project_directory: []const u8, file_path: []const u8, row: usize, col: usize) !void {
fn goto_type_definition(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_type_definition" });
defer frame.deinit();
const project = self.projects.get(project_directory) orelse return tp.exit("No project");
const project = self.projects.get(project_directory) orelse return error.NoProject;
return project.goto_type_definition(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) !void {
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();
const project = self.projects.get(project_directory) orelse return tp.exit("No project");
const project = self.projects.get(project_directory) orelse return error.NoProject;
return project.references(from, file_path, row, col);
}
fn completion(self: *Process, from: tp.pid_ref, project_directory: []const u8, file_path: []const u8, row: usize, col: usize) !void {
fn completion(self: *Process, from: tp.pid_ref, project_directory: []const u8, file_path: []const u8, row: usize, col: usize) (ProjectError || Project.SendError || Project.GetFileLspError)!void {
const frame = tracy.initZone(@src(), .{ .name = module_name ++ ".completion" });
defer frame.deinit();
const project = self.projects.get(project_directory) orelse return tp.exit("No project");
const project = self.projects.get(project_directory) orelse return error.NoProject;
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 {
fn hover(self: *Process, from: tp.pid_ref, project_directory: []const u8, file_path: []const u8, row: usize, col: usize) (ProjectError || Project.SendError || Project.InvalidMessageError || Project.GetFileLspError || cbor.Error)!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");
const project = self.projects.get(project_directory) orelse return error.NoProject;
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 {
fn get_mru_position(self: *Process, from: tp.pid_ref, project_directory: []const u8, file_path: []const u8) (ProjectError || Project.SendError)!void {
const frame = tracy.initZone(@src(), .{ .name = module_name ++ ".get_mru_position" });
defer frame.deinit();
const project = self.projects.get(project_directory) orelse return tp.exit("No project");
const project = self.projects.get(project_directory) orelse return error.NoProject;
return project.get_mru_position(from, file_path);
}
fn update_mru(self: *Process, project_directory: []const u8, file_path: []const u8, row: usize, col: usize) !void {
const project = self.projects.get(project_directory) orelse return tp.exit("No project");
fn update_mru(self: *Process, project_directory: []const u8, file_path: []const u8, row: usize, col: usize) (ProjectError || OutOfMemoryError)!void {
const project = self.projects.get(project_directory) orelse return error.NoProject;
return project.update_mru(file_path, row, col);
}
fn dispatch_notify(self: *Process, project_directory: []const u8, language_server: []const u8, method: []const u8, params_cb: []const u8) !void {
fn dispatch_notify(self: *Process, project_directory: []const u8, language_server: []const u8, method: []const u8, params_cb: []const u8) (ProjectError || Project.InvalidMessageError || Project.SendError || cbor.Error || cbor.JsonEncodeError)!void {
_ = language_server;
const project = self.projects.get(project_directory) orelse return tp.exit("No project");
const project = self.projects.get(project_directory) orelse return error.NoProject;
return if (std.mem.eql(u8, method, "textDocument/publishDiagnostics"))
project.publish_diagnostics(self.parent.ref(), params_cb)
else if (std.mem.eql(u8, method, "window/showMessage"))
@ -487,15 +507,16 @@ const Process = struct {
};
}
fn dispatch_request(self: *Process, from: tp.pid_ref, project_directory: []const u8, language_server: []const u8, method: []const u8, id: i32, params_cb: []const u8) !void {
fn dispatch_request(self: *Process, from: tp.pid_ref, project_directory: []const u8, language_server: []const u8, method: []const u8, id: i32, params_cb: []const u8) (ProjectError || SendError || cbor.Error || cbor.JsonEncodeError || UnsupportedError)!void {
_ = language_server;
const project = if (self.projects.get(project_directory)) |p| p else return tp.exit("No project");
const project = if (self.projects.get(project_directory)) |p| p else return error.NoProject;
return if (std.mem.eql(u8, method, "client/registerCapability"))
project.register_capability(from, id, params_cb)
else blk: {
const params = try cbor.toJsonAlloc(self.allocator, params_cb);
defer self.allocator.free(params);
break :blk tp.exit_fmt("unsupported LSP request: {s} -> {s}", .{ method, params });
self.logger.print_err("lsp", "unsupported LSP request: {s} -> {s}", .{ method, params });
break :blk error.Unsupported;
};
}
@ -609,7 +630,7 @@ const Process = struct {
}
};
fn request_path_files_async(a_: std.mem.Allocator, parent_: tp.pid_ref, project_: *Project, max_: usize, path_: []const u8) !void {
fn request_path_files_async(a_: std.mem.Allocator, parent_: tp.pid_ref, project_: *Project, max_: usize, path_: []const u8) (SpawnError || std.fs.Dir.OpenError)!void {
return struct {
allocator: std.mem.Allocator,
project_name: []const u8,
@ -621,7 +642,7 @@ fn request_path_files_async(a_: std.mem.Allocator, parent_: tp.pid_ref, project_
const path_files = @This();
const Receiver = tp.Receiver(*path_files);
fn spawn_link(allocator: std.mem.Allocator, parent: tp.pid_ref, project: *Project, max: usize, path: []const u8) !void {
fn spawn_link(allocator: std.mem.Allocator, parent: tp.pid_ref, project: *Project, max: usize, path: []const u8) (SpawnError || std.fs.Dir.OpenError)!void {
const self = try allocator.create(path_files);
self.* = .{
.allocator = allocator,
@ -673,7 +694,7 @@ fn request_path_files_async(a_: std.mem.Allocator, parent_: tp.pid_ref, project_
}.spawn_link(a_, parent_, project_, max_, path_);
}
fn walk_tree_async(a_: std.mem.Allocator, root_path_: []const u8) !tp.pid {
fn walk_tree_async(a_: std.mem.Allocator, root_path_: []const u8) (SpawnError || std.fs.Dir.OpenError)!tp.pid {
return struct {
allocator: std.mem.Allocator,
root_path: []const u8,
@ -685,7 +706,7 @@ fn walk_tree_async(a_: std.mem.Allocator, root_path_: []const u8) !tp.pid {
const tree_walker = @This();
const Receiver = tp.Receiver(*tree_walker);
fn spawn_link(allocator: std.mem.Allocator, root_path: []const u8) !tp.pid {
fn spawn_link(allocator: std.mem.Allocator, root_path: []const u8) (SpawnError || std.fs.Dir.OpenError)!tp.pid {
const self = try allocator.create(tree_walker);
self.* = .{
.allocator = allocator,
@ -775,7 +796,7 @@ const FilteredWalker = struct {
dirname_len: usize,
};
pub fn next(self: *FilteredWalker) error{OutOfMemory}!?Path {
pub fn next(self: *FilteredWalker) OutOfMemoryError!?Path {
while (self.stack.items.len != 0) {
var top = &self.stack.items[self.stack.items.len - 1];
var containing = top;

View file

@ -1,5 +1,6 @@
const std = @import("std");
const tp = @import("thespian");
const cbor = @import("cbor");
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
const Self = @This();
@ -8,9 +9,16 @@ const MessageFilter = Self;
ptr: *anyopaque,
vtable: *const VTable,
pub const Error = (cbor.Error || cbor.JsonEncodeError || error{
OutOfMemory,
ThespianSpawnFailed,
NoProject,
SendFailed,
});
pub const VTable = struct {
deinit: *const fn (ctx: *anyopaque) void,
filter: *const fn (ctx: *anyopaque, from: tp.pid_ref, m: tp.message) error{Exit}!bool,
filter: *const fn (ctx: *anyopaque, from: tp.pid_ref, m: tp.message) Error!bool,
type_name: []const u8,
};
@ -27,7 +35,7 @@ pub fn to_owned(pimpl: anytype) Self {
}
}.deinit,
.filter = struct {
pub fn filter(ctx: *anyopaque, from_: tp.pid_ref, m: tp.message) error{Exit}!bool {
pub fn filter(ctx: *anyopaque, from_: tp.pid_ref, m: tp.message) Error!bool {
return child.filter(@as(*child, @ptrCast(@alignCast(ctx))), from_, m);
}
}.filter,
@ -46,7 +54,7 @@ pub fn to_unowned(pimpl: anytype) Self {
pub fn deinit(_: *anyopaque) void {}
}.deinit,
.filter = struct {
pub fn filter(ctx: *anyopaque, from_: tp.pid_ref, m: tp.message) error{Exit}!bool {
pub fn filter(ctx: *anyopaque, from_: tp.pid_ref, m: tp.message) Error!bool {
return child.filter(@as(*child, @ptrCast(@alignCast(ctx))), from_, m);
}
}.filter,
@ -54,7 +62,7 @@ pub fn to_unowned(pimpl: anytype) Self {
};
}
pub fn bind(pimpl: anytype, comptime f: *const fn (ctx: @TypeOf(pimpl), from: tp.pid_ref, m: tp.message) error{Exit}!bool) Self {
pub fn bind(pimpl: anytype, comptime f: *const fn (ctx: @TypeOf(pimpl), from: tp.pid_ref, m: tp.message) Error!bool) Self {
const impl = @typeInfo(@TypeOf(pimpl));
const child: type = impl.Pointer.child;
return .{
@ -65,7 +73,7 @@ pub fn bind(pimpl: anytype, comptime f: *const fn (ctx: @TypeOf(pimpl), from: tp
pub fn deinit(_: *anyopaque) void {}
}.deinit,
.filter = struct {
pub fn filter(ctx: *anyopaque, from_: tp.pid_ref, m: tp.message) error{Exit}!bool {
pub fn filter(ctx: *anyopaque, from_: tp.pid_ref, m: tp.message) Error!bool {
return @call(.auto, f, .{ @as(*child, @ptrCast(@alignCast(ctx))), from_, m });
}
}.filter,
@ -84,7 +92,7 @@ pub fn dynamic_cast(self: Self, comptime T: type) ?*T {
null;
}
pub fn filter(self: Self, from_: tp.pid_ref, m: tp.message) error{Exit}!bool {
pub fn filter(self: Self, from_: tp.pid_ref, m: tp.message) Error!bool {
return self.vtable.filter(self.ptr, from_, m);
}
@ -120,14 +128,14 @@ pub const List = struct {
self.list.orderedRemove(i).deinit();
}
pub fn filter(self: *const List, from: tp.pid_ref, m: tp.message) error{Exit}!bool {
pub fn filter(self: *const List, from: tp.pid_ref, m: tp.message) Error!bool {
var sfa = std.heap.stackFallback(4096, self.allocator);
const a = sfa.get();
const buf = a.alloc(u8, m.buf.len) catch |e| return tp.exit_error(e, @errorReturnTrace());
const buf = try a.alloc(u8, m.buf.len);
defer a.free(buf);
@memcpy(buf[0..m.buf.len], m.buf);
const m_: tp.message = .{ .buf = buf[0..m.buf.len] };
var e: ?error{Exit} = null;
var e: ?Error = null;
for (self.list.items) |*i| {
const consume = i.filter(from, m_) catch |e_| ret: {
e = e_;

View file

@ -333,7 +333,7 @@ fn diff_result_send(from: tp.pid_ref, edits: []diff.Edit) !void {
from.send_raw(tp.message{ .buf = stream.getWritten() }) catch return;
}
pub fn process_diff(self: *Self, cb: []const u8) !void {
pub fn process_diff(self: *Self, cb: []const u8) MessageFilter.Error!void {
var iter = cb;
self.diff_symbols_clear();
var count = try cbor.decodeArrayHeader(&iter);
@ -382,10 +382,10 @@ fn process_edit(self: *Self, kind: Kind, line: usize, offset: usize, bytes: []co
};
}
pub fn filter_receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
pub fn filter_receive(self: *Self, _: tp.pid_ref, m: tp.message) MessageFilter.Error!bool {
var cb: []const u8 = undefined;
if (try m.match(.{ "DIFF", tp.extract_cbor(&cb) })) {
self.process_diff(cb) catch |e| return tp.exit_error(e, @errorReturnTrace());
if (cbor.match(m.buf, .{ "DIFF", tp.extract_cbor(&cb) }) catch false) {
try self.process_diff(cb);
return true;
}
return false;

View file

@ -5,10 +5,12 @@ const Allocator = @import("std").mem.Allocator;
const ArrayList = @import("std").ArrayList;
const tp = @import("thespian");
const cbor = @import("cbor");
const Plane = @import("renderer").Plane;
const Widget = @import("Widget.zig");
const MessageFilter = @import("MessageFilter.zig");
const escape = fmt.fmtSliceEscapeLower;
@ -82,21 +84,21 @@ fn output_tdiff(self: *Self, tdiff: i64) !void {
}
}
pub fn process_log(m: tp.message) !void {
pub fn process_log(m: tp.message) MessageFilter.Error!void {
var src: []const u8 = undefined;
var context: []const u8 = undefined;
var msg: []const u8 = undefined;
const buffer = get_buffer();
if (try m.match(.{ "log", tp.extract(&src), tp.extract(&msg) })) {
if (try cbor.match(m.buf, .{ "log", tp.extract(&src), tp.extract(&msg) })) {
try append(buffer, src, msg, .info);
} else if (try m.match(.{ "log", "error", tp.extract(&src), tp.extract(&context), "->", tp.extract(&msg) })) {
} else if (try cbor.match(m.buf, .{ "log", "error", tp.extract(&src), tp.extract(&context), "->", tp.extract(&msg) })) {
const err_stop = "error.Stop";
if (eql(u8, msg, err_stop))
return;
if (msg.len >= err_stop.len + 1 and eql(u8, msg[0 .. err_stop.len + 1], err_stop ++ "\n"))
return;
try append_error(buffer, src, context, msg);
} else if (try m.match(.{ "log", tp.extract(&src), tp.more })) {
} else if (try cbor.match(m.buf, .{ "log", tp.extract(&src), tp.more })) {
try append_json(buffer, src, m);
}
}
@ -120,16 +122,23 @@ fn append(buffer: *Buffer, src: []const u8, msg: []const u8, level: Level) !void
};
}
fn append_error(buffer: *Buffer, src: []const u8, context: []const u8, msg_: []const u8) !void {
var buf: [4096]u8 = undefined;
const msg = try fmt.bufPrint(&buf, "error in {s}: {s}", .{ context, msg_ });
try append(buffer, src, msg, .err);
fn append_error(buffer: *Buffer, src: []const u8, context: []const u8, msg_: []const u8) MessageFilter.Error!void {
const std = @import("std");
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
var sfa = std.heap.stackFallback(4096, arena.allocator());
var msg = std.ArrayList(u8).init(sfa.get());
try fmt.format(msg.writer(), "error in {s}: {s}", .{ context, msg_ });
try append(buffer, src, msg.items, .err);
}
fn append_json(buffer: *Buffer, src: []const u8, m: tp.message) !void {
var buf: [4096]u8 = undefined;
const json = try m.to_json(&buf);
try append(buffer, src, json, .err);
fn append_json(buffer: *Buffer, src: []const u8, m: tp.message) MessageFilter.Error!void {
const std = @import("std");
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
var sfa = std.heap.stackFallback(4096, arena.allocator());
const msg = try cbor.toJsonAlloc(sfa.get(), m.buf);
try append(buffer, src, msg, .err);
}
fn get_buffer() *Buffer {

View file

@ -1,5 +1,6 @@
const std = @import("std");
const tp = @import("thespian");
const cbor = @import("cbor");
const log = @import("log");
const root = @import("root");
@ -216,15 +217,15 @@ pub fn Create(options: type) type {
try self.do_complete();
}
fn receive_path_entry(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
if (try m.match(.{ "PRJ", tp.more })) {
self.process_project_manager(m) catch |e| return tp.exit_error(e, @errorReturnTrace());
fn receive_path_entry(self: *Self, _: tp.pid_ref, m: tp.message) MessageFilter.Error!bool {
if (try cbor.match(m.buf, .{ "PRJ", tp.more })) {
try self.process_project_manager(m);
return true;
}
return false;
}
fn process_project_manager(self: *Self, m: tp.message) !void {
fn process_project_manager(self: *Self, m: tp.message) MessageFilter.Error!void {
defer {
if (tui.current().mini_mode) |*mini_mode| {
mini_mode.text = self.file_path.items;
@ -232,23 +233,23 @@ pub fn Create(options: type) type {
}
}
var count: usize = undefined;
if (try m.match(.{ "PRJ", "path_entry", tp.more })) {
if (try cbor.match(m.buf, .{ "PRJ", "path_entry", tp.more })) {
return self.process_path_entry(m);
} else if (try m.match(.{ "PRJ", "path_done", tp.any, tp.any, tp.extract(&count) })) {
} else if (try cbor.match(m.buf, .{ "PRJ", "path_done", tp.any, tp.any, tp.extract(&count) })) {
try self.do_complete();
} else {
log.logger("file_browser").err("receive", tp.unexpected(m));
}
}
fn process_path_entry(self: *Self, m: tp.message) !void {
fn process_path_entry(self: *Self, m: tp.message) MessageFilter.Error!void {
var path: []const u8 = undefined;
var file_name: []const u8 = undefined;
if (try m.match(.{ tp.any, tp.any, tp.any, tp.extract(&path), "DIR", tp.extract(&file_name) })) {
if (try cbor.match(m.buf, .{ tp.any, tp.any, tp.any, tp.extract(&path), "DIR", tp.extract(&file_name) })) {
(try self.entries.addOne()).* = .{ .name = try self.allocator.dupe(u8, file_name), .type = .dir };
} else if (try m.match(.{ tp.any, tp.any, tp.any, tp.extract(&path), "LINK", tp.extract(&file_name) })) {
} else if (try cbor.match(m.buf, .{ tp.any, tp.any, tp.any, tp.extract(&path), "LINK", tp.extract(&file_name) })) {
(try self.entries.addOne()).* = .{ .name = try self.allocator.dupe(u8, file_name), .type = .link };
} else if (try m.match(.{ tp.any, tp.any, tp.any, tp.extract(&path), "FILE", tp.extract(&file_name) })) {
} else if (try cbor.match(m.buf, .{ tp.any, tp.any, tp.any, tp.extract(&path), "FILE", tp.extract(&file_name) })) {
(try self.entries.addOne()).* = .{ .name = try self.allocator.dupe(u8, file_name), .type = .file };
} else {
log.logger("file_browser").err("receive", tp.unexpected(m));

View file

@ -118,7 +118,7 @@ pub fn restore_state(palette: *Type) !void {
tp.extract(&name_),
tp.extract(&used_time),
}) catch |e| switch (e) {
error.CborTooShort => return,
error.TooShort => return,
else => return e,
}) {
const id = command.getId(name_) orelse continue;

View file

@ -148,19 +148,19 @@ fn add_item(self: *Self, file_name: []const u8, matches: ?[]const u8) !void {
try self.menu.add_item_with_handler(label.items, menu_action_open_file);
}
fn receive_project_manager(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
if (try m.match(.{ "PRJ", tp.more })) {
self.process_project_manager(m) catch |e| return tp.exit_error(e, @errorReturnTrace());
fn receive_project_manager(self: *Self, _: tp.pid_ref, m: tp.message) MessageFilter.Error!bool {
if (cbor.match(m.buf, .{ "PRJ", tp.more }) catch false) {
try self.process_project_manager(m);
return true;
}
return false;
}
fn process_project_manager(self: *Self, m: tp.message) !void {
fn process_project_manager(self: *Self, m: tp.message) MessageFilter.Error!void {
var file_name: []const u8 = undefined;
var matches: []const u8 = undefined;
var query: []const u8 = undefined;
if (try m.match(.{ "PRJ", "recent", tp.extract(&self.longest), tp.extract(&file_name), tp.extract_cbor(&matches) })) {
if (try cbor.match(m.buf, .{ "PRJ", "recent", tp.extract(&self.longest), tp.extract(&file_name), tp.extract_cbor(&matches) })) {
if (self.need_reset) self.reset_results();
try self.add_item(file_name, matches);
self.menu.resize(.{ .y = 0, .x = self.menu_pos_x(), .w = self.menu_width() });
@ -169,7 +169,7 @@ fn process_project_manager(self: *Self, m: tp.message) !void {
self.need_select_first = false;
}
tui.need_render();
} else if (try m.match(.{ "PRJ", "recent", tp.extract(&self.longest), tp.extract(&file_name) })) {
} else if (try cbor.match(m.buf, .{ "PRJ", "recent", tp.extract(&self.longest), tp.extract(&file_name) })) {
if (self.need_reset) self.reset_results();
try self.add_item(file_name, null);
self.menu.resize(.{ .y = 0, .x = self.menu_pos_x(), .w = self.menu_width() });
@ -178,7 +178,7 @@ fn process_project_manager(self: *Self, m: tp.message) !void {
self.need_select_first = false;
}
tui.need_render();
} else if (try m.match(.{ "PRJ", "recent_done", tp.extract(&self.longest), tp.extract(&query) })) {
} else if (try cbor.match(m.buf, .{ "PRJ", "recent_done", tp.extract(&self.longest), tp.extract(&query) })) {
self.query_pending = false;
self.need_reset = true;
if (!std.mem.eql(u8, self.inputbox.text.items, query))
@ -283,7 +283,7 @@ fn reset_results(self: *Self) void {
self.need_select_first = true;
}
fn start_query(self: *Self) !void {
fn start_query(self: *Self) MessageFilter.Error!void {
if (self.query_pending) return;
self.query_pending = true;
try project_manager.query_recent_files(max_recent_files, self.inputbox.text.items);

View file

@ -1,5 +1,6 @@
const std = @import("std");
const tp = @import("thespian");
const cbor = @import("cbor");
const zeit = @import("zeit");
const Plane = @import("renderer").Plane;
@ -67,8 +68,8 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool {
return false;
}
fn receive_tick(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
if (try m.match(.{"CLOCK"})) {
fn receive_tick(self: *Self, _: tp.pid_ref, m: tp.message) MessageFilter.Error!bool {
if (try cbor.match(m.buf, .{"CLOCK"})) {
tui.need_render();
self.update_tick_timer(.ticked);
return true;

View file

@ -1,5 +1,6 @@
const std = @import("std");
const tp = @import("thespian");
const cbor = @import("cbor");
const log = @import("log");
const Plane = @import("renderer").Plane;
@ -76,13 +77,13 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool {
return false;
}
fn receive_log(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
fn receive_log(self: *Self, _: tp.pid_ref, m: tp.message) MessageFilter.Error!bool {
var clear_msg_num: usize = 0;
if (try m.match(.{ "log", tp.more })) {
logview.process_log(m) catch |e| return tp.exit_error(e, @errorReturnTrace());
self.process_log(m) catch |e| return tp.exit_error(e, @errorReturnTrace());
if (try cbor.match(m.buf, .{ "log", tp.more })) {
try logview.process_log(m);
try self.process_log(m);
return true;
} else if (try m.match(.{ "MINILOG", tp.extract(&clear_msg_num) })) {
} else if (try cbor.match(m.buf, .{ "MINILOG", tp.extract(&clear_msg_num) })) {
if (clear_msg_num == self.msg_counter)
self.clear();
return true;
@ -90,20 +91,20 @@ fn receive_log(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
return false;
}
fn process_log(self: *Self, m: tp.message) !void {
fn process_log(self: *Self, m: tp.message) MessageFilter.Error!void {
var src: []const u8 = undefined;
var context: []const u8 = undefined;
var msg: []const u8 = undefined;
if (try m.match(.{ "log", tp.extract(&src), tp.extract(&msg) })) {
if (try cbor.match(m.buf, .{ "log", tp.extract(&src), tp.extract(&msg) })) {
try self.set(msg, .info);
} else if (try m.match(.{ "log", "error", tp.extract(&src), tp.extract(&context), "->", tp.extract(&msg) })) {
} else if (try cbor.match(m.buf, .{ "log", "error", tp.extract(&src), tp.extract(&context), "->", tp.extract(&msg) })) {
const err_stop = "error.Stop";
if (std.mem.eql(u8, msg, err_stop))
return;
if (msg.len >= err_stop.len + 1 and std.mem.eql(u8, msg[0 .. err_stop.len + 1], err_stop ++ "\n"))
return;
try self.set(msg, .err);
} else if (try m.match(.{ "log", tp.extract(&src), tp.more })) {
} else if (try cbor.match(m.buf, .{ "log", tp.extract(&src), tp.more })) {
self.level = .err;
var s = std.json.writeStream(self.msg.writer(), .{});
var iter: []const u8 = m.buf;