From 8de9b9568caf088e9f75d880fae7105661d9e0dc Mon Sep 17 00:00:00 2001 From: pommicket Date: Wed, 5 Mar 2025 20:31:30 -0500 Subject: add support for textDocument/prepareRename --- command.c | 5 +++-- ide-rename-symbol.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++------ lsp-parse.c | 38 +++++++++++++++++++++++++++++++++++- lsp-write.c | 8 ++++++++ lsp.c | 8 ++++++++ lsp.h | 17 ++++++++++++++++ main.c | 1 - ted.h | 2 ++ 8 files changed, 125 insertions(+), 10 deletions(-) diff --git a/command.c b/command.c index c1e9e3a..4f8e7ea 100644 --- a/command.c +++ b/command.c @@ -705,8 +705,9 @@ void command_execute_ex(Ted *ted, Command c, const CommandArgument *full_argumen macro_execute(ted, (u32)argument); break; case CMD_RENAME_SYMBOL: - if (buffer && buffer_lsp(buffer)) - menu_open(ted, MENU_RENAME_SYMBOL); + if (buffer && buffer_lsp(buffer)) { + rename_symbol_start(ted); + } break; case CMD_FORMAT_FILE: format_file(ted); diff --git a/ide-rename-symbol.c b/ide-rename-symbol.c index ff96311..dbbed0f 100644 --- a/ide-rename-symbol.c +++ b/ide-rename-symbol.c @@ -2,11 +2,17 @@ struct RenameSymbol { LSPServerRequestID request_id; + bool sent_rename; + bool have_range; + BufferPos range_start; + BufferPos range_end; }; static void rename_symbol_clear(Ted *ted) { RenameSymbol *rs = ted->rename_symbol; ted_cancel_lsp_request(ted, &rs->request_id); + rs->sent_rename = false; + rs->have_range = false; } void rename_symbol_quit(Ted *ted) { @@ -15,21 +21,37 @@ void rename_symbol_quit(Ted *ted) { ted->rename_symbol = NULL; } +void rename_symbol_start(Ted *ted) { + RenameSymbol *rs = ted->rename_symbol; + TextBuffer *buffer = ted_active_buffer(ted); + LSP *lsp = ted_active_lsp(ted); + if (!buffer || !lsp) { + ted_flash_error_cursor(ted); + return; + } + if (lsp_has_prepare_rename(lsp)) { + LSPRequest request = {.type = LSP_REQUEST_PREPARE_RENAME}; + LSPRequestPrepareRename *data = &request.data.prepare_rename; + data->position = buffer_cursor_pos_as_lsp_document_position(buffer); + rs->request_id = lsp_send_request(lsp, &request); + } + menu_open(ted, MENU_RENAME_SYMBOL); +} + void rename_symbol_at_cursor(Ted *ted, TextBuffer *buffer, const char *new_name) { if (!buffer) return; RenameSymbol *rs = ted->rename_symbol; LSP *lsp = buffer_lsp(buffer); if (!lsp) return; - - if (!rs->request_id.id) { + if (!rs->sent_rename) { // send the request LSPRequest request = {.type = LSP_REQUEST_RENAME}; LSPRequestRename *data = &request.data.rename; data->position = buffer_cursor_pos_as_lsp_document_position(buffer); data->new_name = lsp_request_add_string(&request, new_name); rs->request_id = lsp_send_request(lsp, &request); + rs->sent_rename = true; } - } void rename_symbol_frame(Ted *ted) { @@ -61,7 +83,7 @@ static void rename_symbol_menu_render(Ted *ted) { menu_close(ted); return; } - if (rs->request_id.id) { + if (rs->sent_rename) { // already entered a new name return; } @@ -80,6 +102,10 @@ static void rename_symbol_menu_render(Ted *ted) { .line = cursor_pos.line, .index = sym_end }; + if (rs->have_range) { + bpos0 = rs->range_start; + bpos1 = rs->range_end; + } // symbol should span from pos0 to pos1 vec2 p0 = buffer_pos_to_pixels(buffer, bpos0); vec2 p1 = buffer_pos_to_pixels(buffer, bpos1); @@ -114,10 +140,28 @@ static bool rename_symbol_menu_close(Ted *ted) { void rename_symbol_process_lsp_response(Ted *ted, const LSPResponse *response) { RenameSymbol *rs = ted->rename_symbol; - if (response->request.type != LSP_REQUEST_RENAME - || response->request.id != rs->request_id.id) { + if (response->request.id != rs->request_id.id) { return; } + if (response->request.type == LSP_REQUEST_PREPARE_RENAME) { + if (lsp_response_is_error(response)) { + // prepareRename returned an error + menu_close(ted); + ted_flash_error_cursor(ted); + return; + } + const LSPResponsePrepareRename *prep = &response->data.prepare_rename; + if (prep->use_range) { + TextBuffer *buffer = ted_active_buffer_behind_menu(ted); + if (!buffer) return; + rs->range_start = buffer_pos_from_lsp(buffer, prep->range.start); + rs->range_end = buffer_pos_from_lsp(buffer, prep->range.end); + rs->have_range = true; + } + return; + } + if (response->request.type != LSP_REQUEST_RENAME) + return; LSP *lsp = ted_get_lsp_by_id(ted, rs->request_id.lsp); if (menu_is_open(ted, MENU_RENAME_SYMBOL)) diff --git a/lsp-parse.c b/lsp-parse.c index 72c6b88..5a38453 100644 --- a/lsp-parse.c +++ b/lsp-parse.c @@ -249,9 +249,15 @@ static void parse_capabilities(LSP *lsp, const JSON *json, JSONObject capabiliti // check for textDocument/rename support JSONValue rename_value = json_object_get(json, capabilities, "renameProvider"); if (rename_value.type != JSON_UNDEFINED && rename_value.type != JSON_FALSE) { + if (rename_value.type == JSON_OBJECT) { + JSONValue rename_prepare_value = json_object_get(json, rename_value.val.object, "prepareProvider"); + if (rename_prepare_value.type != JSON_UNDEFINED && rename_prepare_value.type != JSON_FALSE) { + cap->prepare_rename_support = true; + } + } cap->rename_support = true; } - + // check for textDocument/documentLink support JSONValue document_link_value = json_object_get(json, capabilities, "documentLinkProvider"); if (document_link_value.type == JSON_OBJECT) { @@ -938,6 +944,33 @@ static bool parse_rename_response(LSP *lsp, const JSON *json, LSPResponse *respo return parse_workspace_edit(lsp, response, json, result, &response->data.rename); } +static bool parse_prepare_rename_response(LSP *lsp, const JSON *json, LSPResponse *response) { + LSPResponsePrepareRename *prep = &response->data.prepare_rename; + JSONValue result_value = json_get(json, "result"); + if (result_value.type == JSON_NULL) + return false; // invalid rename + if (result_value.type != JSON_OBJECT) { + lsp_set_error(lsp, "Bad result type for textDocument/prepareRename response."); + return false; + } + JSONObject result = result_value.val.object; + JSONValue range_value = json_object_get(json, result, "range"); + if (json_object_get(json, result, "start").type != JSON_UNDEFINED) { + // result is a Range + prep->use_range = true; + return parse_range(lsp, json, result_value, &prep->range); + } else if (range_value.type != JSON_UNDEFINED) { + prep->use_range = true; + return parse_range(lsp, json, range_value, &prep->range); + } else if (json_object_get(json, result, "defaultBehavior").type == JSON_TRUE) { + prep->use_range = false; + return true; + } else { + lsp_set_error(lsp, "Unrecognizable response for textDocument/prepareRename."); + return false; + } +} + static bool parse_highlight_response(LSP *lsp, const JSON *json, LSPResponse *response) { LSPResponseHighlight *hl = &response->data.highlight; JSONArray result = json_force_array(json_get(json, "result")); @@ -1121,6 +1154,9 @@ void process_message(LSP *lsp, JSON *json) { case LSP_REQUEST_RENAME: add_to_messages = parse_rename_response(lsp, json, &response); break; + case LSP_REQUEST_PREPARE_RENAME: + add_to_messages = parse_prepare_rename_response(lsp, json, &response); + break; case LSP_REQUEST_FORMATTING: case LSP_REQUEST_RANGE_FORMATTING: add_to_messages = parse_formatting_response(lsp, json, &response); diff --git a/lsp-write.c b/lsp-write.c index 9788347..506369e 100644 --- a/lsp-write.c +++ b/lsp-write.c @@ -297,6 +297,8 @@ static const char *lsp_request_method(LSPRequest *request) { return "textDocument/documentLink"; case LSP_REQUEST_RENAME: return "textDocument/rename"; + case LSP_REQUEST_PREPARE_RENAME: + return "textDocument/prepareRename"; case LSP_REQUEST_WORKSPACE_FOLDERS: return "workspace/workspaceFolders"; case LSP_REQUEST_DID_CHANGE_WORKSPACE_FOLDERS: @@ -614,6 +616,12 @@ void write_request(LSP *lsp, LSPRequest *request, StrBuilder *builder) { write_key_string(o, "newName", lsp_request_string(request, rename->new_name)); write_obj_end(o); } break; + case LSP_REQUEST_PREPARE_RENAME: { + const LSPRequestPrepareRename *prep = &request->data.prepare_rename; + write_key_obj_start(o, "params"); + write_document_position(o, prep->position); + write_obj_end(o); + } break; case LSP_REQUEST_WORKSPACE_SYMBOLS: { const LSPRequestWorkspaceSymbols *syms = &request->data.workspace_symbols; write_key_obj_start(o, "params"); diff --git a/lsp.c b/lsp.c index 7de51c3..b1fe8e0 100644 --- a/lsp.c +++ b/lsp.c @@ -133,6 +133,7 @@ void lsp_request_free(LSPRequest *r) { case LSP_REQUEST_SHOW_MESSAGE: case LSP_REQUEST_LOG_MESSAGE: case LSP_REQUEST_RENAME: + case LSP_REQUEST_PREPARE_RENAME: case LSP_REQUEST_WORKSPACE_SYMBOLS: break; case LSP_REQUEST_DID_CHANGE: { @@ -263,6 +264,8 @@ static bool lsp_supports_request(LSP *lsp, const LSPRequest *request) { return cap->workspace_symbols_support; case LSP_REQUEST_RENAME: return cap->rename_support; + case LSP_REQUEST_PREPARE_RENAME: + return cap->prepare_rename_support; case LSP_REQUEST_HIGHLIGHT: return cap->highlight_support; case LSP_REQUEST_REFERENCES: @@ -311,6 +314,7 @@ static bool request_type_is_notification(LSPRequestType type) { case LSP_REQUEST_IMPLEMENTATION: case LSP_REQUEST_REFERENCES: case LSP_REQUEST_RENAME: + case LSP_REQUEST_PREPARE_RENAME: case LSP_REQUEST_WORKSPACE_SYMBOLS: case LSP_REQUEST_WORKSPACE_FOLDERS: case LSP_REQUEST_DOCUMENT_LINK: @@ -946,6 +950,10 @@ bool lsp_has_incremental_sync_support(LSP *lsp) { return lsp->capabilities.incremental_sync_support; } +bool lsp_has_prepare_rename(LSP *lsp) { + return lsp->capabilities.prepare_rename_support; +} + const char *lsp_get_command(LSP *lsp) { return lsp->command; } diff --git a/lsp.h b/lsp.h index 37370bc..66f24de 100644 --- a/lsp.h +++ b/lsp.h @@ -86,6 +86,7 @@ typedef enum { LSP_REQUEST_HIGHLIGHT, //< textDocument/documentHighlight LSP_REQUEST_REFERENCES, //< textDocument/references LSP_REQUEST_RENAME, //< textDocument/rename + LSP_REQUEST_PREPARE_RENAME, //< textDocument/prepareRename LSP_REQUEST_DOCUMENT_LINK, //< textDocument/documentLink LSP_REQUEST_FORMATTING, //< textDocument/formatting LSP_REQUEST_RANGE_FORMATTING, //< textDocument/rangeFormatting @@ -232,6 +233,10 @@ typedef struct { LSPString new_name; } LSPRequestRename; +typedef struct { + LSPDocumentPosition position; +} LSPRequestPrepareRename; + typedef struct { LSPDocumentID *removed; // dynamic array LSPDocumentID *added; // dynamic array @@ -286,6 +291,7 @@ typedef struct { LSPRequestMessage message; LSPRequestDidChangeWorkspaceFolders change_workspace_folders; LSPRequestRename rename; + LSPRequestPrepareRename prepare_rename; LSPRequestDocumentLink document_link; LSPRequestPublishDiagnostics publish_diagnostics; // LSP_REQUEST_FORMATTING and LSP_REQUEST_RANGE_FORMATTING @@ -538,6 +544,13 @@ typedef struct { } LSPWorkspaceEdit; typedef LSPWorkspaceEdit LSPResponseRename; +typedef struct { + // if false, the rename is valid but no range should be highlighted + bool use_range; + // range to highlight + LSPRange range; +} LSPResponsePrepareRename; + typedef struct { LSPRange range; LSPString target; @@ -567,6 +580,7 @@ typedef struct { LSPResponseDefinition definition; LSPResponseWorkspaceSymbols workspace_symbols; LSPResponseRename rename; + LSPResponsePrepareRename prepare_rename; LSPResponseHighlight highlight; LSPResponseReferences references; LSPResponseDocumentLink document_link; @@ -610,6 +624,7 @@ typedef struct { // (but jdtls and gopls do) bool workspace_folders_support; bool rename_support; + bool prepare_rename_support; bool references_support; bool document_link_support; bool formatting_support; @@ -702,6 +717,8 @@ bool lsp_document_position_eq(LSPDocumentPosition a, LSPDocumentPosition b); /// see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_synchronization /// for more info. bool lsp_has_incremental_sync_support(LSP *lsp); +/// does this server support textDocument/prepareRename requests? +bool lsp_has_prepare_rename(LSP *lsp); /// get dynamic array of completion trigger characters. const uint32_t *lsp_completion_trigger_chars(LSP *lsp); /// get dynamic array of signature help trigger characters. diff --git a/main.c b/main.c index 1839f56..3ca8029 100644 --- a/main.c +++ b/main.c @@ -2,7 +2,6 @@ FUTURE FEATURES: - path-specific extensions - more tests -- prepare rename support - config variables - bind key to series of commands - convert macro to command list diff --git a/ted.h b/ted.h index da4144e..44527ea 100644 --- a/ted.h +++ b/ted.h @@ -945,6 +945,8 @@ void format_file(Ted *ted); void hover_reset_timer(Ted *ted); // === ide-rename-symbol.c === +/// Start renaming process (open rename menu when LSP is ready) +void rename_symbol_start(Ted *ted); /// rename symbol at cursor of `buffer` to `new_name` void rename_symbol_at_cursor(Ted *ted, TextBuffer *buffer, const char *new_name); -- cgit v1.2.3