summaryrefslogtreecommitdiff
path: root/lsp.c
diff options
context:
space:
mode:
Diffstat (limited to 'lsp.c')
-rw-r--r--lsp.c170
1 files changed, 170 insertions, 0 deletions
diff --git a/lsp.c b/lsp.c
new file mode 100644
index 0000000..a204dec
--- /dev/null
+++ b/lsp.c
@@ -0,0 +1,170 @@
+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;
+}