summaryrefslogtreecommitdiff
path: root/lsp.c
diff options
context:
space:
mode:
Diffstat (limited to 'lsp.c')
-rw-r--r--lsp.c801
1 files changed, 801 insertions, 0 deletions
diff --git a/lsp.c b/lsp.c
new file mode 100644
index 0000000..61ba014
--- /dev/null
+++ b/lsp.c
@@ -0,0 +1,801 @@
+// @TODO:
+// - make json_object/array_get take value not pointer
+// - documentation
+// - make sure offsets are utf-16!
+// - maximum queue size for requests/responses just in case?
+
+typedef enum {
+ LSP_REQUEST,
+ LSP_RESPONSE
+} LSPMessageType;
+
+typedef enum {
+ LSP_NONE,
+
+ // client-to-server
+ LSP_INITIALIZE,
+ LSP_INITIALIZED,
+ LSP_OPEN,
+ LSP_COMPLETION,
+ LSP_SHUTDOWN,
+ LSP_EXIT,
+
+ // server-to-client
+ LSP_SHOW_MESSAGE,
+ LSP_LOG_MESSAGE
+} LSPRequestType;
+
+typedef struct {
+ // buffer language
+ Language language;
+ // freed by lsp_request_free
+ char *filename;
+ // freed by lsp_request_free
+ char *file_contents;
+} LSPRequestOpen;
+
+typedef enum {
+ ERROR = 1,
+ WARNING = 2,
+ INFO = 3,
+ LOG = 4
+} LSPWindowMessageType;
+
+typedef struct {
+ LSPWindowMessageType type;
+ // freed by lsp_request_free
+ char *message;
+} LSPRequestMessage;
+
+typedef struct {
+ // freed by lsp_request_free
+ char *path;
+ u32 line;
+ u32 character;
+} LSPDocumentPosition;
+
+typedef struct {
+ LSPDocumentPosition position;
+} LSPRequestCompletion;
+
+typedef struct {
+ LSPRequestType type;
+ union {
+ LSPRequestOpen open;
+ LSPRequestCompletion completion;
+ // for LSP_SHOW_MESSAGE and LSP_LOG_MESSAGE
+ LSPRequestMessage message;
+ } data;
+} LSPRequest;
+
+// info we want to keep track of about a request,
+// so we can deal with the response appropriately.
+typedef struct {
+ u64 id;
+ LSPRequestType type;
+} LSPRequestTrackedInfo;
+
+typedef struct {
+ u32 offset;
+} LSPResponseString;
+
+typedef struct {
+ LSPResponseString label;
+ // note: the items are sorted here in this file,
+ // so you probably don't need to access this.
+ LSPResponseString sort_text;
+} LSPCompletionItem;
+
+typedef struct {
+ // dynamic array
+ LSPCompletionItem *items;
+} LSPResponseCompletion;
+
+typedef LSPRequestType LSPResponseType;
+typedef struct {
+ LSPResponseType type;
+ // LSP responses tend to have a lot of strings.
+ // to avoid doing a ton of allocations+frees,
+ // they're all stored here.
+ char *string_data;
+ union {
+ LSPResponseCompletion completion;
+ } data;
+} LSPResponse;
+
+typedef struct {
+ LSPMessageType type;
+ union {
+ LSPRequest request;
+ LSPResponse response;
+ } u;
+} LSPMessage;
+
+typedef struct {
+ Process process;
+ u64 request_id;
+ LSPMessage *messages;
+ SDL_mutex *messages_mutex;
+ LSPRequest *requests_client2server;
+ LSPRequest *requests_server2client;
+ // only applicable for client-to-server requests
+ LSPRequestTrackedInfo *requests_tracked_info;
+ SDL_mutex *requests_mutex;
+ bool initialized; // has the response to the initialize request been sent?
+ SDL_Thread *communication_thread;
+ SDL_sem *quit_sem;
+ char *received_data; // dynamic array
+ SDL_mutex *error_mutex;
+ char error[256];
+} LSP;
+
+// returns true if there's an error.
+// returns false and sets error to "" if there's no error.
+// if clear = true, the error will be cleared.
+// you may set error = NULL, error_size = 0, clear = true to just clear the error
+bool lsp_get_error(LSP *lsp, char *error, size_t error_size, bool clear) {
+ bool has_err = false;
+ SDL_LockMutex(lsp->error_mutex);
+ has_err = *lsp->error != '\0';
+ if (error_size)
+ str_cpy(error, error_size, lsp->error);
+ if (clear)
+ *lsp->error = '\0';
+ SDL_UnlockMutex(lsp->error_mutex);
+ return has_err;
+}
+
+#define lsp_set_error(lsp, ...) do {\
+ SDL_LockMutex(lsp->error_mutex);\
+ strbuf_printf(lsp->error, __VA_ARGS__);\
+ SDL_UnlockMutex(lsp->error_mutex);\
+ } while (0)
+
+static const char *lsp_language_id(Language lang) {
+ switch (lang) {
+ case LANG_CONFIG:
+ case LANG_TED_CFG:
+ case LANG_NONE:
+ return "text";
+ case LANG_C:
+ return "c";
+ case LANG_CPP:
+ return "cpp";
+ case LANG_JAVA:
+ return "java";
+ case LANG_JAVASCRIPT:
+ return "javascript";
+ case LANG_MARKDOWN:
+ return "markdown";
+ case LANG_GO:
+ return "go";
+ case LANG_RUST:
+ return "rust";
+ case LANG_PYTHON:
+ return "python";
+ case LANG_HTML:
+ return "html";
+ case LANG_TEX:
+ return "latex";
+ case LANG_COUNT: break;
+ }
+ assert(0);
+ return "text";
+}
+
+
+static void lsp_position_free(LSPDocumentPosition *position) {
+ free(position->path);
+}
+
+static void lsp_request_free(LSPRequest *r) {
+ switch (r->type) {
+ case LSP_NONE:
+ assert(0);
+ break;
+ case LSP_INITIALIZE:
+ case LSP_INITIALIZED:
+ case LSP_SHUTDOWN:
+ case LSP_EXIT:
+ break;
+ case LSP_COMPLETION: {
+ LSPRequestCompletion *completion = &r->data.completion;
+ lsp_position_free(&completion->position);
+ } break;
+ case LSP_OPEN: {
+ LSPRequestOpen *open = &r->data.open;
+ free(open->filename);
+ free(open->file_contents);
+ } break;
+ case LSP_SHOW_MESSAGE:
+ case LSP_LOG_MESSAGE:
+ free(r->data.message.message);
+ break;
+ }
+}
+
+static void lsp_response_free(LSPResponse *r) {
+ arr_free(r->string_data);
+ switch (r->type) {
+ case LSP_COMPLETION:
+ arr_free(r->data.completion.items);
+ break;
+ default:
+ break;
+ }
+}
+
+void lsp_message_free(LSPMessage *message) {
+ switch (message->type) {
+ case LSP_REQUEST:
+ lsp_request_free(&message->u.request);
+ break;
+ case LSP_RESPONSE:
+ lsp_response_free(&message->u.response);
+ break;
+ }
+ memset(message, 0, sizeof *message);
+}
+
+static WarnUnusedResult bool lsp_expect_type(LSP *lsp, JSONValue value, JSONValueType type, const char *what) {
+ if (value.type != type) {
+ lsp_set_error(lsp, "Expected %s for %s, got %s",
+ json_type_to_str(type),
+ what,
+ json_type_to_str(value.type));
+ return false;
+ }
+ return true;
+}
+
+static WarnUnusedResult bool lsp_expect_object(LSP *lsp, JSONValue value, const char *what) {
+ return lsp_expect_type(lsp, value, JSON_OBJECT, what);
+}
+
+static WarnUnusedResult bool lsp_expect_array(LSP *lsp, JSONValue value, const char *what) {
+ return lsp_expect_type(lsp, value, JSON_ARRAY, what);
+}
+
+static WarnUnusedResult bool lsp_expect_string(LSP *lsp, JSONValue value, const char *what) {
+ return lsp_expect_type(lsp, value, JSON_STRING, what);
+}
+
+static WarnUnusedResult bool lsp_expect_number(LSP *lsp, JSONValue value, const char *what) {
+ return lsp_expect_type(lsp, value, JSON_NUMBER, what);
+}
+
+// technically there are "requests" and "notifications"
+// notifications are different in that they don't have IDs and don't return responses.
+// this function handles both.
+// returns the ID of the request
+static void write_request(LSP *lsp, const LSPRequest *request) {
+
+ StrBuilder builder = str_builder_new();
+
+ u32 max_header_size = 64;
+ // this is where our header will go
+ str_builder_append_null(&builder, max_header_size);
+
+ str_builder_append(&builder, "{\"jsonrpc\":\"2.0\",");
+
+ bool is_notification = request->type == LSP_INITIALIZED
+ || request->type == LSP_EXIT;
+ if (!is_notification) {
+ unsigned long long id = lsp->request_id++;
+ str_builder_appendf(&builder, "\"id\":%llu,", id);
+ LSPRequestTrackedInfo info = {
+ .id = id,
+ .type = request->type
+ };
+ SDL_LockMutex(lsp->requests_mutex);
+ arr_add(lsp->requests_tracked_info, info);
+ SDL_UnlockMutex(lsp->requests_mutex);
+ }
+
+ switch (request->type) {
+ case LSP_NONE:
+ // these are server-to-client-only requests
+ case LSP_SHOW_MESSAGE:
+ case LSP_LOG_MESSAGE:
+ assert(0);
+ break;
+ case LSP_INITIALIZE: {
+ str_builder_appendf(&builder,
+ "\"method\":\"initialize\",\"params\":{"
+ "\"processId\":%d,"
+ "\"capabilities\":{}"
+ "}", process_get_id());
+ } break;
+ case LSP_INITIALIZED:
+ str_builder_append(&builder, "\"method\":\"initialized\"");
+ break;
+ case LSP_OPEN: {
+ const LSPRequestOpen *open = &request->data.open;
+ char *escaped_filename = json_escape(open->filename);
+ char *escaped_text = json_escape(open->file_contents);
+
+ str_builder_appendf(&builder,
+ "\"method\":\"textDocument/open\",\"params\":{"
+ "textDocument:{"
+ "uri:\"file://%s\","
+ "languageId:\"%s\","
+ "version:1,"
+ "text:\"%s\"}}",
+ escaped_filename,
+ lsp_language_id(open->language),
+ escaped_text);
+ free(escaped_text);
+ free(escaped_filename);
+ } break;
+ case LSP_COMPLETION: {
+ const LSPRequestCompletion *completion = &request->data.completion;
+ char *escaped_path = json_escape(completion->position.path);
+ str_builder_appendf(&builder,"\"method\":\"textDocument/completion\",\"params\":{"
+ "\"textDocument\":{\"uri\":\"file://%s\"},"
+ "\"position\":{"
+ "\"line\":%lu,"
+ "\"character\":%lu"
+ "}"
+ "}",
+ escaped_path,
+ (ulong)completion->position.line,
+ (ulong)completion->position.character);
+ free(escaped_path);
+ } break;
+ case LSP_SHUTDOWN:
+ str_builder_append(&builder, "\"method\":\"shutdown\"");
+ break;
+ case LSP_EXIT:
+ str_builder_append(&builder, "\"method\":\"exit\"");
+ break;
+ }
+
+ str_builder_append(&builder, "}");
+
+ // this is kind of hacky but it lets us send the whole request with one write call.
+ // probably not *actually* needed. i thought it would help fix an error but it didn't.
+ size_t content_length = str_builder_len(&builder) - max_header_size;
+ char content_length_str[32];
+ sprintf(content_length_str, "%zu", content_length);
+ size_t header_size = strlen("Content-Length: \r\n\r\n") + strlen(content_length_str);
+ char *header = &builder.str[max_header_size - header_size];
+ strcpy(header, "Content-Length: ");
+ strcat(header, content_length_str);
+ // we specifically DON'T want a null byte
+ memcpy(header + strlen(header), "\r\n\r\n", 4);
+
+ char *content = header;
+ #if 1
+ printf("\x1b[1m%s\x1b[0m\n",content);
+ #endif
+
+ // @TODO: does write always write the full amount? probably not. this should be fixed.
+ process_write(&lsp->process, content, strlen(content));
+
+ str_builder_free(&builder);
+}
+
+// figure out if data begins with a complete LSP response.
+static bool has_response(const char *data, size_t data_len, u64 *p_offset, u64 *p_size) {
+ const char *content_length = strstr(data, "Content-Length");
+ if (!content_length) return false;
+ const char *p = content_length + strlen("Content-Length");
+ if (!p[0] || !p[1] || !p[2]) return false;
+ p += 2;
+ size_t size = (size_t)atoll(p);
+ *p_size = size;
+ const char *header_end = strstr(content_length, "\r\n\r\n");
+ if (!header_end) return false;
+ header_end += 4;
+ u64 offset = (u64)(header_end - data);
+ *p_offset = offset;
+ return offset + size <= data_len;
+}
+
+void lsp_send_request(LSP *lsp, const LSPRequest *request) {
+ SDL_LockMutex(lsp->requests_mutex);
+ arr_add(lsp->requests_client2server, *request);
+ SDL_UnlockMutex(lsp->requests_mutex);
+}
+
+static bool parse_server2client_request(LSP *lsp, JSON *json, LSPRequest *request) {
+ JSONValue method_value = json_get(json, "method");
+ if (!lsp_expect_string(lsp, method_value, "request method"))
+ return false;
+
+ char method[64] = {0};
+ json_string_get(json, method_value.val.string, method, sizeof method);
+
+ if (streq(method, "window/showMessage")) {
+ request->type = LSP_SHOW_MESSAGE;
+ goto window_message;
+ } else if (streq(method, "window/logMessage")) {
+ request->type = LSP_LOG_MESSAGE;
+ window_message:;
+ JSONValue type = json_get(json, "params.type");
+ JSONValue message = json_get(json, "params.message");
+ if (!lsp_expect_number(lsp, type, "MessageType"))
+ return false;
+ if (!lsp_expect_string(lsp, message, "message string"))
+ return false;
+
+ int mtype = (int)type.val.number;
+ if (mtype < 1 || mtype > 4) {
+ lsp_set_error(lsp, "Bad MessageType: %g", type.val.number);
+ return false;
+ }
+
+ LSPRequestMessage *m = &request->data.message;
+ m->type = (LSPWindowMessageType)mtype;
+ m->message = json_string_get_alloc(json, message.val.string);
+ return true;
+ } else if (str_has_prefix(method, "$/")) {
+ // we can safely ignore this
+ } else {
+ lsp_set_error(lsp, "Unrecognized request method: %s", method);
+ }
+ return false;
+}
+
+const char *lsp_response_string(const LSPResponse *response, LSPResponseString string) {
+ assert(string.offset < arr_len(response->string_data));
+ return &response->string_data[string.offset];
+}
+
+static LSPResponseString lsp_response_add_json_string(LSPResponse *response, const JSON *json, JSONString string) {
+ u32 offset = arr_len(response->string_data);
+ arr_set_len(response->string_data, offset + string.len + 1);
+ json_string_get(json, string, response->string_data + offset, string.len + 1);
+ return (LSPResponseString){
+ .offset = offset
+ };
+}
+
+static int completion_qsort_cmp(void *context, const void *av, const void *bv) {
+ const LSPResponse *response = context;
+ const LSPCompletionItem *a = av, *b = bv;
+ const char *a_sort_text = lsp_response_string(response, a->sort_text);
+ const char *b_sort_text = lsp_response_string(response, b->sort_text);
+ int sort_text_cmp = strcmp(a_sort_text, b_sort_text);
+ if (sort_text_cmp != 0)
+ return sort_text_cmp;
+ // for some reason, rust-analyzer outputs identical sortTexts
+ // i have no clue what that means.
+ // the LSP "specification" is not very specific.
+ // we'll sort by label in this case.
+ const char *a_label = lsp_response_string(response, a->label);
+ const char *b_label = lsp_response_string(response, b->label);
+ return strcmp(a_label, b_label);
+}
+
+static bool parse_completion(LSP *lsp, const JSON *json, LSPResponse *response) {
+ // deal with textDocument/completion response.
+ // result: CompletionItem[] | CompletionList | null
+ response->type = LSP_COMPLETION;
+ LSPResponseCompletion *completion = &response->data.completion;
+
+ JSONValue result = json_get(json, "result");
+ JSONValue items_value = {0};
+ switch (result.type) {
+ case JSON_NULL:
+ // no completions
+ return true;
+ case JSON_ARRAY:
+ items_value = result;
+ break;
+ case JSON_OBJECT:
+ items_value = json_object_get(json, &result.val.object, "items");
+ break;
+ default:
+ lsp_set_error(lsp, "Weird result type for textDocument/completion response: %s.", json_type_to_str(result.type));
+ break;
+ }
+
+ if (!lsp_expect_array(lsp, items_value, "completion list"))
+ return false;
+
+ JSONArray items = items_value.val.array;
+
+ arr_set_len(completion->items, items.len);
+
+ for (u32 i = 0; i < items.len; ++i) {
+ LSPCompletionItem *item = &completion->items[i];
+
+ JSONValue item_value = json_array_get(json, &items, i);
+ if (!lsp_expect_object(lsp, item_value, "completion list"))
+ return false;
+ JSONObject item_object = item_value.val.object;
+
+ JSONValue label_value = json_object_get(json, &item_object, "label");
+ if (!lsp_expect_string(lsp, label_value, "completion label"))
+ return false;
+ JSONString label = label_value.val.string;
+
+ JSONString sort_text = label;
+ JSONValue sort_text_value = json_object_get(json, &item_object, "sortText");
+ if (sort_text_value.type == JSON_STRING) {
+ // LSP allows using a different string for sorting.
+ sort_text = sort_text_value.val.string;
+ }
+
+ item->label = lsp_response_add_json_string(response, json, label);
+ item->sort_text = lsp_response_add_json_string(response, json, sort_text);
+ }
+
+ qsort_with_context(completion->items, items.len, sizeof *completion->items,
+ completion_qsort_cmp, response);
+
+ return true;
+}
+
+
+static void process_message(LSP *lsp, JSON *json) {
+
+ #if 0
+ printf("\x1b[3m");
+ json_debug_print(json);
+ printf("\x1b[0m\n");
+ #endif
+
+ JSONValue error = json_get(json, "error.message");
+ if (error.type == JSON_STRING) {
+ char err[256] = {0};
+ json_string_get(json, error.val.string, err, sizeof err);;
+ lsp_set_error(lsp, "%s", err);
+ goto ret;
+ }
+
+ JSONValue result = json_get(json, "result");
+ if (result.type != JSON_UNDEFINED) {
+ JSONValue id = json_get(json, "id");
+
+ if (id.type != JSON_NUMBER) {
+ // what
+ lsp_set_error(lsp, "Response with no ID.");
+ goto ret;
+ }
+ if (id.val.number == 0) {
+ // it's the response to our initialize request!
+ // let's send back an "initialized" request (notification) because apparently
+ // that's something we need to do.
+ LSPRequest initialized = {
+ .type = LSP_INITIALIZED,
+ .data = {0},
+ };
+ write_request(lsp, &initialized);
+ // we can now send requests which have nothing to do with initialization
+ lsp->initialized = true;
+ } else {
+ u64 id_no = (u64)id.val.number;
+ LSPRequestTrackedInfo tracked_info = {0};
+ SDL_LockMutex(lsp->requests_mutex);
+ arr_foreach_ptr(lsp->requests_tracked_info, LSPRequestTrackedInfo, info) {
+ if (info->id == id_no) {
+ // hey its the thing
+ tracked_info = *info;
+ arr_remove(lsp->requests_tracked_info, (u32)(info - lsp->requests_tracked_info));
+ break;
+ }
+ }
+ SDL_UnlockMutex(lsp->requests_mutex);
+
+ LSPResponse response = {0};
+ bool success = false;
+ switch (tracked_info.type) {
+ case LSP_COMPLETION:
+ success = parse_completion(lsp, json, &response);
+ break;
+ default:
+ // it's some response we don't care about
+ break;
+ }
+ if (success) {
+ SDL_LockMutex(lsp->messages_mutex);
+ LSPMessage *message = arr_addp(lsp->messages);
+ message->type = LSP_RESPONSE;
+ message->u.response = response;
+ SDL_UnlockMutex(lsp->messages_mutex);
+ } else {
+ lsp_response_free(&response);
+ }
+ }
+ } else if (json_has(json, "method")) {
+ LSPRequest request = {0};
+ if (parse_server2client_request(lsp, json, &request)) {
+ SDL_LockMutex(lsp->messages_mutex);
+ LSPMessage *message = arr_addp(lsp->messages);
+ message->type = LSP_REQUEST;
+ message->u.request = request;
+ SDL_UnlockMutex(lsp->messages_mutex);
+ }
+ } else {
+ lsp_set_error(lsp, "Bad message from server (no result, no method).");
+ }
+ ret:
+ json_free(json);
+
+}
+
+// receive responses/requests/notifications from LSP, up to max_size bytes.
+static void lsp_receive(LSP *lsp, size_t max_size) {
+
+ {
+ // read stderr. if all goes well, we shouldn't get anything over stderr.
+ char stderr_buf[1024] = {0};
+ for (size_t i = 0; i < (max_size + sizeof stderr_buf) / sizeof stderr_buf; ++i) {
+ ssize_t nstderr = process_read_stderr(&lsp->process, stderr_buf, sizeof stderr_buf - 1);
+ if (nstderr > 0) {
+ // uh oh
+ stderr_buf[nstderr] = '\0';
+ fprintf(stderr, "\x1b[1m\x1b[93m%s\x1b[0m", stderr_buf);
+ } else {
+ break;
+ }
+ }
+ }
+
+ size_t received_so_far = arr_len(lsp->received_data);
+ arr_reserve(lsp->received_data, received_so_far + max_size + 1);
+ long long bytes_read = process_read(&lsp->process, lsp->received_data + received_so_far, max_size);
+ if (bytes_read <= 0) {
+ // no data
+ return;
+ }
+ received_so_far += (size_t)bytes_read;
+ // kind of a hack. this is needed because arr_set_len zeroes the data.
+ arr_hdr_(lsp->received_data)->len = (u32)received_so_far;
+ lsp->received_data[received_so_far] = '\0';// null terminate
+ #if 1
+ printf("\x1b[3m%s\x1b[0m\n",lsp->received_data);
+ #endif
+
+ u64 response_offset=0, response_size=0;
+ while (has_response(lsp->received_data, received_so_far, &response_offset, &response_size)) {
+ char *copy = strn_dup(lsp->received_data + response_offset, response_size);
+ JSON json = {0};
+ if (json_parse(&json, copy)) {
+ assert(json.text == copy);
+ json.is_text_copied = true;
+ process_message(lsp, &json);
+ } else {
+ lsp_set_error(lsp, "couldn't parse response JSON: %s", json.error);
+ json_free(&json);
+ }
+ size_t leftover_data_len = arr_len(lsp->received_data) - (response_offset + response_size);
+ memmove(lsp->received_data, lsp->received_data + response_offset + response_size,
+ leftover_data_len);
+ arr_set_len(lsp->received_data, leftover_data_len);
+ arr_reserve(lsp->received_data, leftover_data_len + 1);
+ lsp->received_data[leftover_data_len] = '\0';
+ }
+}
+
+// send requests.
+static bool lsp_send(LSP *lsp) {
+ if (!lsp->initialized) {
+ // don't send anything before the server is initialized.
+ return false;
+ }
+
+ LSPRequest *requests = NULL;
+ SDL_LockMutex(lsp->requests_mutex);
+ size_t n_requests = arr_len(lsp->requests_client2server);
+ requests = calloc(n_requests, sizeof *requests);
+ memcpy(requests, lsp->requests_client2server, n_requests * sizeof *requests);
+ arr_clear(lsp->requests_client2server);
+ SDL_UnlockMutex(lsp->requests_mutex);
+
+ bool quit = false;
+ for (size_t i = 0; i < n_requests; ++i) {
+ LSPRequest *r = &requests[i];
+ if (!quit) {
+ // this could slow down lsp_free if there's a gigantic request.
+ // whatever.
+ write_request(lsp, r);
+ }
+ lsp_request_free(r);
+
+ if (SDL_SemTryWait(lsp->quit_sem) == 0) {
+ quit = true;
+ // important that we don't break here so all the requests get freed.
+ }
+ }
+
+ free(requests);
+ return quit;
+}
+
+
+// Do any necessary communication with the LSP.
+// This writes requests and reads (and parses) responses.
+static int lsp_communication_thread(void *data) {
+ LSP *lsp = data;
+ while (1) {
+ bool quit = lsp_send(lsp);
+ if (quit) break;
+
+ lsp_receive(lsp, (size_t)10<<20);
+ if (SDL_SemWaitTimeout(lsp->quit_sem, 5) == 0)
+ break;
+ }
+
+ if (lsp->initialized) {
+ LSPRequest shutdown = {
+ .type = LSP_SHUTDOWN,
+ .data = {0}
+ };
+ LSPRequest exit = {
+ .type = LSP_EXIT,
+ .data = {0}
+ };
+ write_request(lsp, &shutdown);
+ // i give you ONE MILLISECOND to send your fucking shutdown response
+ time_sleep_ms(1);
+ write_request(lsp, &exit);
+ // i give you ONE MILLISECOND to terminate
+ // I WILL KILL YOU IF IT TAKES ANY LONGER
+ time_sleep_ms(1);
+
+ #if 1
+ char buf[1024]={0};
+ long long n = process_read(&lsp->process, buf, sizeof buf);
+ if (n>0) {
+ buf[n]=0;
+ printf("%s\n",buf);
+ }
+ n = process_read_stderr(&lsp->process, buf, sizeof buf);
+ if (n>0) {
+ buf[n]=0;
+ printf("\x1b[1m%s\x1b[0m\n",buf);
+ }
+ #endif
+ }
+ return 0;
+}
+
+bool lsp_create(LSP *lsp, const char *analyzer_command) {
+ ProcessSettings settings = {
+ .stdin_blocking = true,
+ .stdout_blocking = false,
+ .stderr_blocking = false,
+ .separate_stderr = true,
+ };
+ process_run_ex(&lsp->process, analyzer_command, &settings);
+ LSPRequest initialize = {
+ .type = LSP_INITIALIZE
+ };
+ // immediately send the request rather than queueing it.
+ // this is a small request, so it shouldn't be a problem.
+ write_request(lsp, &initialize);
+
+ lsp->quit_sem = SDL_CreateSemaphore(0);
+ lsp->messages_mutex = SDL_CreateMutex();
+ lsp->requests_mutex = SDL_CreateMutex();
+ lsp->communication_thread = SDL_CreateThread(lsp_communication_thread, "LSP communicate", lsp);
+ return true;
+}
+
+bool lsp_next_message(LSP *lsp, LSPMessage *message) {
+ bool any = false;
+ SDL_LockMutex(lsp->messages_mutex);
+ if (arr_len(lsp->messages)) {
+ *message = lsp->messages[0];
+ arr_remove(lsp->messages, 0);
+ any = true;
+ }
+ SDL_UnlockMutex(lsp->messages_mutex);
+ return any;
+}
+
+void lsp_free(LSP *lsp) {
+ SDL_SemPost(lsp->quit_sem);
+ SDL_WaitThread(lsp->communication_thread, NULL);
+ SDL_DestroyMutex(lsp->messages_mutex);
+ SDL_DestroySemaphore(lsp->quit_sem);
+ process_kill(&lsp->process);
+ arr_free(lsp->received_data);
+ arr_foreach_ptr(lsp->messages, LSPMessage, message) {
+ lsp_message_free(message);
+ }
+ arr_free(lsp->messages);
+}