Switch to OOP style code
This commit is contained in:
@@ -5,56 +5,20 @@
|
||||
#include "pch.h"
|
||||
#include "utils/utils.h"
|
||||
|
||||
struct LSPPending {
|
||||
Editor *editor = nullptr;
|
||||
std::function<void(Editor *, const json &)> callback;
|
||||
};
|
||||
#define LSP_TIMEOUT 3000
|
||||
|
||||
// TODO: Defer any editor mutation to main thread to get rid of
|
||||
// all mutex locks on the editor rope.
|
||||
// struct LSPPendingResponse {
|
||||
// LSPPending *pending = nullptr;
|
||||
// json message;
|
||||
// };
|
||||
|
||||
struct LSPOpenRequest {
|
||||
Language language;
|
||||
Editor *editor;
|
||||
};
|
||||
|
||||
struct LSPInstance {
|
||||
std::shared_mutex mtx;
|
||||
const LSP *lsp;
|
||||
std::string root_dir;
|
||||
int pid{-1};
|
||||
int stdin_fd{-1};
|
||||
int stdout_fd{-1};
|
||||
std::atomic<bool> initialized = false;
|
||||
std::atomic<bool> exited = false;
|
||||
bool incremental_sync = false;
|
||||
bool allow_hover = false;
|
||||
bool allow_completion = false;
|
||||
bool allow_resolve = false;
|
||||
bool allow_formatting = false;
|
||||
bool allow_formatting_on_type = false;
|
||||
bool is_utf8 = false;
|
||||
std::vector<char> format_chars;
|
||||
std::vector<char> trigger_chars;
|
||||
std::vector<char> end_chars;
|
||||
uint32_t last_id = 0;
|
||||
Queue<json> inbox;
|
||||
Queue<json> outbox;
|
||||
Queue<std::pair<Language, Editor *>> open_queue;
|
||||
std::unordered_map<uint32_t, LSPPending *> pending;
|
||||
std::vector<Editor *> editors;
|
||||
};
|
||||
|
||||
extern std::shared_mutex active_lsps_mtx;
|
||||
extern std::unordered_map<std::string, std::shared_ptr<LSPInstance>>
|
||||
namespace lsp {
|
||||
extern std::mutex lsp_mutex;
|
||||
extern std::unordered_map<std::string, std::unique_ptr<LSPInstance>>
|
||||
active_lsps;
|
||||
extern Queue<LSPOpenRequest> lsp_open_queue;
|
||||
extern Queue<std::string> need_opening;
|
||||
extern std::unordered_set<std::string> opened;
|
||||
extern std::vector<Editor *> new_editors;
|
||||
} // namespace lsp
|
||||
|
||||
static json client_capabilities = {
|
||||
void lsp_worker();
|
||||
|
||||
static const json client_capabilities = {
|
||||
{"general", {{"positionEncodings", {"utf-16"}}}},
|
||||
{"textDocument",
|
||||
{{"publishDiagnostics", {{"relatedInformation", true}}},
|
||||
@@ -78,20 +42,403 @@ static json client_capabilities = {
|
||||
{"contextSupport", true},
|
||||
{"insertTextMode", 1}}}}}};
|
||||
|
||||
void lsp_send(std::shared_ptr<LSPInstance> lsp, json message,
|
||||
LSPPending *pending);
|
||||
void lsp_worker();
|
||||
struct LSPMessage {
|
||||
Editor *editor = nullptr;
|
||||
json message;
|
||||
std::function<void(const LSPMessage &)> callback;
|
||||
};
|
||||
|
||||
std::shared_ptr<LSPInstance> get_or_init_lsp(std::string lsp_id);
|
||||
void clean_lsp(std::shared_ptr<LSPInstance> lsp, std::string lsp_id);
|
||||
void close_lsp(std::string lsp_id);
|
||||
std::optional<json> read_lsp_message(int fd);
|
||||
struct LSPInstance {
|
||||
const LSP *lsp_info;
|
||||
std::string root_dir;
|
||||
int pid{-1};
|
||||
int stdin_fd{-1};
|
||||
int stdout_fd{-1};
|
||||
std::atomic<bool> initialized = false;
|
||||
std::atomic<bool> exited = false;
|
||||
bool incremental_sync = false;
|
||||
bool allow_hover = false;
|
||||
bool allow_completion = false;
|
||||
bool allow_resolve = false;
|
||||
bool allow_formatting = false;
|
||||
bool allow_formatting_on_type = false;
|
||||
bool is_utf8 = false;
|
||||
std::vector<char> format_chars;
|
||||
std::vector<char> trigger_chars;
|
||||
std::vector<char> end_chars;
|
||||
uint32_t last_id = 0;
|
||||
Queue<json> inbox;
|
||||
Queue<json> outbox;
|
||||
std::unordered_map<uint32_t, std::unique_ptr<LSPMessage>> pending;
|
||||
std::vector<std::unique_ptr<LSPMessage>> lsp_response_queue;
|
||||
std::vector<Editor *> editors;
|
||||
|
||||
void open_editor(std::shared_ptr<LSPInstance> lsp,
|
||||
std::pair<Language, Editor *> entry);
|
||||
void request_add_to_lsp(Language language, Editor *editor);
|
||||
void add_to_lsp(Language language, Editor *editor);
|
||||
void remove_from_lsp(Editor *editor);
|
||||
void lsp_handle(std::shared_ptr<LSPInstance> lsp, json message);
|
||||
LSPInstance(std::string lsp_id) {
|
||||
lsp_info = &lsps[lsp_id];
|
||||
if (!init_process()) {
|
||||
exited = true;
|
||||
return;
|
||||
}
|
||||
json initialize_message = {
|
||||
{"jsonrpc", "2.0"},
|
||||
{"id", ++last_id},
|
||||
{"method", "initialize"},
|
||||
{"params",
|
||||
{{"processId", getpid()},
|
||||
{"rootUri", "file://" + percent_encode(path_abs("."))},
|
||||
{"capabilities", client_capabilities}}}};
|
||||
send_raw(initialize_message);
|
||||
pollfd pfd{stdout_fd, POLLIN, 0};
|
||||
poll(&pfd, 1, LSP_TIMEOUT);
|
||||
if (!(pfd.revents & POLLIN)) {
|
||||
exited = true;
|
||||
return;
|
||||
}
|
||||
json response = *read_lsp_message();
|
||||
log("Lsp response: %s", response.dump().c_str());
|
||||
if (response.contains("result") &&
|
||||
response["result"].contains("capabilities")) {
|
||||
auto &caps = response["result"]["capabilities"];
|
||||
// if (caps.contains("positionEncoding")) {
|
||||
// std::string s = caps["positionEncoding"].get<std::string>();
|
||||
// if (s == "utf-8")
|
||||
// is_utf8 = true;
|
||||
// log("Lsp name: %s, supports: %s", lsp->command.c_str(),
|
||||
// s.c_str());
|
||||
// }
|
||||
if (caps.contains("textDocumentSync")) {
|
||||
auto &sync = caps["textDocumentSync"];
|
||||
if (sync.is_number()) {
|
||||
int change_type = sync.get<int>();
|
||||
incremental_sync = (change_type == 2);
|
||||
} else if (sync.is_object() && sync.contains("change")) {
|
||||
int change_type = sync["change"].get<int>();
|
||||
incremental_sync = (change_type == 2);
|
||||
}
|
||||
}
|
||||
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<std::string>();
|
||||
if (s.size() == 1)
|
||||
format_chars.push_back(s[0]);
|
||||
}
|
||||
if (fmt.contains("moreTriggerCharacter")) {
|
||||
for (auto &c : fmt["moreTriggerCharacter"]) {
|
||||
std::string s = c.get<std::string>();
|
||||
if (s.size() == 1)
|
||||
format_chars.push_back(s[0]);
|
||||
}
|
||||
}
|
||||
allow_formatting_on_type = true;
|
||||
} else if (fmt.is_boolean()) {
|
||||
allow_formatting_on_type = fmt.get<bool>();
|
||||
}
|
||||
}
|
||||
if (caps.contains("hoverProvider")) {
|
||||
auto &hover = caps["hoverProvider"];
|
||||
allow_hover =
|
||||
hover.is_boolean() ? hover.get<bool>() : hover.is_object();
|
||||
} else {
|
||||
allow_hover = false;
|
||||
}
|
||||
if (caps.contains("completionProvider")) {
|
||||
allow_completion = true;
|
||||
if (caps["completionProvider"].contains("resolveProvider"))
|
||||
allow_resolve =
|
||||
caps["completionProvider"]["resolveProvider"].get<bool>();
|
||||
if (caps["completionProvider"].contains("triggerCharacters")) {
|
||||
auto &chars = caps["completionProvider"]["triggerCharacters"];
|
||||
if (chars.is_array()) {
|
||||
for (auto &c : chars) {
|
||||
std::string str = c.get<std::string>();
|
||||
if (str.size() != 1)
|
||||
continue;
|
||||
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<std::string>();
|
||||
if (str.size() != 1)
|
||||
continue;
|
||||
end_chars.push_back(str[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
initialized = true;
|
||||
json initialized_message = {{"jsonrpc", "2.0"},
|
||||
{"method", "initialized"},
|
||||
{"params", json::object()}};
|
||||
send_raw(initialized_message);
|
||||
}
|
||||
~LSPInstance() {
|
||||
for (auto &ed : editors)
|
||||
ed->lsp.store(nullptr);
|
||||
initialized = false;
|
||||
exited = true;
|
||||
if (pid == -1)
|
||||
return;
|
||||
json shutdown = {{"id", ++last_id}, {"method", "shutdown"}};
|
||||
send_raw(shutdown);
|
||||
pollfd pfd{stdout_fd, POLLIN, 0};
|
||||
poll(&pfd, 1, LSP_TIMEOUT);
|
||||
json exit_msg = {{"method", "exit"}};
|
||||
send_raw(exit_msg);
|
||||
int waited = 0;
|
||||
while (waited < LSP_TIMEOUT) {
|
||||
int status;
|
||||
pid_t res = waitpid(pid, &status, WNOHANG);
|
||||
if (res == pid)
|
||||
break;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
waited += 10;
|
||||
}
|
||||
if (kill(pid, 0) == 0) {
|
||||
kill(pid, SIGKILL);
|
||||
waitpid(pid, nullptr, 0);
|
||||
}
|
||||
pid = -1;
|
||||
close(stdin_fd);
|
||||
close(stdout_fd);
|
||||
}
|
||||
bool init_process() {
|
||||
int in_pipe[2];
|
||||
int out_pipe[2];
|
||||
if (pipe(in_pipe) == -1 || pipe(out_pipe) == -1) {
|
||||
perror("pipe");
|
||||
return false;
|
||||
}
|
||||
pid_t pid_tmp = fork();
|
||||
if (pid_tmp == -1) {
|
||||
perror("fork");
|
||||
return false;
|
||||
}
|
||||
if (pid_tmp == 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<char *> argv;
|
||||
argv.push_back(const_cast<char *>(lsp_info->command.c_str()));
|
||||
for (auto &arg : lsp_info->args)
|
||||
argv.push_back(const_cast<char *>(arg.c_str()));
|
||||
argv.push_back(nullptr);
|
||||
execvp(lsp_info->command.c_str(), argv.data());
|
||||
perror("execvp");
|
||||
_exit(127);
|
||||
}
|
||||
pid = pid_tmp;
|
||||
stdin_fd = in_pipe[1];
|
||||
stdout_fd = out_pipe[0];
|
||||
close(in_pipe[0]);
|
||||
close(out_pipe[1]);
|
||||
return true;
|
||||
}
|
||||
void add(Editor *ed) {
|
||||
std::unique_lock lock(lsp::lsp_mutex);
|
||||
editors.push_back(ed);
|
||||
lock.unlock();
|
||||
ed->lsp.store(this);
|
||||
char *buf = read(ed->root, 0, ed->root->char_count);
|
||||
std::string text(buf);
|
||||
free(buf);
|
||||
auto message = std::make_unique<LSPMessage>();
|
||||
message->message = {{"method", "textDocument/didOpen"},
|
||||
{"params",
|
||||
{{"textDocument",
|
||||
{{"uri", ed->uri},
|
||||
{"languageId", ed->lang.name},
|
||||
{"version", 1},
|
||||
{"text", text}}}}}};
|
||||
send(std::move(message));
|
||||
}
|
||||
void remove(Editor *ed) {
|
||||
std::unique_lock lock(lsp::lsp_mutex);
|
||||
editors.erase(std::remove(editors.begin(), editors.end(), ed),
|
||||
editors.end());
|
||||
lock.unlock();
|
||||
auto message = std::make_unique<LSPMessage>();
|
||||
message->message = {{"method", "textDocument/didClose"},
|
||||
{"params", {{"textDocument", {{"uri", ed->uri}}}}}};
|
||||
send(std::move(message));
|
||||
}
|
||||
void work() {
|
||||
if (exited)
|
||||
return;
|
||||
int status;
|
||||
pid_t res = waitpid(pid, &status, WNOHANG);
|
||||
if (res == pid) {
|
||||
exited = true;
|
||||
pid = -1;
|
||||
return;
|
||||
}
|
||||
while (!outbox.empty()) {
|
||||
json message = outbox.front();
|
||||
std::string m = message.value("method", "");
|
||||
outbox.pop();
|
||||
send_raw(message);
|
||||
}
|
||||
pollfd pfd{stdout_fd, POLLIN | POLLHUP | POLLERR, 0};
|
||||
int r = poll(&pfd, 1, 0);
|
||||
if (r > 0 && pfd.revents & (POLLHUP | POLLERR)) {
|
||||
exited = true;
|
||||
pid = -1;
|
||||
return;
|
||||
}
|
||||
while ((r = poll(&pfd, 1, 0)) > 0) {
|
||||
if (pfd.revents & (POLLHUP | POLLERR)) {
|
||||
exited = true;
|
||||
pid = -1;
|
||||
return;
|
||||
}
|
||||
auto msg = read_lsp_message();
|
||||
if (!msg)
|
||||
break;
|
||||
if (msg->contains("id")) {
|
||||
uint32_t id = msg->at("id").get<uint32_t>();
|
||||
auto it = pending.find(id);
|
||||
if (it != pending.end()) {
|
||||
if (it->second->editor) {
|
||||
it->second->message = *msg;
|
||||
lsp_response_queue.push_back(std::move(it->second));
|
||||
} else {
|
||||
auto message = *std::move(it->second);
|
||||
message.message = *msg;
|
||||
message.callback(message);
|
||||
}
|
||||
pending.erase(it);
|
||||
}
|
||||
} else if (msg->contains("method")) {
|
||||
std::string uri;
|
||||
if (msg->contains("params")) {
|
||||
auto &p = (*msg)["params"];
|
||||
if (p.contains("textDocument") && p["textDocument"].contains("uri"))
|
||||
uri = p["textDocument"]["uri"].get<std::string>();
|
||||
else if (p.contains("uri"))
|
||||
uri = p["uri"].get<std::string>();
|
||||
}
|
||||
Editor *ed = resolve_uri(uri);
|
||||
auto response = std::make_unique<LSPMessage>();
|
||||
response->editor = ed;
|
||||
response->message = *msg;
|
||||
response->callback = editor_handle_wrapper;
|
||||
if (ed)
|
||||
lsp_response_queue.push_back(std::move(response));
|
||||
else
|
||||
lsp_handle(*msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
inline static void editor_handle_wrapper(const LSPMessage &message) {
|
||||
message.editor->lsp_handle(message.message);
|
||||
}
|
||||
void callbacks() {
|
||||
for (auto &message : lsp_response_queue)
|
||||
message->callback(*message);
|
||||
lsp_response_queue.clear();
|
||||
}
|
||||
inline void 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(stdin_fd, ptr, remaining);
|
||||
if (n <= 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
ptr += n;
|
||||
remaining -= n;
|
||||
}
|
||||
};
|
||||
inline std::optional<json> read_lsp_message() {
|
||||
std::string header;
|
||||
char c;
|
||||
while (true) {
|
||||
ssize_t n = read(stdout_fd, &c, 1);
|
||||
if (n <= 0)
|
||||
return std::nullopt;
|
||||
header.push_back(c);
|
||||
if (header.size() >= 4 && header.substr(header.size() - 4) == "\r\n\r\n")
|
||||
break;
|
||||
}
|
||||
size_t pos = header.find("Content-Length:");
|
||||
if (pos == std::string::npos)
|
||||
return std::nullopt;
|
||||
pos += strlen("Content-Length:");
|
||||
while (pos < header.size() && std::isspace(header[pos]))
|
||||
pos++;
|
||||
size_t end = pos;
|
||||
while (end < header.size() && std::isdigit(header[end]))
|
||||
end++;
|
||||
size_t len = std::stoul(header.substr(pos, end - pos));
|
||||
std::string body(len, '\0');
|
||||
size_t got = 0;
|
||||
while (got < len) {
|
||||
ssize_t n = read(stdout_fd, &body[got], len - got);
|
||||
if (n <= 0)
|
||||
return std::nullopt;
|
||||
got += n;
|
||||
}
|
||||
return json::parse(body);
|
||||
}
|
||||
inline Editor *resolve_uri(std::string uri) {
|
||||
if (uri.empty())
|
||||
return nullptr;
|
||||
for (auto &editor : editors)
|
||||
if (editor->uri == uri)
|
||||
return editor;
|
||||
return nullptr;
|
||||
}
|
||||
inline void lsp_handle(json &message) {
|
||||
std::string method = message.value("method", "");
|
||||
if (method == "window/showMessage") {
|
||||
if (message.contains("params")) {
|
||||
auto &p = message["params"];
|
||||
if (p.contains("message"))
|
||||
log("%s\n", p["message"].get<std::string>().c_str());
|
||||
}
|
||||
} else if (method == "window/logMessage") {
|
||||
if (message.contains("params")) {
|
||||
auto &p = message["params"];
|
||||
if (p.contains("message"))
|
||||
log("%s\n", p["message"].get<std::string>().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
void send(std::unique_ptr<LSPMessage> message) {
|
||||
if (pid == -1)
|
||||
return;
|
||||
message->message["jsonrpc"] = "2.0";
|
||||
if (message->callback)
|
||||
message->message["id"] = ++last_id;
|
||||
outbox.push(message->message);
|
||||
if (!message->callback)
|
||||
return;
|
||||
std::lock_guard lock(lsp::lsp_mutex);
|
||||
pending[last_id] = std::move(message);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user