summaryrefslogtreecommitdiff
path: root/lsp.c
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2022-12-09 15:57:24 -0500
committerpommicket <pommicket@gmail.com>2022-12-09 15:57:24 -0500
commit2dce45002b9d4d93cc2db15b1697d13c1709fb92 (patch)
tree808efd41be71afe286a1d8bf83480ca1ff7df75d /lsp.c
parent27c9af7de5f47c33217be02107e31061366f481f (diff)
more lsp
Diffstat (limited to 'lsp.c')
-rw-r--r--lsp.c362
1 files changed, 262 insertions, 100 deletions
diff --git a/lsp.c b/lsp.c
index 8b62853..bff1ed0 100644
--- a/lsp.c
+++ b/lsp.c
@@ -1,36 +1,86 @@
+// @TODO: documentation
+// @TODO : make sure offsets are utf-16!
// @TODO: 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
+ LSP_EXIT,
+
+ // server-to-client
+ LSP_SHOW_MESSAGE,
+ LSP_LOG_MESSAGE
} LSPRequestType;
typedef struct {
// buffer language
Language language;
- // these will be free'd.
+ // 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;
typedef struct {
+ LSPMessageType type;
+ union {
+ LSPRequest request;
+ JSON response;
+ } u;
+} LSPMessage;
+
+typedef struct {
Process process;
u64 request_id;
- JSON *responses;
- SDL_mutex *responses_mutex;
- LSPRequest *requests;
+ LSPMessage *messages;
+ SDL_mutex *messages_mutex;
+ LSPRequest *requests_client2server;
+ LSPRequest *requests_server2client;
SDL_mutex *requests_mutex;
bool initialized; // has the response to the initialize request been sent?
SDL_Thread *communication_thread;
@@ -62,17 +112,6 @@ bool lsp_get_error(LSP *lsp, char *error, size_t error_size, bool clear) {
SDL_UnlockMutex(lsp->error_mutex);\
} while (0)
-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:
@@ -112,67 +151,98 @@ static const char *lsp_language_id(Language lang) {
static u64 write_request(LSP *lsp, const LSPRequest *request) {
unsigned long long id = lsp->request_id++;
+ 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\",");
+
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: {
- char content[1024];
- strbuf_printf(content,
- "{\"jsonrpc\":\"2.0\",\"id\":%llu,\"method\":\"initialize\",\"params\":{"
+ str_builder_appendf(&builder,
+ "\"id\":%llu,\"method\":\"initialize\",\"params\":{"
"\"processId\":%d,"
"\"capabilities\":{}"
- "}}", id, process_get_id());
- write_request_content(lsp, content);
- } break;
- case LSP_INITIALIZED: {
- write_request_content(lsp, "{\"jsonrpc\":\"2.0\",\"method\":\"initialized\"}");
+ "}", id, 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 *did_open = a_sprintf(
- "{\"jsonrpc\":\"2.0\",\"id\":%llu,\"method\":\"textDocument/open\",\"params\":{"
+ char *escaped_text = json_escape(open->file_contents);
+
+ str_builder_appendf(&builder,
+ "\"id\":%llu,\"method\":\"textDocument/open\",\"params\":{"
"textDocument:{"
"uri:\"file://%s\","
"languageId:\"%s\","
"version:1,"
- "text:\"",
- id, escaped_filename, lsp_language_id(open->language));
+ "text:\"%s\"}}",
+ id,
+ escaped_filename,
+ lsp_language_id(open->language),
+ escaped_text);
+ free(escaped_text);
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\":\"textDocument/completion\",\"params\":{"
- "}}", id);
- write_request_content(lsp, content);
- } break;
- case LSP_SHUTDOWN: {
- char content[1024];
- strbuf_printf(content,
- "{\"jsonrpc\":\"2.0\",\"id\":%llu,\"method\":\"shutdown\"}", id);
- write_request_content(lsp, content);
- } break;
- case LSP_EXIT: {
- write_request_content(lsp, "{\"jsonrpc\":\"2.0\",\"method\":\"exit\"}");
+ const LSPRequestCompletion *completion = &request->data.completion;
+ char *escaped_path = json_escape(completion->position.path);
+ str_builder_appendf(&builder,"\"id\":%llu,\"method\":\"textDocument/completion\",\"params\":{"
+ "textDocument:\"%s\","
+ "position:{"
+ "line:%lu,"
+ "character:%lu"
+ "}"
+ "}",
+ id,
+ escaped_path,
+ (ulong)completion->position.line,
+ (ulong)completion->position.character);
+ free(escaped_path);
} break;
+ case LSP_SHUTDOWN:
+ str_builder_appendf(&builder, "\"id\":%llu,\"method\":\"shutdown\"", id);
+ break;
+ case LSP_EXIT:
+ str_builder_appendf(&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);
+
return (u64)id;
}
@@ -195,55 +265,121 @@ static bool has_response(const char *data, size_t data_len, u64 *p_offset, u64 *
void lsp_send_request(LSP *lsp, const LSPRequest *request) {
SDL_LockMutex(lsp->requests_mutex);
- arr_add(lsp->requests, *request);
+ arr_add(lsp->requests_client2server, *request);
SDL_UnlockMutex(lsp->requests_mutex);
}
-static void process_response(LSP *lsp, const JSON *json) {
+static bool parse_server2client_request(LSP *lsp, JSON *json, LSPRequest *request) {
+ JSONValue method = json_get(json, "method");
+ if (method.type != JSON_STRING) {
+ lsp_set_error(lsp, "Bad type for request method: %s", json_type_to_str(method.type));
+ }
+
+ char str[64] = {0};
+ json_string_get(json, &method.val.string, str, sizeof str);
+
+ if (streq(str, "window/showMessage")) {
+ request->type = LSP_SHOW_MESSAGE;
+ goto window_message;
+ } else if (streq(str, "window/logMessage")) {
+ request->type = LSP_LOG_MESSAGE;
+ window_message:;
+ JSONValue type = json_get(json, "params.type");
+ JSONValue message = json_get(json, "params.message");
+ if (type.type != JSON_NUMBER) {
+ lsp_set_error(lsp, "Expected MessageType, got %s", json_type_to_str(type.type));
+ return false;
+ }
+ if (message.type != JSON_STRING) {
+ lsp_set_error(lsp, "Expected message string, got %s", json_type_to_str(message.type));
+ 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(str, "$/")) {
+ // we can safely ignore this
+ } else {
+ lsp_set_error(lsp, "Unrecognized request method: %s", str);
+ }
+ return false;
+}
+
+static void process_message(LSP *lsp, JSON *json) {
#if 1
printf("\x1b[3m");
json_debug_print(json);
printf("\x1b[0m\n");
#endif
+ bool keep_json = false;
+ 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");
- JSONValue id = json_get(json, "id");
+ if (result.type != JSON_UNDEFINED) {
+ JSONValue id = json_get(json, "id");
- if (result.type == JSON_UNDEFINED || id.type != JSON_NUMBER) {
- // uh oh
- 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);
+ 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},
+ };
+ u64 initialized_id = write_request(lsp, &initialized);
+ // this should be the second request.
+ (void)initialized_id;
+ assert(initialized_id == 1);
+ // we can now send requests which have nothing to do with initialization
+ lsp->initialized = true;
} else {
- lsp_set_error(lsp, "Server error (no message)");
+ SDL_LockMutex(lsp->messages_mutex);
+ LSPMessage *message = arr_addp(lsp->messages);
+ message->type = LSP_RESPONSE;
+ message->u.response = *json;
+ SDL_UnlockMutex(lsp->messages_mutex);
+ keep_json = true;
+ }
+ } 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 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},
- };
- u64 initialized_id = write_request(lsp, &initialized);
- // this should be the second request.
- (void)initialized_id;
- assert(initialized_id == 1);
- // we can now send requests which have nothing to do with initialization
- lsp->initialized = true;
} else {
- SDL_LockMutex(lsp->responses_mutex);
- arr_add(lsp->responses, *json);
- SDL_UnlockMutex(lsp->responses_mutex);
+ lsp_set_error(lsp, "Bad message from server (no result, no method).");
}
+ ret:
+ if (!keep_json)
+ json_free(json);
}
-// receive responses from LSP, up to max_size bytes.
+// receive responses/requests/notifications from LSP, up to max_size bytes.
static void lsp_receive(LSP *lsp, size_t max_size) {
{
@@ -283,9 +419,8 @@ static void lsp_receive(LSP *lsp, size_t max_size) {
if (json_parse(&json, copy)) {
assert(json.text == copy);
json.is_text_copied = true;
- process_response(lsp, &json);
+ process_message(lsp, &json);
} else {
-
lsp_set_error(lsp, "couldn't parse response JSON: %s", json.error);
json_free(&json);
}
@@ -298,22 +433,44 @@ static void lsp_receive(LSP *lsp, size_t max_size) {
}
}
-static void free_request(LSPRequest *r) {
+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_COMPLETION:
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;
+ }
+}
+
+void lsp_message_free(LSPMessage *message) {
+ switch (message->type) {
+ case LSP_REQUEST:
+ lsp_request_free(&message->u.request);
+ break;
+ case LSP_RESPONSE:
+ json_free(&message->u.response);
+ break;
}
}
@@ -326,10 +483,10 @@ static bool lsp_send(LSP *lsp) {
LSPRequest *requests = NULL;
SDL_LockMutex(lsp->requests_mutex);
- size_t n_requests = arr_len(lsp->requests);
+ size_t n_requests = arr_len(lsp->requests_client2server);
requests = calloc(n_requests, sizeof *requests);
- memcpy(requests, lsp->requests, n_requests * sizeof *requests);
- arr_clear(lsp->requests);
+ memcpy(requests, lsp->requests_client2server, n_requests * sizeof *requests);
+ arr_clear(lsp->requests_client2server);
SDL_UnlockMutex(lsp->requests_mutex);
bool quit = false;
@@ -340,7 +497,7 @@ static bool lsp_send(LSP *lsp) {
// whatever.
write_request(lsp, r);
}
- free_request(r);
+ lsp_request_free(r);
if (SDL_SemTryWait(lsp->quit_sem) == 0) {
quit = true;
@@ -416,27 +573,32 @@ bool lsp_create(LSP *lsp, const char *analyzer_command) {
write_request(lsp, &initialize);
lsp->quit_sem = SDL_CreateSemaphore(0);
- lsp->responses_mutex = SDL_CreateMutex();
+ lsp->messages_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 lsp_next_message(LSP *lsp, LSPMessage *message) {
bool any = false;
- SDL_LockMutex(lsp->responses_mutex);
- if (arr_len(lsp->responses)) {
- *json = lsp->responses[0];
- arr_remove(lsp->responses, 0);
+ SDL_LockMutex(lsp->messages_mutex);
+ if (arr_len(lsp->messages)) {
+ *message = lsp->messages[0];
+ arr_remove(lsp->messages, 0);
any = true;
}
- SDL_UnlockMutex(lsp->responses_mutex);
+ 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);
}