diff --git a/src/LSP.zig b/src/LSP.zig index 7f19c47..baf2689 100644 --- a/src/LSP.zig +++ b/src/LSP.zig @@ -9,7 +9,7 @@ pid: ?tp.pid, const Self = @This(); const module_name = @typeName(Self); -const sp_tag = "LSP"; +const sp_tag = "child"; const debug_lsp = true; pub const Error = error{ OutOfMemory, Exit }; @@ -55,6 +55,7 @@ const Process = struct { recv_buf: std.ArrayList(u8), parent: tp.pid, tag: [:0]const u8, + sp_tag: [:0]const u8, log_file: ?std.fs.File = null, next_id: i32 = 0, requests: std.AutoHashMap(i32, tp.pid), @@ -63,6 +64,10 @@ const Process = struct { pub fn create(a: std.mem.Allocator, cmd: tp.message, tag: [:0]const u8) Error!tp.pid { const self = try a.create(Process); + var sp_tag_ = std.ArrayList(u8).init(a); + defer sp_tag_.deinit(); + try sp_tag_.appendSlice(tag); + try sp_tag_.appendSlice("-" ++ sp_tag); self.* = .{ .a = a, .cmd = try cmd.clone(a), @@ -71,6 +76,7 @@ const Process = struct { .parent = tp.self_pid().clone(), .tag = try a.dupeZ(u8, tag), .requests = std.AutoHashMap(i32, tp.pid).init(a), + .sp_tag = try sp_tag_.toOwnedSliceSentinel(0), }; return tp.spawn_link(self.a, self, Process.start, tag) catch |e| tp.exit_error(e); } @@ -78,16 +84,19 @@ const Process = struct { fn deinit(self: *Process) void { var i = self.requests.iterator(); while (i.next()) |req| req.value_ptr.deinit(); + self.a.free(self.sp_tag); self.recv_buf.deinit(); self.a.free(self.cmd.buf); - if (self.log_file) |file| file.close(); self.close() catch {}; + self.write_log("### terminated LSP process ###\n", .{}); + if (self.log_file) |file| file.close(); } fn close(self: *Process) tp.result { if (self.sp) |*sp| { defer self.sp = null; try sp.close(); + self.write_log("### closed ###\n", .{}); } } @@ -95,7 +104,7 @@ const Process = struct { const frame = tracy.initZone(@src(), .{ .name = module_name ++ " start" }); defer frame.deinit(); _ = tp.set_trap(true); - self.sp = tp.subprocess.init(self.a, self.cmd, sp_tag, .Pipe) catch |e| return tp.exit_error(e); + self.sp = tp.subprocess.init(self.a, self.cmd, self.sp_tag, .Pipe) catch |e| return tp.exit_error(e); tp.receive(&self.receiver); var log_file_path = std.ArrayList(u8).init(self.a); @@ -111,21 +120,24 @@ const Process = struct { errdefer self.deinit(); var method: []u8 = ""; var bytes: []u8 = ""; + var err: []u8 = ""; + var code: u32 = 0; if (try m.match(.{ "REQ", tp.extract(&method), tp.extract(&bytes) })) { self.send_request(from, method, bytes) catch |e| return tp.exit_error(e); } else if (try m.match(.{ "NTFY", tp.extract(&method), tp.extract(&bytes) })) { self.send_notification(method, bytes) catch |e| return tp.exit_error(e); } else if (try m.match(.{"close"})) { + self.write_log("### LSP close ###\n", .{}); try self.close(); - } else if (try m.match(.{ sp_tag, "stdout", tp.extract(&bytes) })) { + } else if (try m.match(.{ self.sp_tag, "stdout", tp.extract(&bytes) })) { self.handle_output(bytes) catch |e| return tp.exit_error(e); - } else if (try m.match(.{ sp_tag, "term", tp.more })) { - self.handle_terminated() catch |e| return tp.exit_error(e); - } else if (try m.match(.{ sp_tag, "stderr", tp.extract(&bytes) })) { + } else if (try m.match(.{ self.sp_tag, "term", tp.extract(&err), tp.extract(&code) })) { + self.handle_terminated(err, code) catch |e| return tp.exit_error(e); + } else if (try m.match(.{ self.sp_tag, "stderr", tp.extract(&bytes) })) { self.write_log("{s}\n", .{bytes}); } else if (try m.match(.{ "exit", "normal" })) { - return tp.exit_normal(); + // self.write_log("### exit normal ###\n", .{}); } else { const e = tp.unexpected(m); self.write_log("{s}\n", .{tp.error_text()}); @@ -189,9 +201,9 @@ const Process = struct { try self.frame_message_recv(); } - fn handle_terminated(self: *Process) !void { - self.write_log("terminated\n", .{}); - try self.parent.send(.{ self.tag, "done" }); + fn handle_terminated(self: *Process, err: []const u8, code: u32) !void { + self.write_log("### subprocess terminated {s} {d} ###\n", .{ err, code }); + try self.parent.send(.{ sp_tag, self.tag, "done" }); } fn send_request(self: *Process, from: tp.pid_ref, method: []const u8, params_cb: []const u8) !void { @@ -218,7 +230,7 @@ const Process = struct { var output = std.ArrayList(u8).init(self.a); defer output.deinit(); const writer = output.writer(); - try writer.print("Content-Length: {d}\r\n\r\n", .{json.len}); + try writer.print("Content-Length: {d}\r\nContent-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n", .{json.len}); _ = try writer.write(json); try sp.send(output.items); @@ -273,6 +285,16 @@ const Process = struct { const json = if (params) |p| try cbor.toJsonPrettyAlloc(self.a, p) else null; defer if (json) |p| self.a.free(p); self.write_log("### RECV req: {d}\nmethod: {s}\n{s}\n###\n", .{ id, method, json orelse "no params" }); + var msg = std.ArrayList(u8).init(self.a); + defer msg.deinit(); + const writer = msg.writer(); + try cbor.writeArrayHeader(writer, 6); + try cbor.writeValue(writer, sp_tag); + try cbor.writeValue(writer, self.tag); + try cbor.writeValue(writer, "request"); + try cbor.writeValue(writer, method); + try cbor.writeValue(writer, id); + if (params) |p| _ = try writer.write(p) else try cbor.writeValue(writer, null); } fn receive_lsp_response(self: *Process, id: i32, result: ?[]const u8, err: ?[]const u8) !void { @@ -285,13 +307,15 @@ const Process = struct { var msg = std.ArrayList(u8).init(self.a); defer msg.deinit(); const writer = msg.writer(); - try cbor.writeArrayHeader(writer, 2); + try cbor.writeArrayHeader(writer, 4); + try cbor.writeValue(writer, sp_tag); + try cbor.writeValue(writer, self.tag); if (err) |err_| { try cbor.writeValue(writer, "error"); - try cbor.writeValue(writer, err_); + _ = try writer.write(err_); } else if (result) |result_| { try cbor.writeValue(writer, "result"); - try cbor.writeValue(writer, result_); + _ = try writer.write(result_); } try from.send_raw(.{ .buf = msg.items }); } @@ -300,6 +324,15 @@ const Process = struct { const json = if (params) |p| try cbor.toJsonPrettyAlloc(self.a, p) else null; defer if (json) |p| self.a.free(p); self.write_log("### RECV notify:\nmethod: {s}\n{s}\n###\n", .{ method, json orelse "no params" }); + var msg = std.ArrayList(u8).init(self.a); + defer msg.deinit(); + const writer = msg.writer(); + try cbor.writeArrayHeader(writer, 5); + try cbor.writeValue(writer, sp_tag); + try cbor.writeValue(writer, self.tag); + try cbor.writeValue(writer, "notify"); + try cbor.writeValue(writer, method); + if (params) |p| _ = try writer.write(p) else try cbor.writeValue(writer, null); } fn write_log(self: *Process, comptime format: []const u8, args: anytype) void { diff --git a/src/Project.zig b/src/Project.zig index c8f596b..edd8a8c 100644 --- a/src/Project.zig +++ b/src/Project.zig @@ -41,27 +41,9 @@ fn get_lsp(self: *Self) !LSP { self.lsp = try LSP.open(self.a, tp.message.fmt(.{self.lsp_name}), self.lsp_name); const uri = try self.make_URI(null); defer self.a.free(uri); - const response = try self.lsp.?.send_request(self.a, "initialize", .{ - .processId = std.os.linux.getpid(), - .rootUri = uri, - .clientInfo = .{ .name = root.application_name }, - .capabilities = .{ - .workspace = .{ - .applyEdit = true, - .codeLens = .{ .refreshSupport = true }, - .configuration = true, - .diagnostics = .{ .refreshSupport = true }, - .fileOperations = .{ - .didCreate = true, - .didDelete = true, - .didRename = true, - .willCreate = true, - .willDelete = true, - .willRename = true, - }, - }, - }, - }); + 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); defer self.a.free(response.buf); return self.lsp.?; } @@ -69,9 +51,9 @@ fn get_lsp(self: *Self) !LSP { fn make_URI(self: *Self, file_path: ?[]const u8) ![]const u8 { var buf = std.ArrayList(u8).init(self.a); if (file_path) |path| - try buf.writer().print("file:/{s}/{s}", .{ self.name, path }) + try buf.writer().print("file://{s}/{s}", .{ self.name, path }) else - try buf.writer().print("file:/{s}", .{self.name}); + try buf.writer().print("file://{s}", .{self.name}); return buf.toOwnedSlice(); } @@ -111,19 +93,13 @@ pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []co } 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); 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/didOpen", .{ - .textDocument = .{ - .uri = uri, - .languageId = file_type, - .version = version, - .text = text, - }, + try lsp.send_notification("textDocument/didOpen", .{ + .textDocument = .{ .uri = uri, .languageId = file_type, .version = version, .text = text }, }); - defer self.a.free(response.buf); - _ = from; } pub fn goto_definition(self: *Self, from: tp.pid_ref, file_path: []const u8, row: usize, col: usize) tp.result { @@ -135,5 +111,305 @@ 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); - _ = from; + try from.send_raw(response); +} + +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", .{ + .processId = std.os.linux.getpid(), + .rootPath = project_path, + .rootUri = project_uri, + .workspaceFolders = .{ + .{ + .uri = project_uri, + .name = project_basename, + }, + }, + .trace = "verbose", + .locale = "en-us", + .clientInfo = .{ + .name = root.application_name, + .version = "0.0.1", + }, + .capabilities = .{ + .workspace = .{ + .applyEdit = true, + .workspaceEdit = .{ + .documentChanges = true, + .resourceOperations = .{ + "create", + "rename", + "delete", + }, + .failureHandling = "textOnlyTransactional", + .normalizesLineEndings = true, + .changeAnnotationSupport = .{ .groupsOnLabel = true }, + }, + .configuration = true, + .didChangeWatchedFiles = .{ + .dynamicRegistration = true, + .relativePatternSupport = true, + }, + .symbol = .{ + .dynamicRegistration = true, + .symbolKind = .{ + .valueSet = .{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 }, + }, + .tagSupport = .{ .valueSet = .{1} }, + .resolveSupport = .{ .properties = .{"location.range"} }, + }, + .codeLens = .{ .refreshSupport = true }, + .executeCommand = .{ .dynamicRegistration = true }, + .didChangeConfiguration = .{ .dynamicRegistration = true }, + .workspaceFolders = true, + .semanticTokens = .{ .refreshSupport = true }, + .fileOperations = .{ + .dynamicRegistration = true, + .didCreate = true, + .didRename = true, + .didDelete = true, + .willCreate = true, + .willRename = true, + .willDelete = true, + }, + .inlineValue = .{ .refreshSupport = true }, + .inlayHint = .{ .refreshSupport = true }, + .diagnostics = .{ .refreshSupport = true }, + }, + .textDocument = .{ + .publishDiagnostics = .{ + .relatedInformation = true, + .versionSupport = false, + .tagSupport = .{ .valueSet = .{ 1, 2 } }, + .codeDescriptionSupport = true, + .dataSupport = true, + }, + .synchronization = .{ + .dynamicRegistration = true, + .willSave = true, + .willSaveWaitUntil = true, + .didSave = true, + }, + .completion = .{ + .dynamicRegistration = true, + .contextSupport = true, + .completionItem = .{ + .snippetSupport = true, + .commitCharactersSupport = true, + .documentationFormat = .{ + "markdown", + "plaintext", + }, + .deprecatedSupport = true, + .preselectSupport = true, + .tagSupport = .{ .valueSet = .{1} }, + .insertReplaceSupport = true, + .resolveSupport = .{ .properties = .{ + "documentation", + "detail", + "additionalTextEdits", + } }, + .insertTextModeSupport = .{ .valueSet = .{ 1, 2 } }, + .labelDetailsSupport = true, + }, + .insertTextMode = 2, + .completionItemKind = .{ + .valueSet = .{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 }, + }, + .completionList = .{ .itemDefaults = .{ + "commitCharacters", + "editRange", + "insertTextFormat", + "insertTextMode", + } }, + }, + .hover = .{ + .dynamicRegistration = true, + .contentFormat = .{ "markdown", "plaintext" }, + }, + .signatureHelp = .{ + .dynamicRegistration = true, + .signatureInformation = .{ + .documentationFormat = .{ "markdown", "plaintext" }, + .parameterInformation = .{ .labelOffsetSupport = true }, + .activeParameterSupport = true, + }, + .contextSupport = true, + }, + .definition = .{ + .dynamicRegistration = true, + .linkSupport = true, + }, + .references = .{ .dynamicRegistration = true }, + .documentHighlight = .{ .dynamicRegistration = true }, + .documentSymbol = .{ + .dynamicRegistration = true, + .symbolKind = .{ + .valueSet = .{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 }, + }, + .hierarchicalDocumentSymbolSupport = true, + .tagSupport = .{ .valueSet = .{1} }, + .labelSupport = true, + }, + .codeAction = .{ + .dynamicRegistration = true, + .isPreferredSupport = true, + .disabledSupport = true, + .dataSupport = true, + .resolveSupport = .{ .properties = .{"edit"} }, + .codeActionLiteralSupport = .{ + .codeActionKind = .{ + .valueSet = .{ + "", + "quickfix", + "refactor", + "refactor.extract", + "refactor.inline", + "refactor.rewrite", + "source", + "source.organizeImports", + }, + }, + }, + .honorsChangeAnnotations = false, + }, + .codeLens = .{ .dynamicRegistration = true }, + .formatting = .{ .dynamicRegistration = true }, + .rangeFormatting = .{ .dynamicRegistration = true }, + .onTypeFormatting = .{ .dynamicRegistration = true }, + .rename = .{ + .dynamicRegistration = true, + .prepareSupport = true, + .prepareSupportDefaultBehavior = 1, + .honorsChangeAnnotations = true, + }, + .documentLink = .{ + .dynamicRegistration = true, + .tooltipSupport = true, + }, + .typeDefinition = .{ + .dynamicRegistration = true, + .linkSupport = true, + }, + .implementation = .{ + .dynamicRegistration = true, + .linkSupport = true, + }, + .colorProvider = .{ .dynamicRegistration = true }, + .foldingRange = .{ + .dynamicRegistration = true, + .rangeLimit = 5000, + .lineFoldingOnly = true, + .foldingRangeKind = .{ .valueSet = .{ "comment", "imports", "region" } }, + .foldingRange = .{ .collapsedText = false }, + }, + .declaration = .{ + .dynamicRegistration = true, + .linkSupport = true, + }, + .selectionRange = .{ .dynamicRegistration = true }, + .callHierarchy = .{ .dynamicRegistration = true }, + .semanticTokens = .{ + .dynamicRegistration = true, + .tokenTypes = .{ + "namespace", + "type", + "class", + "enum", + "interface", + "struct", + "typeParameter", + "parameter", + "variable", + "property", + "enumMember", + "event", + "function", + "method", + "macro", + "keyword", + "modifier", + "comment", + "string", + "number", + "regexp", + "operator", + "decorator", + }, + .tokenModifiers = .{ + "declaration", + "definition", + "readonly", + "static", + "deprecated", + "abstract", + "async", + "modification", + "documentation", + "defaultLibrary", + }, + .formats = .{"relative"}, + .requests = .{ + .range = true, + .full = .{ .delta = true }, + }, + .multilineTokenSupport = false, + .overlappingTokenSupport = false, + .serverCancelSupport = true, + .augmentsSyntaxTokens = true, + }, + .linkedEditingRange = .{ .dynamicRegistration = true }, + .typeHierarchy = .{ .dynamicRegistration = true }, + .inlineValue = .{ .dynamicRegistration = true }, + .inlayHint = .{ + .dynamicRegistration = true, + .resolveSupport = .{ + .properties = .{ + "tooltip", + "textEdits", + "label.tooltip", + "label.location", + "label.command", + }, + }, + }, + .diagnostic = .{ + .dynamicRegistration = true, + .relatedDocumentSupport = false, + }, + }, + .window = .{ + .showMessage = .{ + .messageActionItem = .{ .additionalPropertiesSupport = true }, + }, + .showDocument = .{ .support = true }, + .workDoneProgress = true, + }, + .general = .{ + .staleRequestSupport = .{ + .cancel = true, + .retryOnContentModified = .{ + "textDocument/semanticTokens/full", + "textDocument/semanticTokens/range", + "textDocument/semanticTokens/full/delta", + }, + }, + .regularExpressions = .{ + .engine = "ECMAScript", + .version = "ES2020", + }, + .markdown = .{ + .parser = "marked", + .version = "1.1.0", + }, + .positionEncodings = .{"utf-16"}, + }, + .notebookDocument = .{ + .synchronization = .{ + .dynamicRegistration = true, + .executionSummarySupport = true, + }, + }, + }, + }); } diff --git a/src/tui/editor.zig b/src/tui/editor.zig index b305e07..37c4383 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -164,7 +164,7 @@ pub const Editor = struct { file_path: ?[]const u8, buffer: ?*Buffer, - lsp_version: usize = 0, + lsp_version: usize = 1, cursels: CurSel.List, cursels_saved: CurSel.List,