#include "lsp/lsp.h" static bool init_lsp(std::shared_ptr lsp) { int in_pipe[2]; int out_pipe[2]; if (pipe(in_pipe) == -1 || pipe(out_pipe) == -1) { perror("pipe"); return false; } pid_t pid = fork(); if (pid == -1) { perror("fork"); return false; } if (pid == 0) { dup2(in_pipe[0], STDIN_FILENO); dup2(out_pipe[1], STDOUT_FILENO); int devnull = open("/dev/null", O_WRONLY); if (devnull >= 0) { dup2(devnull, STDERR_FILENO); close(devnull); } close(in_pipe[0]); close(in_pipe[1]); close(out_pipe[0]); close(out_pipe[1]); std::vector argv; argv.push_back(const_cast(lsp->lsp->command.c_str())); for (auto &arg : lsp->lsp->args) argv.push_back(const_cast(arg.c_str())); argv.push_back(nullptr); execvp(lsp->lsp->command.c_str(), argv.data()); perror("execvp"); _exit(127); } lsp->pid = pid; lsp->stdin_fd = in_pipe[1]; lsp->stdout_fd = out_pipe[0]; close(in_pipe[0]); close(out_pipe[1]); return true; } std::shared_ptr get_or_init_lsp(std::string lsp_id) { std::unique_lock lock(active_lsps_mtx); auto it = active_lsps.find(lsp_id); if (it == active_lsps.end()) { auto map_it = lsps.find(lsp_id); if (map_it == lsps.end()) return nullptr; std::shared_ptr lsp = std::make_shared(); lsp->lsp = &map_it->second; if (!init_lsp(lsp)) return nullptr; LSPPending *pending = new LSPPending(); pending->editor = nullptr; pending->callback = [lsp, lsp_id](Editor *, const json &msg) { if (msg.contains("result") && msg["result"].contains("capabilities")) { auto &caps = msg["result"]["capabilities"]; // if (caps.contains("positionEncoding")) { // std::string s = caps["positionEncoding"].get(); // if (s == "utf-8") // lsp->is_utf8 = true; // log("Lsp name: %s, supports: %s", lsp->lsp->command.c_str(), // s.c_str()); // } if (caps.contains("textDocumentSync")) { auto &sync = caps["textDocumentSync"]; if (sync.is_number()) { int change_type = sync.get(); lsp->incremental_sync = (change_type == 2); } else if (sync.is_object() && sync.contains("change")) { int change_type = sync["change"].get(); lsp->incremental_sync = (change_type == 2); } } lsp->allow_formatting = caps.value("documentFormattingProvider", false); if (lsp_id != "lua-language-server" /* Lua ls gives terrible ontype formatting so disable */ && caps.contains("documentOnTypeFormattingProvider")) { auto &fmt = caps["documentOnTypeFormattingProvider"]; if (fmt.is_object()) { if (fmt.contains("firstTriggerCharacter")) { std::string s = fmt["firstTriggerCharacter"].get(); if (s.size() == 1) lsp->format_chars.push_back(s[0]); } if (fmt.contains("moreTriggerCharacter")) { for (auto &c : fmt["moreTriggerCharacter"]) { std::string s = c.get(); if (s.size() == 1) lsp->format_chars.push_back(s[0]); } } lsp->allow_formatting_on_type = true; } else if (fmt.is_boolean()) { lsp->allow_formatting_on_type = fmt.get(); } } if (caps.contains("hoverProvider")) { auto &hover = caps["hoverProvider"]; lsp->allow_hover = hover.is_boolean() ? hover.get() : hover.is_object(); } else { lsp->allow_hover = false; } if (caps.contains("completionProvider")) { lsp->allow_completion = true; if (caps["completionProvider"].contains("resolveProvider")) lsp->allow_resolve = caps["completionProvider"]["resolveProvider"].get(); if (caps["completionProvider"].contains("triggerCharacters")) { auto &chars = caps["completionProvider"]["triggerCharacters"]; if (chars.is_array()) { for (auto &c : chars) { std::string str = c.get(); if (str.size() != 1) continue; lsp->trigger_chars.push_back(str[0]); } } } if (caps["completionProvider"].contains("allCommitCharacters")) { auto &chars = caps["completionProvider"]["allCommitCharacters"]; if (chars.is_array()) { for (auto &c : chars) { std::string str = c.get(); if (str.size() != 1) continue; lsp->end_chars.push_back(str[0]); } } } } } lsp->initialized = true; json initialized = {{"jsonrpc", "2.0"}, {"method", "initialized"}, {"params", json::object()}}; lsp_send(lsp, initialized, nullptr); }; json init_message = { {"jsonrpc", "2.0"}, {"method", "initialize"}, {"params", {{"processId", getpid()}, {"rootUri", "file://" + percent_encode(path_abs("."))}, {"capabilities", client_capabilities}}}}; lsp_send(lsp, init_message, pending); active_lsps[lsp_id] = lsp; return lsp; } return it->second; } void close_lsp(std::string lsp_id) { std::shared_ptr lsp; { std::shared_lock lock(active_lsps_mtx); auto it = active_lsps.find(lsp_id); if (it == active_lsps.end()) return; lsp = it->second; } if (!lsp || lsp->pid == -1 || lsp->exited) return; lsp->exited = true; lsp->initialized = false; auto send_raw = [&](const json &msg) { std::string payload = msg.dump(); std::string header = "Content-Length: " + std::to_string(payload.size()) + "\r\n\r\n"; std::string out = header + payload; const char *ptr = out.data(); size_t remaining = out.size(); while (remaining > 0) { ssize_t n = write(lsp->stdin_fd, ptr, remaining); if (n <= 0) { if (errno == EINTR) continue; break; } ptr += n; remaining -= n; } }; json shutdown = {{"jsonrpc", "2.0"}, {"id", 1}, {"method", "shutdown"}}; send_raw(shutdown); { pollfd pfd{lsp->stdout_fd, POLLIN, 0}; int timeout_ms = 300; if (poll(&pfd, 1, timeout_ms) > 0) { auto msg = read_lsp_message(lsp->stdout_fd); (void)msg; } } json exit_msg = {{"jsonrpc", "2.0"}, {"method", "exit"}}; send_raw(exit_msg); const int max_wait_ms = 500; int waited = 0; while (waited < max_wait_ms) { int status; pid_t res = waitpid(lsp->pid, &status, WNOHANG); if (res == lsp->pid) break; std::this_thread::sleep_for(std::chrono::milliseconds(10)); waited += 10; } if (kill(lsp->pid, 0) == 0) { kill(lsp->pid, SIGKILL); waitpid(lsp->pid, nullptr, 0); } close(lsp->stdin_fd); close(lsp->stdout_fd); { std::unique_lock lock(lsp->mtx); for (auto &kv : lsp->pending) delete kv.second; lsp->pending.clear(); } for (auto &editor : lsp->editors) { std::unique_lock editor_lock(editor->lsp_mtx); editor->lsp = nullptr; } { std::unique_lock lock(active_lsps_mtx); active_lsps.erase(lsp_id); } } void clean_lsp(std::shared_ptr lsp, std::string lsp_id) { for (auto &kv : lsp->pending) delete kv.second; lsp->pid = -1; close(lsp->stdin_fd); close(lsp->stdout_fd); for (auto &editor : lsp->editors) { std::unique_lock editor_lock(editor->lsp_mtx); editor->lsp = nullptr; } active_lsps.erase(lsp_id); }