typedef enum { LSP_INITIALIZE, LSP_OPEN, LSP_CHANGE, LSP_CLOSE, } 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; char error[256]; } LSP; static void send_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); 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 send_request(LSP *lsp, const LSPRequest *request) { static unsigned long long id; ++id; switch (request->type) { case LSP_INITIALIZE: { char content[1024]; strbuf_printf(content, "{\"jsonrpc\":\"2.0\",\"id\":%llu,\"method\":\"initialize\",\"params\":{" "\"processId\":%d," "\"capabilities\":{}" "}}", id, process_get_id()); send_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); send_request_content(lsp, did_open); } break; default: // @TODO abort(); } } static bool recv_response(LSP *lsp, JSON *json) { static char response_data[500000]; // i think this should always read all the response data. long long bytes_read = process_read(&lsp->process, response_data, sizeof response_data); if (bytes_read < 0) { strbuf_printf(lsp->error, "Read error."); return false; } response_data[bytes_read] = '\0'; char *body_start = strstr(response_data, "\r\n\r\n"); if (body_start) { body_start += 4; if (!json_parse(json, body_start)) { strbuf_cpy(lsp->error, json->error); return false; } 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 { strbuf_printf(lsp->error, "No response body."); return false; } return true; } bool lsp_create(LSP *lsp, const char *analyzer_command) { ProcessSettings settings = { .stdin_blocking = true, .stdout_blocking = true }; process_run_ex(&lsp->process, analyzer_command, &settings); char init_request[1024]; strbuf_printf(init_request, "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{" "\"processId\":%d," "\"capabilities\":{}" "}}", process_get_id()); send_request_content(lsp, init_request); JSON json = {0}; if (!recv_response(lsp, &json)) { return false; } json_debug_print(&json); return true; }