summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2022-12-29 18:44:37 -0500
committerpommicket <pommicket@gmail.com>2022-12-29 18:44:37 -0500
commitd77782564bf0a090a4e7fa4f4c4bb685383275dc (patch)
treea4bc3c0bf142c663ea7f293b4381927adc896f1c
parent5f7af06a9085835a3d1ad3a51374c245859d7d97 (diff)
textDocument/definition for ctrl+click
-rw-r--r--buffer.c4
-rw-r--r--ide-autocomplete.c (renamed from autocomplete.c)0
-rw-r--r--ide-definitions.c41
-rw-r--r--ide-hover.c (renamed from hover.c)0
-rw-r--r--ide-signature-help.c (renamed from signature-help.c)0
-rw-r--r--lsp-json.c (renamed from json.c)4
-rw-r--r--lsp-parse.c73
-rw-r--r--lsp-write.c13
-rw-r--r--lsp.c25
-rw-r--r--lsp.h22
-rw-r--r--main.c13
-rw-r--r--ted.c33
-rw-r--r--ted.h19
13 files changed, 234 insertions, 13 deletions
diff --git a/buffer.c b/buffer.c
index 4562219..679da65 100644
--- a/buffer.c
+++ b/buffer.c
@@ -2489,12 +2489,14 @@ bool buffer_handle_click(Ted *ted, TextBuffer *buffer, v2 click, u8 times) {
break;
case KEY_MODIFIER_CTRL:
if (!buffer->is_line_buffer) {
+ // go to definition
buffer_cursor_move_to_pos(buffer, buffer_pos);
String32 word = buffer_word_at_cursor(buffer);
if (word.len) {
char *tag = str32_to_utf8_cstr(word);
if (tag) {
- tag_goto(buffer->ted, tag);
+ LSPDocumentPosition pos = buffer_pos_to_lsp_document_position(buffer, buffer_pos);
+ definition_goto(buffer->ted, buffer_lsp(buffer), tag, pos);
free(tag);
}
}
diff --git a/autocomplete.c b/ide-autocomplete.c
index f1c3c13..f1c3c13 100644
--- a/autocomplete.c
+++ b/ide-autocomplete.c
diff --git a/ide-definitions.c b/ide-definitions.c
new file mode 100644
index 0000000..3a36a96
--- /dev/null
+++ b/ide-definitions.c
@@ -0,0 +1,41 @@
+void definition_goto(Ted *ted, LSP *lsp, const char *name, LSPDocumentPosition position) {
+ if (lsp) {
+ // send that request
+ LSPRequest request = {.type = LSP_REQUEST_DEFINITION};
+ request.data.definition.position = position;
+ lsp_send_request(lsp, &request);
+ } else {
+ // just go to the tag
+ tag_goto(ted, name);
+ }
+}
+
+void definitions_process_lsp_response(Ted *ted, LSP *lsp, const LSPResponse *response) {
+ if (response->request.type != LSP_REQUEST_DEFINITION)
+ return;
+
+ const LSPResponseDefinition *response_def = &response->data.definition;
+ Definitions *defs = &ted->definitions;
+
+ if (defs->last_response_lsp == lsp->id
+ && response->request.id < defs->last_response_id) {
+ // we just processed a later response, so let's ignore this
+ return;
+ }
+ defs->last_response_lsp = lsp->id;
+ defs->last_response_id = response->request.id;
+
+ if (!arr_len(response_def->locations)) {
+ // no definition. do the error cursor.
+ ted_flash_error_cursor(ted);
+ return;
+ }
+ LSPLocation location = response_def->locations[0];
+ const char *path = lsp_document_path(lsp, location.document);
+ if (!ted_open_file(ted, path)) {
+ ted_flash_error_cursor(ted);
+ return;
+ }
+ LSPDocumentPosition position = lsp_location_start_position(location);
+ ted_go_to_lsp_document_position(ted, lsp, position);
+}
diff --git a/hover.c b/ide-hover.c
index 9742e58..9742e58 100644
--- a/hover.c
+++ b/ide-hover.c
diff --git a/signature-help.c b/ide-signature-help.c
index 97dc8c3..97dc8c3 100644
--- a/signature-help.c
+++ b/ide-signature-help.c
diff --git a/json.c b/lsp-json.c
index 242eb1f..5bf50c6 100644
--- a/json.c
+++ b/lsp-json.c
@@ -545,6 +545,10 @@ JSONArray json_array_get_array(const JSON *json, JSONArray array, size_t i) {
return json_force_array(json_array_get(json, array, i));
}
+JSONValue json_root(const JSON *json) {
+ return json->values[0];
+}
+
// e.g. if json is { "a" : { "b": 3 }}, then json_get(json, "a.b") = 3.
// returns undefined if there is no such property
JSONValue json_get(const JSON *json, const char *path) {
diff --git a/lsp-parse.c b/lsp-parse.c
index 07837b5..a892539 100644
--- a/lsp-parse.c
+++ b/lsp-parse.c
@@ -25,6 +25,7 @@ static WarnUnusedResult bool lsp_expect_number(LSP *lsp, JSONValue value, const
return lsp_expect_type(lsp, value, JSON_NUMBER, what);
}
+
static LSPString lsp_response_add_json_string(LSPResponse *response, const JSON *json, JSONString string) {
u32 offset = arr_len(response->string_data);
arr_set_len(response->string_data, offset + string.len + 1);
@@ -80,6 +81,24 @@ static bool parse_range(LSP *lsp, const JSON *json, JSONValue range_value, LSPRa
return success;
}
+static bool parse_document_uri(LSP *lsp, const JSON *json, JSONValue value, LSPDocumentID *id) {
+ if (value.type != JSON_STRING) {
+ lsp_set_error(lsp, "Expected string for URI, got %s",
+ json_type_to_str(value.type));
+ return false;
+ }
+ char *string = json_string_get_alloc(json, value.val.string);
+ if (!str_has_prefix(string, "file://")) {
+ lsp_set_error(lsp, "Can't process non-local URI %s",
+ string);
+ free(string);
+ return false;
+ }
+ *id = lsp_document_id(lsp, string + strlen("file://"));
+ free(string);
+ return true;
+}
+
static uint32_t *parse_trigger_characters(const JSON *json, JSONArray trigger_chars) {
uint32_t *array = NULL;
@@ -136,6 +155,12 @@ static void parse_capabilities(LSP *lsp, const JSON *json, JSONObject capabiliti
cap->hover_support = true;
}
+ // check for definition support
+ JSONValue definition_value = json_object_get(json, capabilities, "definitionProvider");
+ if (definition_value.type != JSON_UNDEFINED) {
+ cap->definition_support = true;
+ }
+
JSONObject workspace = json_object_get_object(json, capabilities, "workspace");
// check WorkspaceFoldersServerCapabilities
JSONObject workspace_folders = json_object_get_object(json, workspace, "workspaceFolders");
@@ -208,7 +233,7 @@ static bool parse_completion(LSP *lsp, const JSON *json, LSPResponse *response)
item->text_edit = (LSPTextEdit) {
.type = LSP_TEXT_EDIT_PLAIN,
.at_cursor = true,
- .range = {{0}},
+ .range = {{0}, {0}},
.new_text = item->label
};
@@ -462,6 +487,47 @@ static bool parse_hover(LSP *lsp, const JSON *json, LSPResponse *response) {
return true;
}
+// parse a Location: {uri: DocumentUri, range: Range}
+static bool parse_location(LSP *lsp, const JSON *json, JSONValue value, LSPLocation *location) {
+ if (value.type != JSON_OBJECT) {
+ lsp_set_error(lsp, "Expected object for location but got %s",
+ json_type_to_str(value.type));
+ return false;
+ }
+ JSONObject object = value.val.object;
+ JSONValue uri = json_object_get(json, object, "uri");
+ if (!parse_document_uri(lsp, json, uri, &location->document))
+ return false;
+ JSONValue range = json_object_get(json, object, "range");
+ if (!parse_range(lsp, json, range, &location->range))
+ return false;
+ return true;
+}
+
+static bool parse_definition(LSP *lsp, const JSON *json, LSPResponse *response) {
+ JSONValue result = json_get(json, "result");
+ if (result.type == JSON_NULL) {
+ // no location
+ return true;
+ }
+ LSPResponseDefinition *definition = &response->data.definition;
+ if (result.type == JSON_ARRAY) {
+ JSONArray locations = result.val.array;
+ if (locations.len == 0)
+ return true;
+ for (u32 l = 0; l < locations.len; ++l) {
+ JSONValue location_json = json_array_get(json, locations, l);
+ LSPLocation *location = arr_addp(definition->locations);
+ if (!parse_location(lsp, json, location_json, location))
+ return false;
+ }
+ return true;
+ } else {
+ LSPLocation *location = arr_addp(definition->locations);
+ return parse_location(lsp, json, result, location);
+ }
+}
+
// fills request->id/id_string appropriately given the request's json
// returns true on success
static WarnUnusedResult bool parse_id(JSON *json, LSPRequest *request) {
@@ -552,6 +618,8 @@ static bool parse_server2client_request(LSP *lsp, JSON *json, LSPRequest *reques
return false;
}
+
+
static void process_message(LSP *lsp, JSON *json) {
#if 0
@@ -601,6 +669,9 @@ static void process_message(LSP *lsp, JSON *json) {
case LSP_REQUEST_HOVER:
add_to_messages = parse_hover(lsp, json, &response);
break;
+ case LSP_REQUEST_DEFINITION:
+ add_to_messages = parse_definition(lsp, json, &response);
+ break;
case LSP_REQUEST_INITIALIZE: {
// it's the response to our initialize request!
if (result.type == JSON_OBJECT) {
diff --git a/lsp-write.c b/lsp-write.c
index 7db0f66..7d8a0f1 100644
--- a/lsp-write.c
+++ b/lsp-write.c
@@ -1,3 +1,5 @@
+#define write_bool lsp_write_bool // prevent naming conflict
+
static const char *lsp_language_id(Language lang) {
switch (lang) {
case LANG_CONFIG:
@@ -267,6 +269,8 @@ static const char *lsp_request_method(LSPRequest *request) {
return "textDocument/signatureHelp";
case LSP_REQUEST_HOVER:
return "textDocument/hover";
+ case LSP_REQUEST_DEFINITION:
+ return "textDocument/definition";
case LSP_REQUEST_WORKSPACE_FOLDERS:
return "workspace/workspaceFolders";
case LSP_REQUEST_DID_CHANGE_WORKSPACE_FOLDERS:
@@ -296,6 +300,7 @@ static bool request_type_is_notification(LSPRequestType type) {
case LSP_REQUEST_COMPLETION:
case LSP_REQUEST_SIGNATURE_HELP:
case LSP_REQUEST_HOVER:
+ case LSP_REQUEST_DEFINITION:
case LSP_REQUEST_WORKSPACE_FOLDERS:
return false;
}
@@ -512,6 +517,12 @@ static void write_request(LSP *lsp, LSPRequest *request) {
write_document_position(o, hover->position);
write_obj_end(o);
} break;
+ case LSP_REQUEST_DEFINITION: {
+ const LSPRequestDefinition *def = &request->data.definition;
+ write_key_obj_start(o, "params");
+ write_document_position(o, def->position);
+ write_obj_end(o);
+ } break;
case LSP_REQUEST_DID_CHANGE_WORKSPACE_FOLDERS: {
const LSPRequestDidChangeWorkspaceFolders *w = &request->data.change_workspace_folders;
write_key_obj_start(o, "params");
@@ -605,3 +616,5 @@ static void write_message(LSP *lsp, LSPMessage *message) {
// (as i'm writing this, this won't do anything but it might in the future)
lsp_message_free(message);
}
+
+#undef write_bool
diff --git a/lsp.c b/lsp.c
index 50947ce..830f90d 100644
--- a/lsp.c
+++ b/lsp.c
@@ -3,8 +3,6 @@
// print client-to-server communication
#define LSP_SHOW_C2S 0
-#define write_bool lsp_write_bool
-
static void lsp_request_free(LSPRequest *r);
static void lsp_response_free(LSPResponse *r);
@@ -13,6 +11,7 @@ static void lsp_response_free(LSPResponse *r);
strbuf_printf(lsp->error, __VA_ARGS__);\
SDL_UnlockMutex(lsp->error_mutex);\
} while (0)
+#include "lsp-json.c"
#include "lsp-write.c"
#include "lsp-parse.c"
@@ -44,6 +43,7 @@ static void lsp_request_free(LSPRequest *r) {
case LSP_REQUEST_COMPLETION:
case LSP_REQUEST_SIGNATURE_HELP:
case LSP_REQUEST_HOVER:
+ case LSP_REQUEST_DEFINITION:
case LSP_REQUEST_DID_CLOSE:
case LSP_REQUEST_WORKSPACE_FOLDERS:
case LSP_REQUEST_JDTLS_CONFIGURATION:
@@ -80,6 +80,9 @@ static void lsp_response_free(LSPResponse *r) {
case LSP_REQUEST_SIGNATURE_HELP:
arr_free(r->data.signature_help.signatures);
break;
+ case LSP_REQUEST_DEFINITION:
+ arr_free(r->data.definition.locations);
+ break;
default:
break;
}
@@ -145,6 +148,8 @@ static bool lsp_supports_request(LSP *lsp, const LSPRequest *request) {
return cap->workspace_folders_support;
case LSP_REQUEST_HOVER:
return cap->hover_support;
+ case LSP_REQUEST_DEFINITION:
+ return cap->definition_support;
}
assert(0);
return false;
@@ -542,4 +547,18 @@ bool lsp_document_position_eq(LSPDocumentPosition a, LSPDocumentPosition b) {
return a.document == b.document && lsp_position_eq(a.pos, b.pos);
}
-#undef write_bool
+
+LSPDocumentPosition lsp_location_start_position(LSPLocation location) {
+ return (LSPDocumentPosition) {
+ .document = location.document,
+ .pos = location.range.start
+ };
+}
+
+LSPDocumentPosition lsp_location_end_position(LSPLocation location) {
+ return (LSPDocumentPosition) {
+ .document = location.document,
+ .pos = location.range.end
+ };
+}
+
diff --git a/lsp.h b/lsp.h
index 389f38e..b97df7d 100644
--- a/lsp.h
+++ b/lsp.h
@@ -26,6 +26,11 @@ typedef struct {
LSPPosition end;
} LSPRange;
+typedef struct {
+ LSPDocumentID document;
+ LSPRange range;
+} LSPLocation;
+
typedef enum {
LSP_REQUEST_NONE,
@@ -44,6 +49,7 @@ typedef enum {
LSP_REQUEST_COMPLETION, // textDocument/completion
LSP_REQUEST_SIGNATURE_HELP, // textDocument/signatureHelp
LSP_REQUEST_HOVER, // textDocument/hover
+ LSP_REQUEST_DEFINITION, // textDocument/definition
LSP_REQUEST_DID_CHANGE_WORKSPACE_FOLDERS, // workspace/didChangeWorkspaceFolders
// server-to-client
@@ -117,6 +123,10 @@ typedef struct {
} LSPRequestHover;
typedef struct {
+ LSPDocumentPosition position;
+} LSPRequestDefinition;
+
+typedef struct {
LSPDocumentID *removed; // dynamic array
LSPDocumentID *added; // dynamic array
} LSPRequestDidChangeWorkspaceFolders;
@@ -134,6 +144,7 @@ typedef struct {
LSPRequestCompletion completion;
LSPRequestSignatureHelp signature_help;
LSPRequestHover hover;
+ LSPRequestDefinition definition;
// LSP_REQUEST_SHOW_MESSAGE or LSP_REQUEST_LOG_MESSAGE
LSPRequestMessage message;
LSPRequestDidChangeWorkspaceFolders change_workspace_folders;
@@ -279,6 +290,10 @@ typedef struct {
LSPString contents;
} LSPResponseHover;
+typedef struct {
+ // where the symbol is defined (dynamic array)
+ LSPLocation *locations;
+} LSPResponseDefinition;
typedef LSPRequestType LSPResponseType;
typedef struct {
@@ -292,6 +307,7 @@ typedef struct {
LSPResponseCompletion completion;
LSPResponseSignatureHelp signature_help;
LSPResponseHover hover;
+ LSPResponseDefinition definition;
} data;
} LSPResponse;
@@ -312,6 +328,7 @@ typedef struct {
bool signature_help_support;
bool completion_support;
bool hover_support;
+ bool definition_support;
// support for multiple root folders
// sadly, as of me writing this, clangd and rust-analyzer don't support this
// (but jdtls and gopls do)
@@ -398,8 +415,11 @@ LSP *lsp_create(const char *root_dir, Language language, const char *analyzer_co
// if this fails (i.e. if the LSP does not have workspace support), create a new LSP
// with root directory `new_root_dir`.
bool lsp_try_add_root_dir(LSP *lsp, const char *new_root_dir);
+// report that this document has changed
+void lsp_document_changed(LSP *lsp, const char *document, LSPDocumentChangeEvent change);
bool lsp_next_message(LSP *lsp, LSPMessage *message);
bool lsp_position_eq(LSPPosition a, LSPPosition b);
bool lsp_document_position_eq(LSPDocumentPosition a, LSPDocumentPosition b);
-void lsp_document_changed(LSP *lsp, const char *document, LSPDocumentChangeEvent change);
+LSPDocumentPosition lsp_location_start_position(LSPLocation location);
+LSPDocumentPosition lsp_location_end_position(LSPLocation location);
void lsp_free(LSP *lsp);
diff --git a/main.c b/main.c
index 7fe7847..c02774f 100644
--- a/main.c
+++ b/main.c
@@ -1,5 +1,7 @@
/*
@TODO:
+- check definition capabilities
+- some way of showing that we're currently loading the definition location (different cursor color?)
- more LSP stuff:
- go to definition using LSP
- find usages
@@ -13,9 +15,11 @@
- what to do if initialize request takes a long time?
- delete old sent requests? but make sure requests that just take a long time are okay.
(if the server never sends a response)
+- check that tags still works
- TESTING: make rust-analyzer-slow (waits 10s before sending response)
- run everything through valgrind ideally with leak checking
- grep -i -n TODO *.[ch]
+- when searching files/definitions, sort by length? or put exact matches at the top?
--- LSP MERGE ---
- improve structure of ted source code to make LSP completions better
(make every c file a valid translation unit)
@@ -140,13 +144,13 @@ bool tag_goto(Ted *ted, char const *tag);
#include "build.c"
#include "tags.c"
#include "menu.c"
-#include "autocomplete.c"
-#include "signature-help.c"
-#include "hover.c"
+#include "ide-autocomplete.c"
+#include "ide-signature-help.c"
+#include "ide-hover.c"
+#include "ide-definitions.c"
#include "command.c"
#include "config.c"
#include "session.c"
-#include "json.c"
#include "lsp.c"
#if PROFILE
@@ -891,6 +895,7 @@ int main(int argc, char **argv) {
autocomplete_process_lsp_response(ted, r);
signature_help_process_lsp_response(ted, r);
hover_process_lsp_response(ted, r);
+ definitions_process_lsp_response(ted, lsp, r);
} break;
}
lsp_message_free(&message);
diff --git a/ted.c b/ted.c
index 6fd2906..4a89e8f 100644
--- a/ted.c
+++ b/ted.c
@@ -507,3 +507,36 @@ void ted_press_key(Ted *ted, SDL_Scancode scancode, SDL_Keymod modifier) {
}
}
}
+
+// make the cursor red for a bit to indicate an error (e.g. no autocompletions)
+void ted_flash_error_cursor(Ted *ted) {
+ ted->cursor_error_time = time_get_seconds();
+}
+
+void ted_go_to_position(Ted *ted, const char *path, u32 line, u32 index, bool is_lsp) {
+ if (ted_open_file(ted, path)) {
+ TextBuffer *buffer = ted->active_buffer;
+ BufferPos pos = {0};
+ if (is_lsp) {
+ LSPPosition lsp_pos = {
+ .line = line,
+ .character = index
+ };
+ pos = buffer_pos_from_lsp(buffer, lsp_pos);
+ } else {
+ pos.line = line;
+ pos.index = index;
+ }
+ buffer_cursor_move_to_pos(buffer, pos);
+ buffer->center_cursor_next_frame = true;
+ } else {
+ ted_flash_error_cursor(ted);
+ }
+}
+
+void ted_go_to_lsp_document_position(Ted *ted, LSP *lsp, LSPDocumentPosition position) {
+ const char *path = lsp_document_path(lsp, position.document);
+ u32 line = position.pos.line;
+ u32 character = position.pos.character;
+ ted_go_to_position(ted, path, line, character, true);
+}
diff --git a/ted.h b/ted.h
index 774b5f5..be8fcb0 100644
--- a/ted.h
+++ b/ted.h
@@ -413,6 +413,14 @@ typedef struct {
BufferPos range_end;
} Hover;
+typedef struct {
+
+ // LSPID and ID of the last response which was processed.
+ // used to process responses in chronological order (= ID order)
+ LSPID last_response_lsp;
+ u32 last_response_id;
+} Definitions;
+
typedef struct Ted {
struct LSP *lsps[TED_LSP_MAX + 1];
@@ -470,6 +478,7 @@ typedef struct Ted {
Autocomplete autocomplete;
SignatureHelp signature_help;
Hover hover;
+ Definitions definitions;
FILE *log;
@@ -530,9 +539,6 @@ typedef struct Ted {
char error[512];
char error_shown[512]; // error display in box on screen
} Ted;
-
-void autocomplete_close(Ted *ted);
-void signature_help_retrigger(Ted *ted);
char *buffer_contents_utf8_alloc(TextBuffer *buffer);
Command command_from_str(char const *str);
void command_execute(Ted *ted, Command c, i64 argument);
@@ -566,3 +572,10 @@ char *settings_get_root_dir(Settings *settings, const char *path);
void menu_open(Ted *ted, Menu menu);
void menu_close(Ted *ted);
void find_update(Ted *ted, bool force);
+void autocomplete_close(Ted *ted);
+void signature_help_retrigger(Ted *ted);
+// go to the definition of `name`.
+// if `lsp` is NULL, tags will be used.
+// Note: the document position is required for LSP requests because of overloading (where the name
+// alone isn't sufficient)
+void definition_goto(Ted *ted, LSP *lsp, const char *name, LSPDocumentPosition pos);