// @TODO: maximum queue size for requests/responses just in case? typedef enum { LSP_NONE, LSP_INITIALIZE, LSP_INITIALIZED, LSP_OPEN, LSP_COMPLETION, } LSPRequestType; typedef struct { // buffer language Language language; // these will be free'd. char *filename; char *file_contents; } LSPRequestOpen; typedef struct { LSPRequestType type; union { LSPRequestOpen open; } data; } LSPRequest; typedef struct { Process process; u64 request_id; JSON *responses; SDL_mutex *responses_mutex; LSPRequest *requests; 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 char error[256]; } LSP; static void write_request_content(LSP *lsp, const char *content) { char header[128]; size_t content_size = strlen(content); strbuf_printf(header, "Content-Length: %zu\r\n\r\n", content_size); #if 0 printf("\x1b[1m%s%s\x1b[0m\n", header, content); #endif process_write(&lsp->process, header, strlen(header)); process_write(&lsp->process, content, content_size); } 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 write_request(LSP *lsp, const LSPRequest *request) { unsigned long long id = lsp->request_id++; switch (request->type) { case LSP_NONE: assert(0); break; case LSP_INITIALIZE: { char content[1024]; strbuf_printf(content, "{\"jsonrpc\":\"2.0\",\"id\":%llu,\"method\":\"initialize\",\"params\":{" "\"processId\":%d," "\"capabilities\":{}" "}}", id, process_get_id()); write_request_content(lsp, content); } break; case LSP_INITIALIZED: { char content[1024]; strbuf_printf(content, "{\"jsonrpc\":\"2.0\",\"id\":%llu,\"method\":\"initialized\",\"params\":{" "}}", id); write_request_content(lsp, content); } break; case LSP_OPEN: { const LSPRequestOpen *open = &request->data.open; char *escaped_filename = json_escape(open->filename); char *did_open = a_sprintf( "{\"jsonrpc\":\"2.0\",\"id\":%llu,\"method\":\"textDocument/open\",\"params\":{" "textDocument:{" "uri:\"file://%s\"," "languageId:\"%s\"," "version:1," "text:\"", id, escaped_filename, lsp_language_id(open->language)); free(escaped_filename); size_t did_open_sz = strlen(did_open) + 2 * strlen(open->file_contents) + 16; did_open = realloc(did_open, did_open_sz); size_t n = json_escape_to(did_open + strlen(did_open), did_open_sz - 10 - strlen(did_open), open->file_contents); char *p = did_open + n; sprintf(p, "\"}}}"); free(did_open); write_request_content(lsp, did_open); } break; case LSP_COMPLETION: { char content[1024]; // no params needed strbuf_printf(content, "{\"jsonrpc\":\"2.0\",\"id\":%llu,\"method\":\"initialize\",\"params\":{" "}}", id); write_request_content(lsp, content); } break; } } // 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, *request); SDL_UnlockMutex(lsp->requests_mutex); } // receive responses 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 0 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}; json_parse(&json, copy); assert(json.text == copy); json.is_text_copied = true; JSONValue result = json_get(&json, "result"); if (result.type == JSON_UNDEFINED) { // uh oh JSONValue error = json_get(&json, "error.message"); if (error.type == JSON_STRING) { json_string_get(&json, &error.val.string, lsp->error, sizeof lsp->error); } else { strbuf_printf(lsp->error, "Server error (no message)"); } } else { SDL_LockMutex(lsp->responses_mutex); arr_add(lsp->responses, json); SDL_UnlockMutex(lsp->responses_mutex); } 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'; } } static void free_request(LSPRequest *r) { switch (r->type) { case LSP_NONE: assert(0); break; case LSP_INITIALIZE: case LSP_INITIALIZED: case LSP_COMPLETION: break; case LSP_OPEN: { LSPRequestOpen *open = &r->data.open; free(open->filename); free(open->file_contents); } break; } } // 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); requests = calloc(n_requests, sizeof *requests); memcpy(requests, lsp->requests, n_requests * sizeof *requests); 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); } free_request(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; } 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->responses_mutex = SDL_CreateMutex(); lsp->communication_thread = SDL_CreateThread(lsp_communication_thread, "LSP communicate", lsp); return true; } bool lsp_next_response(LSP *lsp, JSON *json) { bool any = false; SDL_LockMutex(lsp->responses_mutex); if (arr_len(lsp->responses)) { *json = lsp->responses[0]; arr_remove(lsp->responses, 0); any = true; } SDL_UnlockMutex(lsp->responses_mutex); return any; } void lsp_free(LSP *lsp) { SDL_SemPost(lsp->quit_sem); SDL_WaitThread(lsp->communication_thread, NULL); SDL_DestroySemaphore(lsp->quit_sem); process_kill(&lsp->process); arr_free(lsp->received_data); }