From 80400babc4a95edc4e4c96e85af79301cdffae69 Mon Sep 17 00:00:00 2001 From: pommicket Date: Tue, 6 Dec 2022 14:29:40 -0500 Subject: more work on lsp --- base.h | 6 ++++ json.c | 27 ++++++++++++++-- lsp.c | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- util.c | 13 ++++++++ 4 files changed, 147 insertions(+), 8 deletions(-) diff --git a/base.h b/base.h index 2af0820..5fa2b15 100644 --- a/base.h +++ b/base.h @@ -111,6 +111,12 @@ typedef unsigned long long ullong; #define WarnUnusedResult #endif +#if __GNUC__ +#define ATTRIBUTE_PRINTF(fmt_idx, arg_idx) __attribute__ ((format(printf, fmt_idx, arg_idx))) +#else +#define ATTRIBUTE_PRINTF(fmt_idx, arg_idx) +#endif + #define Status bool WarnUnusedResult // false = error, true = success #define arr_count(a) (sizeof (a) / sizeof *(a)) diff --git a/json.c b/json.c index 07b6c10..f514b4f 100644 --- a/json.c +++ b/json.c @@ -25,6 +25,9 @@ typedef struct { } JSONArray; enum { + // note: json doesn't actually include undefined. + // this is only for returning things from json_get etc. + JSON_UNDEFINED, JSON_NULL, JSON_FALSE, JSON_TRUE, @@ -65,6 +68,7 @@ static inline bool json_is_space(char c) { static void json_debug_print_value(const JSON *json, const JSONValue *value) { switch (value->type) { + case JSON_UNDEFINED: printf("undefined"); break; case JSON_NULL: printf("null"); break; case JSON_FALSE: printf("false"); break; case JSON_TRUE: printf("true"); break; @@ -385,7 +389,7 @@ static bool json_streq(const JSON *json, const JSONString *string, const char *n return *name == '\0'; } -// returns null if the property `name` does not exist. +// returns undefined if the property `name` does not exist. static JSONValue json_object_get(const JSON *json, const JSONObject *object, const char *name) { const JSONValue *items = &json->values[object->items]; for (u32 i = 0; i < object->len; ++i) { @@ -398,7 +402,7 @@ static JSONValue json_object_get(const JSON *json, const JSONObject *object, con } // e.g. if json is { "a" : { "b": 3 }}, then json_get(json, "a.b") = 3. -// returns null if there is no such property +// returns undefined if there is no such property static JSONValue json_get(const JSON *json, const char *path) { char segment[128]; const char *p = path; @@ -531,7 +535,24 @@ static void json_test_time_small(void) { static void json_debug_print(const JSON *json) { printf("%u values (capacity %u, text length %zu)\n", arr_len(json->values), arr_cap(json->values), strlen(json->text)); - json_debug_print_value(json, 0); + json_debug_print_value(json, &json->values[0]); +} + +// e.g. converts "Hello\nworld" to "Hello\\nworld" +// the return value is the # of bytes in the escaped string. +static size_t json_escape_to(char *out, size_t out_sz, const char *in) { + (void)out;(void)out_sz;(void)in; + // @TODO + abort(); +} + +// e.g. converts "Hello\nworld" to "Hello\\nworld" +// the resulting string should be free'd. +static char *json_escape(const char *str) { + size_t out_sz = 2 * strlen(str) + 1; + char *out = calloc(1, out_sz); + json_escape_to(out, out_sz, str); + return out; } #undef SKIP_WHITESPACE diff --git a/lsp.c b/lsp.c index 4499cfe..a204dec 100644 --- a/lsp.c +++ b/lsp.c @@ -1,10 +1,32 @@ +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(LSP *lsp, const char *content) { +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); @@ -12,6 +34,84 @@ static void send_request(LSP *lsp, const char *content) { 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. @@ -30,7 +130,7 @@ static bool recv_response(LSP *lsp, JSON *json) { return false; } JSONValue result = json_get(json, "result"); - if (result.type == JSON_NULL) { + if (result.type == JSON_UNDEFINED) { // uh oh JSONValue error = json_get(json, "error.message"); if (error.type == JSON_STRING) { @@ -60,12 +160,11 @@ bool lsp_create(LSP *lsp, const char *analyzer_command) { "\"capabilities\":{}" "}}", process_get_id()); - send_request(lsp, init_request); + send_request_content(lsp, init_request); JSON json = {0}; if (!recv_response(lsp, &json)) { return false; } - JSONValue response = json_get(&json, "result.capabilities.textDocumentSync.openClose"); - json_debug_print_value(&json, &response);printf("\n"); + json_debug_print(&json); return true; } diff --git a/util.c b/util.c index 574128b..fc73b1b 100644 --- a/util.c +++ b/util.c @@ -97,6 +97,19 @@ static char *str_dup(char const *src) { #define strbuf_catf(str, ...) assert(sizeof str != 4 && sizeof str != 8), \ str_catf(str, sizeof str, __VA_ARGS__) +#if __unix__ +static char *a_sprintf(const char *fmt, ...) ATTRIBUTE_PRINTF(1, 2); +static char *a_sprintf(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + char *str = NULL; + vasprintf(&str, fmt, args); + return str; +} +#else +#error "@TODO" +#endif + // on 16-bit systems, this is 16383. on 32/64-bit systems, this is 1073741823 // it is unusual to have a string that long. #define STRLEN_SAFE_MAX (UINT_MAX >> 2) -- cgit v1.2.3