/* * @file lsp_plugin.cpp * @brief LSP plugin: Language Server Protocol JSON-RPC client for diagnostics, hover, completion. * LSP 插件:Language Server Protocol JSON-RPC 客户端,用于诊断、悬停、补全。 * Copyright (c) 2026 dstalk contributors. GPLv3. */ // plugin_lsp — LSP (Language Server Protocol) 服务 / LSP (Language Server Protocol) service // // 自行管理语言服务器子进程,使用 JSON-RPC 2.0 over stdio 通信 / Self-manages language server subprocess, communicates via JSON-RPC 2.0 over stdio. // 无外部服务依赖(不依赖 http/config 等其他插件) / No external service dependencies (does not depend on http/config or other plugins). #include "dstalk/dstalk_host.h" #include "dstalk/dstalk_services.h" #include "lsp_internal.hpp" #include #include #include #include #include #include #include #include #include #include #include // ============================================================================ // 平台相关 — 子进程管理 (内嵌 subprocess::Process) / Platform specific — subprocess management (embedded subprocess::Process) // ============================================================================ #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #ifndef NOMINMAX #define NOMINMAX #endif #include #else #include #include #include #include #include #include #endif namespace json = boost::json; // ============================================================================ // 全局指针 / Global pointers // ============================================================================ static const dstalk_host_api_t* g_host = nullptr; // ============================================================================ // 子进程封装 (内嵌 subprocess.hpp) / Subprocess wrapper (embedded subprocess.hpp) // ============================================================================ struct Process { #ifdef _WIN32 HANDLE hProcess = INVALID_HANDLE_VALUE; HANDLE hThread = INVALID_HANDLE_VALUE; HANDLE hStdIn = INVALID_HANDLE_VALUE; HANDLE hStdOut = INVALID_HANDLE_VALUE; #else pid_t pid = -1; int stdin_fd = -1; int stdout_fd = -1; #endif // 从给定命令行启动子进程。为 stdin/stdout 设置管道 / Start a child process from the given command line. Sets up pipes for stdin/stdout. bool start(const char* cmd) { if (!cmd || !cmd[0]) return false; stop(); #ifdef _WIN32 SECURITY_ATTRIBUTES sa = {}; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle = TRUE; HANDLE child_stdin_read = INVALID_HANDLE_VALUE; HANDLE child_stdout_write = INVALID_HANDLE_VALUE; if (!CreatePipe(&child_stdin_read, &hStdIn, &sa, 0)) goto win32_fail; if (!SetHandleInformation(hStdIn, HANDLE_FLAG_INHERIT, 0)) goto win32_fail; if (!CreatePipe(&hStdOut, &child_stdout_write, &sa, 0)) goto win32_fail; if (!SetHandleInformation(hStdOut, HANDLE_FLAG_INHERIT, 0)) goto win32_fail; { STARTUPINFOW si = {}; si.cb = sizeof(STARTUPINFOW); si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE; si.hStdInput = child_stdin_read; si.hStdOutput = child_stdout_write; si.hStdError = child_stdout_write; PROCESS_INFORMATION pi = {}; std::string cmd_copy(cmd); wchar_t wcmd[4096] = {}; if (MultiByteToWideChar(CP_UTF8, 0, cmd_copy.c_str(), -1, wcmd, 4096) == 0) goto win32_fail; if (!CreateProcessW(nullptr, wcmd, nullptr, nullptr, TRUE, CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi)) goto win32_fail; hProcess = pi.hProcess; hThread = pi.hThread; } CloseHandle(child_stdin_read); CloseHandle(child_stdout_write); return true; win32_fail: if (child_stdin_read != INVALID_HANDLE_VALUE) CloseHandle(child_stdin_read); if (child_stdout_write != INVALID_HANDLE_VALUE) CloseHandle(child_stdout_write); if (hStdIn != INVALID_HANDLE_VALUE) { CloseHandle(hStdIn); hStdIn = INVALID_HANDLE_VALUE; } if (hStdOut != INVALID_HANDLE_VALUE) { CloseHandle(hStdOut); hStdOut = INVALID_HANDLE_VALUE; } if (hProcess != INVALID_HANDLE_VALUE) { CloseHandle(hProcess); hProcess = INVALID_HANDLE_VALUE; } if (hThread != INVALID_HANDLE_VALUE) { CloseHandle(hThread); hThread = INVALID_HANDLE_VALUE; } return false; #else int pin[2] = {-1, -1}; int pout[2] = {-1, -1}; if (pipe(pin) != 0) goto posix_fail; if (pipe(pout) != 0) goto posix_fail; pid = fork(); if (pid < 0) goto posix_fail; if (pid == 0) { dup2(pin[0], STDIN_FILENO); close(pin[0]); close(pin[1]); dup2(pout[1], STDOUT_FILENO); close(pout[0]); close(pout[1]); int max_fd = static_cast(sysconf(_SC_OPEN_MAX)); if (max_fd > 3) { for (int i = 3; i < max_fd; ++i) close(i); } char* argv[64] = {}; int argc = 0; char* cmd_copy = strdup(cmd); char* token = strtok(cmd_copy, " \t"); while (token && argc < 63) { argv[argc++] = token; token = strtok(nullptr, " \t"); } argv[argc] = nullptr; execvp(argv[0], argv); _exit(127); } close(pin[0]); close(pout[1]); stdin_fd = pin[1]; stdout_fd = pout[0]; return true; posix_fail: if (pin[0] != -1) close(pin[0]); if (pin[1] != -1) close(pin[1]); if (pout[0] != -1) close(pout[0]); if (pout[1] != -1) close(pout[1]); stdin_fd = -1; stdout_fd = -1; pid = -1; return false; #endif } // 优雅终止子进程,回退到 SIGKILL/TerminateProcess / Gracefully terminate the child process, with fallback to SIGKILL/TerminateProcess. void stop() { #ifdef _WIN32 if (hProcess != INVALID_HANDLE_VALUE) { WaitForSingleObject(hProcess, 2000); TerminateProcess(hProcess, 1); CloseHandle(hProcess); hProcess = INVALID_HANDLE_VALUE; } if (hThread != INVALID_HANDLE_VALUE) { CloseHandle(hThread); hThread = INVALID_HANDLE_VALUE; } if (hStdIn != INVALID_HANDLE_VALUE) { CloseHandle(hStdIn); hStdIn = INVALID_HANDLE_VALUE; } if (hStdOut != INVALID_HANDLE_VALUE) { CloseHandle(hStdOut); hStdOut = INVALID_HANDLE_VALUE; } #else if (pid > 0) { kill(pid, SIGTERM); int status = 0; for (int i = 0; i < 20; ++i) { if (waitpid(pid, &status, WNOHANG) > 0) break; usleep(100000); } if (waitpid(pid, &status, WNOHANG) == 0) { kill(pid, SIGKILL); waitpid(pid, &status, 0); } pid = -1; } if (stdin_fd != -1) { close(stdin_fd); stdin_fd = -1; } if (stdout_fd != -1) { close(stdout_fd); stdout_fd = -1; } #endif } // 将数据字符串写入子进程 stdin 管道 / Write a data string to the child's stdin pipe. bool write(const std::string& data) { if (data.empty()) return true; #ifdef _WIN32 if (hStdIn == INVALID_HANDLE_VALUE) return false; DWORD written = 0; return WriteFile(hStdIn, data.c_str(), static_cast(data.size()), &written, nullptr) && written == data.size(); #else if (stdin_fd < 0) return false; size_t total = 0; const char* buf = data.c_str(); size_t len = data.size(); while (total < len) { ssize_t n = ::write(stdin_fd, buf + total, len - total); if (n <= 0) return false; total += static_cast(n); } return true; #endif } // 从子进程 stdout 管道读取一行(到并包括 '\n') / Read one line (up to and including '\n') from the child's stdout pipe. bool read_line(std::string& line) { line.clear(); #ifdef _WIN32 if (hStdOut == INVALID_HANDLE_VALUE) return false; char ch; DWORD nread = 0; while (true) { if (!ReadFile(hStdOut, &ch, 1, &nread, nullptr)) return false; if (nread == 0) return false; line += ch; if (ch == '\n') return true; } #else if (stdout_fd < 0) return false; char ch; while (true) { ssize_t n = ::read(stdout_fd, &ch, 1); if (n <= 0) return false; line += ch; if (ch == '\n') return true; } #endif } // 从子进程 stdout 管道读取恰好 count 字节到 buf / Read exactly `count` bytes from the child's stdout pipe into `buf`. bool read_bytes(std::string& buf, int count) { if (count <= 0) { buf.clear(); return true; } #ifdef _WIN32 if (hStdOut == INVALID_HANDLE_VALUE) return false; buf.resize(static_cast(count) + 1); DWORD total = 0, nread = 0; while (total < static_cast(count)) { if (!ReadFile(hStdOut, const_cast(buf.data()) + total, static_cast(count) - total, &nread, nullptr)) return false; if (nread == 0) return false; total += nread; } buf[count] = '\0'; buf.resize(count); return true; #else if (stdout_fd < 0) return false; buf.resize(count); size_t total = 0; while (total < static_cast(count)) { ssize_t n = ::read(stdout_fd, const_cast(buf.data()) + total, static_cast(count) - total); if (n <= 0) return false; total += static_cast(n); } return true; #endif } }; // ============================================================================ // LSP 状态(静态单例) / LSP state (static singleton) // ============================================================================ struct LspState { Process proc; std::atomic running{false}; std::string language; std::atomic next_id{1}; // 响应用于同步等待 / Responses for synchronous waiting std::mutex mutex; std::condition_variable cv; std::unordered_map pending_responses; // 诊断缓存: URI -> JSON 字符串 / Diagnostics cache: URI -> JSON string std::unordered_map diagnostics; // 读取线程 / Reader thread std::thread reader_thread; }; static LspState g_lsp; // ============================================================================ // 辅助函数 / Helper functions // ============================================================================ // 去除 string_view 首尾空白 / Trim leading and trailing whitespace from a string_view. std::string_view lsp_trim(std::string_view sv) { while (!sv.empty() && (sv.front() == ' ' || sv.front() == '\t' || sv.front() == '\r' || sv.front() == '\n')) sv.remove_prefix(1); while (!sv.empty() && (sv.back() == ' ' || sv.back() == '\t' || sv.back() == '\r' || sv.back() == '\n')) sv.remove_suffix(1); return sv; } // 将 JSON-RPC 消息体包装在 LSP 头中 (Content-Length: ...\r\n\r\n) / Wrap a JSON-RPC message body in an LSP header (Content-Length: ...\r\n\r\n). std::string lsp_frame_message(const std::string& body) { std::string frame; frame.reserve(64 + body.size()); frame += "Content-Length: "; frame += std::to_string(body.size()); frame += "\r\n\r\n"; frame += body; return frame; } // 从 LSP 头行中解析 Content-Length 值。解析失败返回 -1 / Parse the Content-Length value from an LSP header line. Returns -1 on parse failure. int lsp_parse_content_length(const std::string& line) { auto sv = lsp_trim(std::string_view(line)); const char prefix[] = "Content-Length:"; const size_t prefix_len = sizeof(prefix) - 1; if (sv.size() <= prefix_len) return -1; for (size_t i = 0; i < prefix_len; ++i) { if (std::tolower(static_cast(sv[i])) != std::tolower(static_cast(prefix[i]))) return -1; } std::string_view num_sv = sv.substr(prefix_len); while (!num_sv.empty() && (num_sv.front() == ' ' || num_sv.front() == '\t')) num_sv.remove_prefix(1); try { return std::stoi(std::string(num_sv)); } catch (...) { return -1; } } // ============================================================================ // JSON-RPC 消息发送 / JSON-RPC message sending // ============================================================================ // 向 LSP 服务器发送 JSON-RPC 请求并返回分配的请求 id / Send a JSON-RPC request to the LSP server and return the assigned request id. static int send_request(const std::string& method, const json::object& params) { int id = g_lsp.next_id.fetch_add(1); json::object msg; msg["jsonrpc"] = "2.0"; msg["id"] = id; msg["method"] = method; msg["params"] = params; std::string body = json::serialize(msg); g_lsp.proc.write(lsp_frame_message(body)); return id; } // 向 LSP 服务器发送 JSON-RPC 通知(无 id 字段,不期待响应) / Send a JSON-RPC notification to the LSP server (no id field, no response expected). static void send_notification(const std::string& method, const json::object& params) { json::object msg; msg["jsonrpc"] = "2.0"; msg["method"] = method; msg["params"] = params; std::string body = json::serialize(msg); g_lsp.proc.write(lsp_frame_message(body)); } // ============================================================================ // 消息处理 / Message handling // ============================================================================ // 分发接收到的 JSON-RPC 消息:将响应路由到待处理队列, // 处理 textDocument/publishDiagnostics 通知并存入诊断缓存 / Dispatch a received JSON-RPC message: route responses to pending queue, // handle textDocument/publishDiagnostics notifications into diagnostics cache. static void handle_message(const std::string& body) { try { json::value val; try { val = json::parse(body); } catch (...) { return; } json::object msg; try { msg = val.as_object(); } catch (...) { return; } if (msg.contains("id") && !msg.contains("method")) { // 响应 (有 id, 无 method) / Response (has id, no method) int id = static_cast(msg["id"].as_int64()); std::lock_guard lock(g_lsp.mutex); g_lsp.pending_responses[id] = body; g_lsp.cv.notify_all(); } else if (msg.contains("method") && !msg.contains("id")) { // 通知 (有 method, 无 id) / Notification (has method, no id) std::string method; try { method = json::value_to(msg["method"]); } catch (...) { return; } if (method == "textDocument/publishDiagnostics") { if (!msg.contains("params")) return; auto params = msg["params"].as_object(); if (!params.contains("uri")) return; std::string uri = json::value_to(params["uri"]); std::string diag_json; if (params.contains("diagnostics")) diag_json = json::serialize(params["diagnostics"]); else diag_json = "[]"; std::lock_guard lock(g_lsp.mutex); g_lsp.diagnostics[uri] = diag_json; } } } catch (const std::exception& e) { if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] handle_message: %s", e.what()); } catch (...) { if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] handle_message: unknown exception"); } } // ============================================================================ // 读取线程主循环 / Reader thread main loop // ============================================================================ // 读取线程主循环:解析 LSP header+body 帧并分发消息 / Main loop for the reader thread: parse LSP header+body frames and dispatch messages. static void reader_loop() { try { while (g_lsp.running) { int content_length = -1; bool pipe_ok = true; // 状态机式读取 header 块:循环 read_line 直到读到空行 / State-machine header block read: loop read_line until empty line // LSP 3.17: header 块以空行(\r\n)结束,允许 Content-Type 等其他 header / LSP 3.17: header block ends with empty line (\r\n), allows other headers like Content-Type while (pipe_ok) { std::string line; if (!g_lsp.proc.read_line(line)) { pipe_ok = false; break; } // header 块以空行结束 / header block ends with empty line auto sv = lsp_trim(std::string_view(line)); if (sv.empty()) break; // 累积 Content-Length;遇到其他 header 不丢弃,继续读取下一行 / Accumulate Content-Length; don't discard other headers, continue reading next line int len = lsp_parse_content_length(line); if (len >= 0) content_length = len; } if (!pipe_ok) break; // 空行前都没读到 Content-Length,协议错误——记日志并跳过这一帧 / Content-Length not read before empty line, protocol error — log and skip this frame if (content_length < 0) { if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] Invalid LSP frame: missing Content-Length header"); continue; } std::string body; if (!g_lsp.proc.read_bytes(body, content_length)) break; handle_message(body); } } catch (const std::exception& e) { if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] reader_loop: %s", e.what()); } catch (...) { if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] reader_loop: unknown exception"); } std::lock_guard lock(g_lsp.mutex); g_lsp.running = false; g_lsp.cv.notify_all(); } // ============================================================================ // LSP 服务 vtable 实现 (定义在 vtable 变量之前) / LSP service vtable implementation (defined before vtable variable) // ============================================================================ static void g_lsp_impl_stop(); static void g_lsp_impl_stop_nolock(); static void g_lsp_impl_stop_locked(std::unique_lock& lock); // 启动 LSP 服务器进程,发送 initialize/initialized 握手,启动读取线程 / Start the LSP server process, send initialize/initialized handshake, start reader thread. static int g_lsp_impl_start(const char* server_cmd, const char* language) { if (!server_cmd || !server_cmd[0]) return -1; try { // 如果已在运行, 先停止 / If already running, stop first if (g_lsp.running) { g_lsp_impl_stop(); } g_lsp.language = language ? language : ""; // 启动进程 / Start process if (!g_lsp.proc.start(server_cmd)) { if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] failed to start: %s", server_cmd); return -1; } // 重置 ID 计数器 / Reset ID counter g_lsp.next_id = 1; // 启动读取线程 / Start reader thread g_lsp.running = true; g_lsp.reader_thread = std::thread(reader_loop); // 构建 initialize 参数 / Build initialize params json::object text_doc_caps; { json::object hover; hover["dynamicRegistration"] = false; text_doc_caps["hover"] = hover; json::object completion; completion["dynamicRegistration"] = false; text_doc_caps["completion"] = completion; json::object diagnostic; diagnostic["dynamicRegistration"] = false; text_doc_caps["diagnostic"] = diagnostic; } json::object capabilities; capabilities["textDocument"] = text_doc_caps; json::object init_params; init_params["processId"] = nullptr; init_params["rootUri"] = nullptr; init_params["capabilities"] = capabilities; // 发送 initialize 请求 / Send initialize request int init_id = send_request("initialize", init_params); // 等待 initialize 响应 (最多 10 秒) / Wait for initialize response (max 10 seconds) { std::unique_lock lock(g_lsp.mutex); bool got = g_lsp.cv.wait_for(lock, std::chrono::seconds(10), [init_id]() { return !g_lsp.running || g_lsp.pending_responses.count(init_id) > 0; }); if (!got || !g_lsp.running) { if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] initialize timed out"); g_lsp_impl_stop_locked(lock); return -1; } g_lsp.pending_responses.erase(init_id); } // 发送 initialized 通知 / Send initialized notification send_notification("initialized", json::object{}); if (g_host) g_host->log(DSTALK_LOG_INFO, "[lsp] server started: %s", server_cmd); return 0; } catch (const std::exception& e) { if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] start: %s", e.what()); return -1; } catch (...) { if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] start: unknown exception"); return -1; } } // 停止 LSP 服务器:发送 shutdown 请求,发送 exit 通知,停止进程和线程 / Stop the LSP server: send shutdown request, send exit notification, stop process & thread. static void g_lsp_impl_stop_nolock() { try { if (!g_lsp.running) return; // 发送 shutdown 请求 / Send shutdown request int shutdown_id = send_request("shutdown", json::object{}); // 等待 shutdown 响应 (最多 2 秒) / Wait for shutdown response (max 2 seconds) { std::unique_lock lock(g_lsp.mutex); g_lsp.cv.wait_for(lock, std::chrono::seconds(2), [shutdown_id]() { return !g_lsp.running || g_lsp.pending_responses.count(shutdown_id) > 0; }); g_lsp.pending_responses.clear(); } // 发送 exit 通知 / Send exit notification send_notification("exit", json::object{}); // 停止读取线程 / Stop reader thread g_lsp.running = false; g_lsp.proc.stop(); if (g_lsp.reader_thread.joinable()) g_lsp.reader_thread.join(); g_lsp.diagnostics.clear(); if (g_host) g_host->log(DSTALK_LOG_INFO, "[lsp] server stopped"); } catch (const std::exception& e) { if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] stop: %s", e.what()); } catch (...) { if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] stop: unknown exception"); } } // 公开 stop:无锁获取(委托给 g_lsp_impl_stop_nolock) / Public stop: acquires no lock (delegates to g_lsp_impl_stop_nolock). static void g_lsp_impl_stop() { g_lsp_impl_stop_nolock(); } // Stop 辅助函数:在调用 g_lsp_impl_stop_nolock 前解锁给定的 unique_lock / Stop helper: unlocks the given unique_lock before calling g_lsp_impl_stop_nolock. static void g_lsp_impl_stop_locked(std::unique_lock& lock) { lock.unlock(); g_lsp_impl_stop_nolock(); } // 向 LSP 服务器发送 textDocument/didOpen 通知 / Send a textDocument/didOpen notification to the LSP server. static int g_lsp_impl_open_document(const char* uri, const char* content, const char* lang_id) { if (!g_lsp.running) return -1; if (!uri || !content || !lang_id) return -1; try { json::object text_doc; text_doc["uri"] = uri; text_doc["languageId"] = lang_id; text_doc["version"] = 1; text_doc["text"] = content; json::object params; params["textDocument"] = text_doc; send_notification("textDocument/didOpen", params); return 0; } catch (const std::exception& e) { if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] open_document: %s", e.what()); return -1; } catch (...) { if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] open_document: unknown exception"); return -1; } } // 向 LSP 服务器发送 textDocument/didClose 通知 / Send a textDocument/didClose notification to the LSP server. static int g_lsp_impl_close_document(const char* uri) { if (!g_lsp.running) return -1; if (!uri) return -1; try { json::object text_doc; text_doc["uri"] = uri; json::object params; params["textDocument"] = text_doc; send_notification("textDocument/didClose", params); return 0; } catch (const std::exception& e) { if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] close_document: %s", e.what()); return -1; } catch (...) { if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] close_document: unknown exception"); return -1; } } // 返回给定文档 URI 的缓存诊断 JSON / Return the cached diagnostics JSON for the given document URI. static int g_lsp_impl_get_diagnostics(const char* uri, char** json_out) { if (!g_lsp.running) return -1; if (!uri || !json_out) return -1; try { std::lock_guard lock(g_lsp.mutex); auto it = g_lsp.diagnostics.find(uri); if (it == g_lsp.diagnostics.end()) { *json_out = g_host->strdup("[]"); } else { *json_out = g_host->strdup(it->second.c_str()); } return 0; } catch (const std::exception& e) { if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] get_diagnostics: %s", e.what()); *json_out = nullptr; return -1; } catch (...) { if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] get_diagnostics: unknown exception"); *json_out = nullptr; return -1; } } // 发送 textDocument/hover 请求并以 JSON 返回悬停结果 / Send a textDocument/hover request and return the hover result as JSON. static int g_lsp_impl_get_hover(const char* uri, int line, int col, char** json_out) { if (!g_lsp.running) return -1; if (!uri || !json_out) return -1; try { json::object position; position["line"] = line; position["character"] = col; json::object text_doc; text_doc["uri"] = uri; json::object params; params["textDocument"] = text_doc; params["position"] = position; int req_id = send_request("textDocument/hover", params); std::unique_lock lock(g_lsp.mutex); bool got = g_lsp.cv.wait_for(lock, std::chrono::seconds(10), [req_id]() { return !g_lsp.running || g_lsp.pending_responses.count(req_id) > 0; }); if (!got || !g_lsp.running || g_lsp.pending_responses.count(req_id) == 0) { return -1; } std::string response_body = g_lsp.pending_responses[req_id]; g_lsp.pending_responses.erase(req_id); json::value val; try { val = json::parse(response_body); } catch (...) { return -1; } json::object resp; try { resp = val.as_object(); } catch (...) { return -1; } if (!resp.contains("result")) return -1; *json_out = g_host->strdup(json::serialize(resp["result"]).c_str()); return 0; } catch (const std::exception& e) { if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] get_hover: %s", e.what()); *json_out = nullptr; return -1; } catch (...) { if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] get_hover: unknown exception"); *json_out = nullptr; return -1; } } // 发送 textDocument/completion 请求并以 JSON 返回补全列表 / Send a textDocument/completion request and return the completion list as JSON. static int g_lsp_impl_get_completion(const char* uri, int line, int col, char** json_out) { if (!g_lsp.running) return -1; if (!uri || !json_out) return -1; try { json::object position; position["line"] = line; position["character"] = col; json::object text_doc; text_doc["uri"] = uri; json::object params; params["textDocument"] = text_doc; params["position"] = position; int req_id = send_request("textDocument/completion", params); std::unique_lock lock(g_lsp.mutex); bool got = g_lsp.cv.wait_for(lock, std::chrono::seconds(10), [req_id]() { return !g_lsp.running || g_lsp.pending_responses.count(req_id) > 0; }); if (!got || !g_lsp.running || g_lsp.pending_responses.count(req_id) == 0) { return -1; } std::string response_body = g_lsp.pending_responses[req_id]; g_lsp.pending_responses.erase(req_id); json::value val; try { val = json::parse(response_body); } catch (...) { return -1; } json::object resp; try { resp = val.as_object(); } catch (...) { return -1; } if (!resp.contains("result")) return -1; *json_out = g_host->strdup(json::serialize(resp["result"]).c_str()); return 0; } catch (const std::exception& e) { if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] get_completion: %s", e.what()); *json_out = nullptr; return -1; } catch (...) { if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] get_completion: unknown exception"); *json_out = nullptr; return -1; } } // ============================================================================ // 服务 vtable / Service vtable // ============================================================================ static dstalk_lsp_service_t g_service_vtable = { &g_lsp_impl_start, &g_lsp_impl_stop, &g_lsp_impl_open_document, &g_lsp_impl_close_document, &g_lsp_impl_get_diagnostics, &g_lsp_impl_get_hover, &g_lsp_impl_get_completion, }; // ============================================================================ // 生命周期回调 / Lifecycle callbacks // ============================================================================ // 插件初始化:保存主机指针并注册 lsp 服务 / Plugin init: store host pointer and register the lsp service. static int on_init(const dstalk_host_api_t* host) { g_host = host; if (g_host) g_host->log(DSTALK_LOG_INFO, "[lsp] initializing LSP service plugin"); return host->register_service("lsp", 1, &g_service_vtable); } // 插件关闭:如果运行中则停止 LSP 服务器,清空主机指针 / Plugin shutdown: stop LSP server if running, null out host pointer. static void on_shutdown() { try { if (g_lsp.running) { g_lsp_impl_stop(); } if (g_host) g_host->log(DSTALK_LOG_INFO, "[lsp] shutdown"); g_host = nullptr; } catch (const std::exception& e) { if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] on_shutdown: %s", e.what()); g_host = nullptr; } catch (...) { if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] on_shutdown: unknown exception"); g_host = nullptr; } } // ============================================================================ // 插件描述符 / Plugin descriptor // ============================================================================ static dstalk_plugin_info_t g_info = { /* .name = */ "lsp", /* .version = */ "1.0.0", /* .description = */ "Language Server Protocol client (subprocess manager) / Language Server Protocol 客户端(子进程管理器)", /* .api_version = */ DSTALK_API_VERSION, /* .dependencies = */ { NULL }, // 无依赖,自行管理子进程 / No dependencies, self-manages subprocess /* .on_init = */ on_init, /* .on_shutdown = */ on_shutdown, /* .on_event = */ nullptr, }; // 必须入口点:返回插件描述符给主机 / Mandatory entry point: returns the plugin descriptor to the host. extern "C" DSTALK_PLUGIN_EXPORT dstalk_plugin_info_t* dstalk_plugin_init(void) { return &g_info; }