diff options
-rw-r--r-- | buffer.c | 4 | ||||
-rw-r--r-- | lsp.c | 120 | ||||
-rw-r--r-- | lsp.h | 64 | ||||
-rw-r--r-- | main.c | 10 |
4 files changed, 130 insertions, 68 deletions
@@ -2132,8 +2132,8 @@ Status buffer_load_file(TextBuffer *buffer, char const *filename) { LSP *lsp = buffer_lsp(buffer); if (lsp) { // send didOpen - LSPRequest request = {.type = LSP_OPEN}; - LSPRequestOpen *open = &request.data.open; + LSPRequest request = {.type = LSP_REQUEST_DID_OPEN}; + LSPRequestDidOpen *open = &request.data.open; open->file_contents = (char *)file_contents; open->path = str_dup(filename); open->language = buffer_language(buffer); @@ -53,34 +53,45 @@ static void lsp_position_free(LSPDocumentPosition *position) { free(position->path); } +static void lsp_document_change_event_free(LSPDocumentChangeEvent *event) { + free(event->text); +} + static void lsp_request_free(LSPRequest *r) { switch (r->type) { - case LSP_NONE: - case LSP_INITIALIZE: - case LSP_INITIALIZED: - case LSP_SHUTDOWN: - case LSP_EXIT: + case LSP_REQUEST_NONE: + case LSP_REQUEST_INITIALIZE: + case LSP_REQUEST_INITIALIZED: + case LSP_REQUEST_SHUTDOWN: + case LSP_REQUEST_EXIT: break; - case LSP_COMPLETION: { + case LSP_REQUEST_COMPLETION: { LSPRequestCompletion *completion = &r->data.completion; lsp_position_free(&completion->position); } break; - case LSP_OPEN: { - LSPRequestOpen *open = &r->data.open; + case LSP_REQUEST_DID_OPEN: { + LSPRequestDidOpen *open = &r->data.open; free(open->path); free(open->file_contents); } break; - case LSP_SHOW_MESSAGE: - case LSP_LOG_MESSAGE: + case LSP_REQUEST_SHOW_MESSAGE: + case LSP_REQUEST_LOG_MESSAGE: free(r->data.message.message); break; + case LSP_REQUEST_DID_CHANGE: { + LSPRequestDidChange *c = &r->data.change; + arr_foreach_ptr(c->changes, LSPDocumentChangeEvent, event) + lsp_document_change_event_free(event); + free(c->document); + arr_free(c->changes); + } break; } } static void lsp_response_free(LSPResponse *r) { arr_free(r->string_data); switch (r->type) { - case LSP_COMPLETION: + case LSP_REQUEST_COMPLETION: arr_free(r->data.completion.items); break; default: @@ -127,6 +138,13 @@ static WarnUnusedResult bool lsp_expect_number(LSP *lsp, JSONValue value, const return lsp_expect_type(lsp, value, JSON_NUMBER, what); } +static void write_string(StrBuilder *builder, const char* string) { + abort(); +} + +static void write_range(StrBuilder *builder, LSPRange range) { +} + // technically there are "requests" and "notifications" // notifications are different in that they don't have IDs and don't return responses. // this function handles both. @@ -142,9 +160,9 @@ static void write_request(LSP *lsp, LSPRequest *request) { str_builder_append(&builder, "{\"jsonrpc\":\"2.0\","); - bool is_notification = request->type == LSP_INITIALIZED - || request->type == LSP_EXIT - || request->type == LSP_OPEN; + bool is_notification = request->type == LSP_REQUEST_INITIALIZED + || request->type == LSP_REQUEST_EXIT + || request->type == LSP_REQUEST_DID_OPEN; if (!is_notification) { u32 id = lsp->request_id++; request->id = id; @@ -152,13 +170,13 @@ static void write_request(LSP *lsp, LSPRequest *request) { } switch (request->type) { - case LSP_NONE: + case LSP_REQUEST_NONE: // these are server-to-client-only requests - case LSP_SHOW_MESSAGE: - case LSP_LOG_MESSAGE: + case LSP_REQUEST_SHOW_MESSAGE: + case LSP_REQUEST_LOG_MESSAGE: assert(0); break; - case LSP_INITIALIZE: { + case LSP_REQUEST_INITIALIZE: { str_builder_appendf(&builder, "\"method\":\"initialize\",\"params\":{" "\"processId\":%d," @@ -167,13 +185,12 @@ static void write_request(LSP *lsp, LSPRequest *request) { "\"workspaceFolders\":null" "}", process_get_id()); } break; - case LSP_INITIALIZED: + case LSP_REQUEST_INITIALIZED: str_builder_append(&builder, "\"method\":\"initialized\""); break; - case LSP_OPEN: { - const LSPRequestOpen *open = &request->data.open; + case LSP_REQUEST_DID_OPEN: { + const LSPRequestDidOpen *open = &request->data.open; char *escaped_path = json_escape(open->path); - char *escaped_text = json_escape(open->file_contents); // @TODO: escape directly into builder str_builder_appendf(&builder, "\"method\":\"textDocument/didOpen\",\"params\":{" @@ -181,14 +198,34 @@ static void write_request(LSP *lsp, LSPRequest *request) { "\"uri\":\"file://%s\"," "\"languageId\":\"%s\"," "\"version\":1," - "\"text\":\"%s\"}}", + "\"text\":", escaped_path, - lsp_language_id(open->language), - escaped_text); - free(escaped_text); + lsp_language_id(open->language)); + write_string(&builder, open->file_contents); + str_builder_appendf(&builder, "}}"); free(escaped_path); } break; - case LSP_COMPLETION: { + case LSP_REQUEST_DID_CHANGE: { + LSPRequestDidChange *change = &request->data.change; + static unsigned long long version_number = 0; // @TODO @TEMPORARY + ++version_number; + str_builder_appendf(&builder, + "\"method\":\"textDocument/didChange\",\"params\":{" + "\"textDocument\":{\"version\":%llu,\"uri\":\"file://%s\"}," + "\"contentChanges\":[", + version_number, change->document); + arr_foreach_ptr(change->changes, LSPDocumentChangeEvent, event) { + if (event != change->changes) str_builder_append(&builder, ","); + str_builder_appendf(&builder, "{\"range\":"); + write_range(&builder, event->range); + str_builder_appendf(&builder, "\"text\":"); + write_string(&builder, event->text); + str_builder_append(&builder, "}"); + } + str_builder_appendf(&builder, + "]}"); + } break; + case LSP_REQUEST_COMPLETION: { const LSPRequestCompletion *completion = &request->data.completion; char *escaped_path = json_escape(completion->position.path); str_builder_appendf(&builder,"\"method\":\"textDocument/completion\",\"params\":{" @@ -203,10 +240,10 @@ static void write_request(LSP *lsp, LSPRequest *request) { (ulong)completion->position.character); free(escaped_path); } break; - case LSP_SHUTDOWN: + case LSP_REQUEST_SHUTDOWN: str_builder_append(&builder, "\"method\":\"shutdown\""); break; - case LSP_EXIT: + case LSP_REQUEST_EXIT: str_builder_append(&builder, "\"method\":\"exit\""); break; } @@ -276,10 +313,10 @@ static bool parse_server2client_request(LSP *lsp, JSON *json, LSPRequest *reques json_string_get(json, method_value.val.string, method, sizeof method); if (streq(method, "window/showMessage")) { - request->type = LSP_SHOW_MESSAGE; + request->type = LSP_REQUEST_SHOW_MESSAGE; goto window_message; } else if (streq(method, "window/logMessage")) { - request->type = LSP_LOG_MESSAGE; + request->type = LSP_REQUEST_LOG_MESSAGE; window_message:; JSONValue type = json_get(json, "params.type"); JSONValue message = json_get(json, "params.message"); @@ -366,7 +403,7 @@ static bool parse_range(LSP *lsp, const JSON *json, JSONValue range_value, LSPRa static bool parse_completion(LSP *lsp, const JSON *json, LSPResponse *response) { // deal with textDocument/completion response. // result: CompletionItem[] | CompletionList | null - response->type = LSP_COMPLETION; + response->type = LSP_REQUEST_COMPLETION; LSPResponseCompletion *completion = &response->data.completion; JSONValue result = json_get(json, "result"); @@ -517,12 +554,12 @@ static void process_message(LSP *lsp, JSON *json) { JSONValue result = json_get(json, "result"); if (result.type != JSON_UNDEFINED) { - if (response_to.type == LSP_INITIALIZE) { + if (response_to.type == LSP_REQUEST_INITIALIZE) { // 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, + .type = LSP_REQUEST_INITIALIZED, .data = {0}, }; write_request(lsp, &initialized); @@ -532,7 +569,7 @@ static void process_message(LSP *lsp, JSON *json) { LSPResponse response = {0}; bool success = false; switch (response_to.type) { - case LSP_COMPLETION: + case LSP_REQUEST_COMPLETION: success = parse_completion(lsp, json, &response); break; default: @@ -671,11 +708,11 @@ static int lsp_communication_thread(void *data) { if (lsp->initialized) { LSPRequest shutdown = { - .type = LSP_SHUTDOWN, + .type = LSP_REQUEST_SHUTDOWN, .data = {0} }; LSPRequest exit = { - .type = LSP_EXIT, + .type = LSP_REQUEST_EXIT, .data = {0} }; write_request(lsp, &shutdown); @@ -712,7 +749,7 @@ bool lsp_create(LSP *lsp, const char *analyzer_command) { }; process_run_ex(&lsp->process, analyzer_command, &settings); LSPRequest initialize = { - .type = LSP_INITIALIZE + .type = LSP_REQUEST_INITIALIZE }; // immediately send the request rather than queueing it. // this is a small request, so it shouldn't be a problem. @@ -750,3 +787,10 @@ void lsp_free(LSP *lsp) { } arr_free(lsp->messages); } + +void lsp_document_changed(LSP *lsp, const char *document, LSPDocumentChangeEvent change) { + // @TODO(optimization, eventually): batch changes (using the contentChanges array) + //LSPRequest request = {.type = LSP_REQUEST_DID_CHANGE}; + //LSPRequestDidChange *change = &request.change; + abort(); // @TODO +} @@ -1,4 +1,6 @@ // @TODO: +// - make a real JSON output library +// - use document IDs instead of strings (also lets us use real document version numbers) // - document this and lsp.c. // - maximum queue size for requests/responses just in case? // - delete old sent requests @@ -10,20 +12,35 @@ typedef enum { LSP_RESPONSE } LSPMessageType; +typedef struct { + u32 offset; +} LSPString; + +typedef struct { + u32 line; + u32 character; +} LSPPosition; + +typedef struct { + LSPPosition start; + LSPPosition end; +} LSPRange; + typedef enum { - LSP_NONE, + LSP_REQUEST_NONE, // client-to-server - LSP_INITIALIZE, - LSP_INITIALIZED, - LSP_OPEN, - LSP_COMPLETION, - LSP_SHUTDOWN, - LSP_EXIT, + LSP_REQUEST_INITIALIZE, + LSP_REQUEST_INITIALIZED, + LSP_REQUEST_DID_OPEN, + LSP_REQUEST_DID_CHANGE, + LSP_REQUEST_COMPLETION, + LSP_REQUEST_SHUTDOWN, + LSP_REQUEST_EXIT, // server-to-client - LSP_SHOW_MESSAGE, - LSP_LOG_MESSAGE + LSP_REQUEST_SHOW_MESSAGE, + LSP_REQUEST_LOG_MESSAGE } LSPRequestType; typedef struct { @@ -33,7 +50,18 @@ typedef struct { char *path; // freed by lsp_request_free char *file_contents; -} LSPRequestOpen; +} LSPRequestDidOpen; + +// see TextDocumentContentChangeEvent in the LSP spec +typedef struct { + LSPRange range; + char *text; +} LSPDocumentChangeEvent; + +typedef struct { + char *document; + LSPDocumentChangeEvent *changes; // dynamic array +} LSPRequestDidChange; typedef enum { ERROR = 1, @@ -65,26 +93,15 @@ typedef struct { u32 id; LSPRequestType type; union { - LSPRequestOpen open; + LSPRequestDidOpen open; + LSPRequestDidChange change; LSPRequestCompletion completion; // for LSP_SHOW_MESSAGE and LSP_LOG_MESSAGE LSPRequestMessage message; } data; } LSPRequest; -typedef struct { - u32 offset; -} LSPString; -typedef struct { - u32 line; - u32 character; -} LSPPosition; - -typedef struct { - LSPPosition start; - LSPPosition end; -} LSPRange; // see InsertTextFormat in the LSP spec. typedef enum { @@ -177,4 +194,5 @@ void lsp_send_request(LSP *lsp, const LSPRequest *request); const char *lsp_response_string(const LSPResponse *response, LSPString string); bool lsp_create(LSP *lsp, const char *analyzer_command); bool lsp_next_message(LSP *lsp, LSPMessage *message); +void lsp_document_changed(LSP *lsp, const char *document, LSPDocumentChangeEvent change); void lsp_free(LSP *lsp); @@ -1,4 +1,4 @@ -/* +/* o @TODO: - rename buffer->filename to buffer->path - rust-analyzer bug reports: @@ -307,7 +307,7 @@ int main(int argc, char **argv) { exit(1); } usleep(1000000);//if we don't do this we get "waiting for cargo metadata or cargo check" - LSPRequest test_req = {.type = LSP_COMPLETION}; + LSPRequest test_req = {.type = LSP_REQUEST_COMPLETION}; test_req.data.completion = (LSPRequestCompletion){ .position = { .path = str_dup("/p/test-lsp/src/main.rs"), @@ -322,7 +322,7 @@ int main(int argc, char **argv) { if (message.type == LSP_RESPONSE) { const LSPResponse *response = &message.u.response; switch (response->type) { - case LSP_COMPLETION: { + case LSP_REQUEST_COMPLETION: { const LSPResponseCompletion *completion = &response->data.completion; arr_foreach_ptr(completion->items, LSPCompletionItem, item) { printf("(%d)%s => ", @@ -338,12 +338,12 @@ int main(int argc, char **argv) { } else if (message.type == LSP_REQUEST) { const LSPRequest *request = &message.u.request; switch (request->type) { - case LSP_SHOW_MESSAGE: { + case LSP_REQUEST_SHOW_MESSAGE: { const LSPRequestMessage *m = &request->data.message; // @TODO actually show printf("Show (%d): %s\n", m->type, m->message); } break; - case LSP_LOG_MESSAGE: { + case LSP_REQUEST_LOG_MESSAGE: { const LSPRequestMessage *m = &request->data.message; // @TODO actually log printf("Log (%d): %s\n", m->type, m->message); |