Fix lsp bugs

- Fix: Incorrect setting of incremental edits for lsp and more
This commit is contained in:
2025-12-27 09:53:46 +00:00
parent bfaba81317
commit 6108f78be3
8 changed files with 145 additions and 44 deletions

View File

@@ -18,7 +18,6 @@ A TUI IDE.
- `textDocument/foldingRange` - i will never use this for folding but it might be useful for other things.
- `textDocument/rename` & `textDocument/prepareRename` - probably useful
- And a lot more (just go through each for `clangd` and then expand to say `solargraph`).
- Make incremental edits apply. // make a bool field in LSP qhich says if it supports incremental and based on it apply edits
- Make a universal plug for lsp. So focus more on making a general purpose solid communication interface. Instead of something specific.
- With a 4ish pass system. (more like each returned value from the lsp is used in 4 ways)
1. One for stuff like jump to x position. or rename symbol x to y. (stuff that explicitly requires user request to do something)

22
grammar/jsonc.scm Normal file
View File

@@ -0,0 +1,22 @@
;; #D2A6FF #000000 0 0 0 2
(pair
key: (_) @string.special.key)
;; #AAD94C #000000 0 0 0 1
(string) @string
;; #7dcfff #000000 0 0 0 2
(number) @number
;; #F07178 #000000 0 0 0 1
[
(null)
(true)
(false)
] @constant.builtin
;; #7dcfff #000000 0 0 0 2
(escape_sequence) @escape
;; #99ADBF #000000 0 1 0 1
(comment) @comment

View File

@@ -156,7 +156,7 @@ struct Editor {
std::vector<VWarn> warnings;
VAI ai;
std::shared_mutex lsp_mtx;
struct LSPInstance *lsp;
std::shared_ptr<struct LSPInstance> lsp;
int lsp_version = 1;
};

View File

@@ -29,28 +29,33 @@ struct LSPInstance {
int pid{-1};
int stdin_fd{-1};
int stdout_fd{-1};
bool initialized = false;
bool incremental_sync = true;
std::atomic<bool> initialized = false;
std::atomic<bool> exited = false;
bool incremental_sync = false;
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<uint8_t, LSPInstance *> active_lsps;
extern std::unordered_map<uint8_t, std::shared_ptr<LSPInstance>> active_lsps;
void lsp_worker();
void lsp_handle(LSPInstance *lsp, json message);
void lsp_handle(std::shared_ptr<LSPInstance> lsp, json message);
LSPInstance *get_or_init_lsp(uint8_t lsp_id);
std::shared_ptr<LSPInstance> get_or_init_lsp(uint8_t lsp_id);
void close_lsp(uint8_t lsp_id);
void request_add_to_lsp(Language language, Editor *editor);
void open_editor(std::shared_ptr<LSPInstance> lsp,
std::pair<Language, Editor *> entry);
void add_to_lsp(Language language, Editor *editor);
void remove_from_lsp(Editor *editor);
void lsp_send(LSPInstance *lsp, json message, LSPPending *pending);
void lsp_send(std::shared_ptr<LSPInstance> lsp, json message,
LSPPending *pending);
#endif

View File

@@ -155,6 +155,15 @@ static const std::unordered_map<uint8_t, LSP> kLsps = {
"make-language-server",
nullptr,
}}},
{22,
{"sql-language-server",
{
"sql-language-server",
"up",
"--method",
"stdio",
nullptr,
}}},
};
static const std::unordered_map<std::string, Language> kLanguages = {
@@ -170,6 +179,7 @@ static const std::unordered_map<std::string, Language> kLanguages = {
{"html", {"html", LANG(html), 10}},
{"javascript", {"javascript", LANG(javascript), 11}},
{"json", {"json", LANG(json), 6}},
{"jsonc", {"jsonc", LANG(json), 6}},
{"erb", {"erb", LANG(embedded_template), 10}},
{"ruby", {"ruby", LANG(ruby), 3}},
{"lua", {"lua", LANG(lua), 12}},
@@ -181,7 +191,8 @@ static const std::unordered_map<std::string, Language> kLanguages = {
{"nginx", {"nginx", LANG(nginx), 17}},
{"toml", {"toml", LANG(toml), 18}},
{"yaml", {"yaml", LANG(yaml), 19}},
{"sql", {"sql", LANG(sql), 20}},
{"sql", {"sql", LANG(sql), 20}}, // Can use `22` for more accuracy but need
// config to connect to database
{"make", {"make", LANG(make), 21}},
{"gdscript", {"gdscript", LANG(gdscript)}}, // TODO: connect to godot
{"diff", {"diff", LANG(diff)}},
@@ -212,7 +223,7 @@ static const std::unordered_map<std::string, std::string> kExtToLang = {
{"js", "javascript"},
{"jsx", "javascript"},
{"json", "json"},
{"jsonc", "json"},
{"jsonc", "jsonc"},
{"lua", "lua"},
{"mk", "make"},
{"makefile", "make"},

View File

@@ -12,8 +12,9 @@ template <typename T> struct Queue {
std::lock_guard<std::mutex> lock(m);
q.push(val);
}
T front() {
std::lock_guard<std::mutex> lock(m);
std::optional<T> front() {
if (q.empty())
return std::nullopt;
return q.front();
}
bool pop(T &val) {

View File

@@ -1,5 +1,6 @@
#include "../include/lsp.h"
#include "../include/maps.h"
#include <cmath>
#include <fcntl.h>
#include <signal.h>
#include <sys/poll.h>
@@ -8,11 +9,11 @@
#include <unistd.h>
std::shared_mutex active_lsps_mtx;
std::unordered_map<uint8_t, LSPInstance *> active_lsps;
std::unordered_map<uint8_t, std::shared_ptr<LSPInstance>> active_lsps;
Queue<LSPOpenRequest> lsp_open_queue;
static bool init_lsp(LSPInstance *lsp) {
static bool init_lsp(std::shared_ptr<LSPInstance> lsp) {
log("initializing %s\n", lsp->lsp->command);
int in_pipe[2];
int out_pipe[2];
@@ -47,19 +48,17 @@ static bool init_lsp(LSPInstance *lsp) {
return true;
}
LSPInstance *get_or_init_lsp(uint8_t lsp_id) {
std::shared_ptr<LSPInstance> get_or_init_lsp(uint8_t lsp_id) {
std::unique_lock lock(active_lsps_mtx);
auto it = active_lsps.find(lsp_id);
if (it == active_lsps.end()) {
auto map_it = kLsps.find(lsp_id);
if (map_it == kLsps.end())
return nullptr;
LSPInstance *lsp = new LSPInstance();
std::shared_ptr<LSPInstance> lsp = std::make_shared<LSPInstance>();
lsp->lsp = &map_it->second;
if (!init_lsp(lsp)) {
delete lsp;
if (!init_lsp(lsp))
return nullptr;
}
log("starting %s\n", lsp->lsp->command);
LSPPending *pending = new LSPPending();
pending->method = "initialize";
@@ -67,18 +66,29 @@ LSPInstance *get_or_init_lsp(uint8_t lsp_id) {
pending->callback = [lsp](Editor *, std::string, json msg) {
if (msg.contains("result") && msg["result"].contains("capabilities")) {
auto &caps = msg["result"]["capabilities"];
if (caps.contains("textDocumentSync") &&
caps["textDocumentSync"].contains("change")) {
int change_type = caps["textDocumentSync"]["change"];
lsp->incremental_sync = (change_type == 2);
if (caps.contains("textDocumentSync")) {
auto &sync = caps["textDocumentSync"];
if (sync.is_number()) {
int change_type = sync.get<int>();
lsp->incremental_sync = (change_type == 2);
} else if (sync.is_object() && sync.contains("change")) {
int change_type = sync["change"].get<int>();
lsp->incremental_sync = (change_type == 2);
}
}
}
log("incremental_sync %d\n", lsp->incremental_sync);
lsp->initialized = true;
json initialized = {{"jsonrpc", "2.0"},
{"method", "initialized"},
{"params", json::object()}};
lsp_send(lsp, initialized, nullptr);
log("initialized %s\n", lsp->lsp->command);
while (!lsp->open_queue.empty()) {
std::pair<Language, Editor *> request;
lsp->open_queue.pop(request);
open_editor(lsp, request);
}
};
json init_message = {
{"jsonrpc", "2.0"},
@@ -96,7 +106,8 @@ LSPInstance *get_or_init_lsp(uint8_t lsp_id) {
return it->second;
}
void lsp_send(LSPInstance *lsp, json message, LSPPending *pending) {
void lsp_send(std::shared_ptr<LSPInstance> lsp, json message,
LSPPending *pending) {
if (!lsp || lsp->stdin_fd == -1)
return;
std::unique_lock lock(lsp->mtx);
@@ -113,8 +124,10 @@ void close_lsp(uint8_t lsp_id) {
auto it = active_lsps.find(lsp_id);
if (it == active_lsps.end())
return;
LSPInstance *lsp = it->second;
std::shared_ptr<LSPInstance> lsp = it->second;
active_lsps_lock.unlock();
lsp->exited = true;
lsp->initialized = false;
LSPPending *shutdown_pending = new LSPPending();
shutdown_pending->method = "shutdown";
shutdown_pending->callback = [lsp, lsp_id](Editor *, std::string, json) {
@@ -127,18 +140,17 @@ void close_lsp(uint8_t lsp_id) {
std::this_thread::sleep_for(100ms);
std::unique_lock active_lsps_lock(active_lsps_mtx);
std::unique_lock lock(lsp->mtx);
if (kill(lsp->pid, 0) == 0)
if (lsp->pid != -1 && kill(lsp->pid, 0) == 0)
kill(lsp->pid, SIGKILL);
waitpid(lsp->pid, nullptr, 0);
close(lsp->stdin_fd);
close(lsp->stdout_fd);
while (!lsp->outbox.empty())
lsp->outbox.pop();
while (!lsp->inbox.empty())
lsp->inbox.pop();
for (auto &kv : lsp->pending)
delete kv.second;
delete lsp;
for (auto &editor : lsp->editors) {
std::unique_lock editor_lock(editor->lsp_mtx);
editor->lsp = nullptr;
}
active_lsps.erase(lsp_id);
});
t.detach();
@@ -177,7 +189,8 @@ static std::optional<json> read_lsp_message(int fd) {
return json::parse(body);
}
static Editor *editor_for_uri(LSPInstance *lsp, std::string uri) {
static Editor *editor_for_uri(std::shared_ptr<LSPInstance> lsp,
std::string uri) {
if (uri.empty())
return nullptr;
for (auto &editor : lsp->editors)
@@ -186,20 +199,45 @@ static Editor *editor_for_uri(LSPInstance *lsp, std::string uri) {
return nullptr;
}
static void clean_lsp(std::shared_ptr<LSPInstance> lsp, uint8_t lsp_id) {
log("cleaning up lsp %d\n", 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);
}
void lsp_worker() {
LSPOpenRequest request;
while (lsp_open_queue.pop(request))
add_to_lsp(request.language, request.editor);
std::shared_lock active_lsps_lock(active_lsps_mtx);
std::unique_lock active_lsps_lock(active_lsps_mtx);
for (auto &kv : active_lsps) {
LSPInstance *lsp = kv.second;
std::shared_ptr<LSPInstance> lsp = kv.second;
std::unique_lock lock(lsp->mtx);
int status;
pid_t res = waitpid(lsp->pid, &status, WNOHANG);
if (res == lsp->pid) {
clean_lsp(lsp, kv.first);
return;
}
while (!lsp->outbox.empty()) {
json message;
message = lsp->outbox.front();
json message = lsp->outbox.front();
std::string m = message.value("method", "");
if (lsp->exited) {
if (m != "exit" && m != "shutdown") {
lsp->outbox.pop(message);
continue;
}
}
if (!lsp->initialized) {
std::string m = message.value("method", "");
if (m != "initialize")
if (m != "initialize" && m != "exit" && m != "shutdown")
break;
}
lsp->outbox.pop(message);
@@ -210,6 +248,12 @@ void lsp_worker() {
const char *ptr = out.data();
size_t remaining = out.size();
while (remaining > 0) {
int status;
pid_t res = waitpid(lsp->pid, &status, WNOHANG);
if (res == lsp->pid) {
clean_lsp(lsp, kv.first);
return;
}
ssize_t written = write(lsp->stdin_fd, ptr, remaining);
if (written == 0)
break;
@@ -217,15 +261,25 @@ void lsp_worker() {
if (errno == EINTR)
continue;
perror("write");
break;
clean_lsp(lsp, kv.first);
return;
} else {
ptr += written;
remaining -= written;
}
}
}
pollfd pfd{lsp->stdout_fd, POLLIN, 0};
while (poll(&pfd, 1, 0) > 0) {
pollfd pfd{lsp->stdout_fd, POLLIN | POLLHUP | POLLERR, 0};
int r = poll(&pfd, 1, 0);
if (r > 0 && pfd.revents & (POLLHUP | POLLERR)) {
clean_lsp(lsp, kv.first);
return;
}
while ((r = poll(&pfd, 1, 0) > 0)) {
if (r > 0 && pfd.revents & (POLLHUP | POLLERR)) {
clean_lsp(lsp, kv.first);
return;
}
auto msg = read_lsp_message(lsp->stdout_fd);
if (!msg)
break;
@@ -268,14 +322,21 @@ void request_add_to_lsp(Language language, Editor *editor) {
}
void add_to_lsp(Language language, Editor *editor) {
LSPInstance *lsp = get_or_init_lsp(language.lsp_id);
std::shared_ptr<LSPInstance> lsp = get_or_init_lsp(language.lsp_id);
if (!lsp)
return;
std::unique_lock lock(lsp->mtx);
if (editor->lsp == lsp)
return;
lsp->editors.push_back(editor);
lsp->open_queue.push({language, editor});
lock.unlock();
}
void open_editor(std::shared_ptr<LSPInstance> lsp,
std::pair<Language, Editor *> entry) {
Language language = entry.first;
Editor *editor = entry.second;
std::unique_lock lock2(editor->lsp_mtx);
editor->lsp = lsp;
lock2.unlock();
@@ -295,7 +356,7 @@ void add_to_lsp(Language language, Editor *editor) {
lsp_send(lsp, message, nullptr);
}
static uint8_t find_lsp_id(LSPInstance *needle) {
static uint8_t find_lsp_id(std::shared_ptr<LSPInstance> needle) {
for (const auto &[id, lsp] : active_lsps)
if (lsp == needle)
return id;
@@ -323,7 +384,7 @@ void remove_from_lsp(Editor *editor) {
close_lsp(lsp_id);
}
void lsp_handle(LSPInstance *, json message) {
void lsp_handle(std::shared_ptr<LSPInstance>, json message) {
std::string method = message.value("method", "");
if (method == "window/showMessage") {
if (message.contains("params")) {

View File

@@ -186,6 +186,7 @@ uint32_t count_clusters(const char *line, size_t len, size_t from, size_t to) {
}
void log(const char *fmt, ...) {
#if defined(__GNUC__) && !defined(__clang__)
FILE *fp = fopen("/tmp/log.txt", "a");
if (!fp)
return;
@@ -195,6 +196,7 @@ void log(const char *fmt, ...) {
va_end(args);
fputc('\n', fp);
fclose(fp);
#endif
}
char *load_file(const char *path, uint32_t *out_len) {