diff options
-rw-r--r-- | command.h | 1 | ||||
-rw-r--r-- | ide-rename.c | 0 | ||||
-rw-r--r-- | lsp-json.c | 14 | ||||
-rw-r--r-- | lsp-parse.c | 64 | ||||
-rw-r--r-- | lsp.c | 3 | ||||
-rw-r--r-- | lsp.h | 54 | ||||
-rw-r--r-- | main.c | 8 |
7 files changed, 106 insertions, 38 deletions
@@ -59,6 +59,7 @@ ENUM_U16 { CMD_RELOAD_ALL, // reload all buffers from file CMD_QUIT, + // IDE features CMD_SET_LANGUAGE, CMD_AUTOCOMPLETE, CMD_AUTOCOMPLETE_BACK, diff --git a/ide-rename.c b/ide-rename.c new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/ide-rename.c @@ -465,6 +465,20 @@ JSONValue json_array_get(const JSON *json, JSONArray array, u64 i) { return (JSONValue){0}; } +// returns the `i`th key in `object`. +JSONValue json_object_key(const JSON *json, JSONObject object, u64 i) { + if (i < object.len) + return json->values[object.items + i]; + return (JSONValue){0}; +} + +// returns the `i`th value in `object`. +JSONValue json_object_value(const JSON *json, JSONObject object, u64 i) { + if (i < object.len) + return json->values[object.items + object.len + i]; + return (JSONValue){0}; +} + // returns NaN if `x` is not a number (ha ha). double json_force_number(JSONValue x) { if (x.type == JSON_NUMBER) { diff --git a/lsp-parse.c b/lsp-parse.c index 7eb8460..1d84408 100644 --- a/lsp-parse.c +++ b/lsp-parse.c @@ -190,6 +190,19 @@ static JSONString get_markup_content(const JSON *json, JSONValue markup_value) { } } +static bool parse_text_edit(LSP *lsp, LSPResponse *response, const JSON *json, JSONValue value, LSPTextEdit *edit) { + JSONObject object = json_force_object(value); + JSONValue range = json_object_get(json, object, "range"); + if (!parse_range(lsp, json, range, &edit->range)) + return false; + JSONValue new_text_value = json_object_get(json, object, "newText"); + if (!lsp_expect_string(lsp, new_text_value, "completion newText")) + return false; + edit->new_text = lsp_response_add_json_string(response, + json, new_text_value.val.string); + return true; +} + static bool parse_completion(LSP *lsp, const JSON *json, LSPResponse *response) { // deal with textDocument/completion response. // result: CompletionItem[] | CompletionList | null @@ -239,9 +252,9 @@ static bool parse_completion(LSP *lsp, const JSON *json, LSPResponse *response) // defaults item->sort_text = item->label; item->filter_text = item->label; + item->edit_type = LSP_COMPLETION_EDIT_PLAIN; + item->at_cursor = true; item->text_edit = (LSPTextEdit) { - .type = LSP_TEXT_EDIT_PLAIN, - .at_cursor = true, .range = {{0}, {0}}, .new_text = item->label }; @@ -278,13 +291,13 @@ static bool parse_completion(LSP *lsp, const JSON *json, LSPResponse *response) double edit_type = json_object_get_number(json, item_object, "insertTextFormat"); if (!isnan(edit_type)) { - if (edit_type != LSP_TEXT_EDIT_PLAIN && edit_type != LSP_TEXT_EDIT_SNIPPET) { + if (edit_type != LSP_COMPLETION_EDIT_PLAIN && edit_type != LSP_COMPLETION_EDIT_SNIPPET) { // maybe in the future more edit types will be added. // probably they'll have associated capabilities, but I think it's best to just ignore unrecognized types debug_println("Bad InsertTextFormat: %g", edit_type); - edit_type = LSP_TEXT_EDIT_PLAIN; + edit_type = LSP_COMPLETION_EDIT_PLAIN; } - item->text_edit.type = (LSPTextEditType)edit_type; + item->edit_type = (LSPCompletionEditType)edit_type; } JSONValue documentation_value = json_object_get(json, item_object, "documentation"); @@ -312,18 +325,9 @@ static bool parse_completion(LSP *lsp, const JSON *json, LSPResponse *response) // what should happen when this completion is selected? JSONValue text_edit_value = json_object_get(json, item_object, "textEdit"); if (text_edit_value.type == JSON_OBJECT) { - JSONObject text_edit = text_edit_value.val.object; - item->text_edit.at_cursor = false; - - JSONValue range = json_object_get(json, text_edit, "range"); - if (!parse_range(lsp, json, range, &item->text_edit.range)) + item->at_cursor = false; + if (!parse_text_edit(lsp, response, json, text_edit_value, &item->text_edit)) return false; - - JSONValue new_text_value = json_object_get(json, text_edit, "newText"); - if (!lsp_expect_string(lsp, new_text_value, "completion newText")) - return false; - item->text_edit.new_text = lsp_response_add_json_string(response, - json, new_text_value.val.string); } else { // not using textEdit. check insertText. JSONValue insert_text_value = json_object_get(json, item_object, "insertText"); @@ -676,6 +680,31 @@ static bool parse_server2client_request(LSP *lsp, JSON *json, LSPRequest *reques return false; } +static bool parse_workspace_edit(LSP *lsp, LSPResponse *response, const JSON *json, JSONObject object, LSPWorkspaceEdit *edit) { + JSONObject changes = json_object_get_object(json, object, "changes"); + for (u32 c = 0; c < changes.len; ++c) { + JSONValue uri = json_object_key(json, changes, c); + JSONArray edits = json_force_array(json_object_value(json, changes, c)); + LSPDocumentID document = 0; + if (!parse_document_uri(lsp, json, uri, &document)) + return false; + for (u32 e = 0; e < edits.len; ++e) { + LSPWorkspaceChange *change = arr_addp(edit->changes); + change->type = LSP_CHANGE_EDIT; + change->data.edit.document = document; + JSONValue text_edit = json_array_get(json, edits, e); + if (!parse_text_edit(lsp, response, json, text_edit, &change->data.edit.edit)) + return false; + } + } + todo : the other thing + return true; +} + +static bool parse_rename(LSP *lsp, const JSON *json, LSPResponse *response) { + JSONObject result = json_force_object(json_get(json, "result")); + return parse_workspace_edit(lsp, response, json, result, &response->data.rename); +} static void process_message(LSP *lsp, JSON *json) { @@ -733,6 +762,9 @@ static void process_message(LSP *lsp, JSON *json) { case LSP_REQUEST_WORKSPACE_SYMBOLS: add_to_messages = parse_workspace_symbols(lsp, json, &response); break; + case LSP_REQUEST_RENAME: + add_to_messages = parse_rename(lsp, json, &response); + break; case LSP_REQUEST_INITIALIZE: { // it's the response to our initialize request! if (result.type == JSON_OBJECT) { @@ -100,6 +100,9 @@ static void lsp_response_free(LSPResponse *r) { case LSP_REQUEST_WORKSPACE_SYMBOLS: arr_free(r->data.workspace_symbols.symbols); break; + case LSP_REQUEST_RENAME: + arr_free(r->data.rename.changes); + break; default: break; } @@ -249,29 +249,19 @@ typedef enum { #define LSP_COMPLETION_KIND_MAX 25 } LSPCompletionKind; +typedef struct { + LSPRange range; + LSPString new_text; +} LSPTextEdit; // see InsertTextFormat in the LSP spec. typedef enum { // plain text - LSP_TEXT_EDIT_PLAIN = 1, + LSP_COMPLETION_EDIT_PLAIN = 1, // snippet e.g. "some_method($1, $2)$0" - LSP_TEXT_EDIT_SNIPPET = 2 -} LSPTextEditType; - -typedef struct { - LSPTextEditType type; + LSP_COMPLETION_EDIT_SNIPPET = 2 +} LSPCompletionEditType; - // if set to true, `range` should be ignored - // -- this is a completion which uses insertText. - // how to handle this: - // "VS Code when code complete is requested in this example - // `con<cursor position>` and a completion item with an `insertText` of - // `console` is provided it will only insert `sole`" - bool at_cursor; - - LSPRange range; - LSPString new_text; -} LSPTextEdit; typedef struct { // display text for this completion @@ -284,6 +274,15 @@ typedef struct { LSPString documentation; // the edit to be applied when this completion is selected. LSPTextEdit text_edit; + // type for text_edit + LSPCompletionEditType edit_type; + // if set to true, `text_edit.range` should be ignored + // -- this is a completion which uses insertText. + // how to handle this: + // "VS Code when code complete is requested in this example + // `con<cursor position>` and a completion item with an `insertText` of + // `console` is provided it will only insert `sole`" + bool at_cursor; // note: the items are sorted here in this file, // so you probably don't need to access this. LSPString sort_text; @@ -346,7 +345,25 @@ typedef struct { LSPSymbolInformation *symbols; } LSPResponseWorkspaceSymbols; -typedef LSPRequestType LSPResponseType; +typedef enum { + LSP_CHANGE_EDIT = 1 +} LSPWorkspaceChangeType; + +typedef struct { + LSPWorkspaceChangeType type; + union { + struct { + LSPDocumentID document; + LSPTextEdit edit; + } edit; + } data; +} LSPWorkspaceChange; + +typedef struct { + LSPWorkspaceChange *changes; +} LSPWorkspaceEdit; +typedef LSPWorkspaceEdit LSPResponseRename; + typedef struct { LSPRequest request; // the request which this is a response to char *error; // if not NULL, the data field will just be zeroed @@ -361,6 +378,7 @@ typedef struct { LSPResponseHover hover; LSPResponseDefinition definition; LSPResponseWorkspaceSymbols workspace_symbols; + LSPResponseRename rename; } data; } LSPResponse; @@ -1,13 +1,12 @@ /* @TODO: - more LSP stuff: - - find usages - - rename - - check for others + - document highlight (textDocument/documentHighlight) + - find usages (textDocument/references) + - rename (textDocument/rename) - handle multiple symbols with same name in go-to-definition menu - :go-to-cursor-definition - test full unicode position handling -- check if there are any other non-optional/nice-to-have-support-for server-to-client requests - better non-error window/showMessage(Request) - document lsp.h and lsp.c. - add last_request_id checking to autocomplete_process_lsp_response @@ -39,6 +38,7 @@ FUTURE FEATURES: - robust find (results shouldn't move around when you type things) - multiple files with command line arguments - :set-build-command +- document links using LSP textDocument/documentLink request - add numlock as a key modifier? (but make sure "Ctrl+S" handles both "No NumLock+Ctrl+S" and "NumLock+Ctrl+S") - better undo chaining (dechain on backspace?) - allow multiple fonts (fonts directory?) |