From 45d69107725a02cf6d912a571b771da3ef5abfbf Mon Sep 17 00:00:00 2001 From: pommicket Date: Fri, 4 Aug 2023 19:35:48 -0400 Subject: textDocument/documentLink parsing --- CMakeLists.txt | 6 ++--- ide-document-link.c | 49 +++++++++++++++++++++++++++++++++++ lsp-json.c | 1 + lsp-parse.c | 66 +++++++++++++++++++++++++++++++++++------------ lsp-write.c | 15 +++++++++++ lsp.c | 7 +++++ lsp.h | 18 +++++++++++++ main.c | 5 ++-- ted.h | 12 +++++++++ test/lsp/go/main.go | 1 + test/lsp/rust/.gitignore | 1 + test/lsp/rust/Cargo.lock | 7 +++++ test/lsp/rust/Cargo.toml | 8 ++++++ test/lsp/rust/src/main.rs | 3 +++ test/test.html | 6 +++++ 15 files changed, 184 insertions(+), 21 deletions(-) create mode 100644 ide-document-link.c create mode 100644 test/lsp/rust/.gitignore create mode 100644 test/lsp/rust/Cargo.lock create mode 100644 test/lsp/rust/Cargo.toml create mode 100644 test/lsp/rust/src/main.rs create mode 100644 test/test.html diff --git a/CMakeLists.txt b/CMakeLists.txt index e808041..32a8b50 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,9 +2,9 @@ cmake_minimum_required(VERSION 3.1) project(ted) if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(SOURCES buffer.c build.c colors.c command.c config.c find.c gl.c ide-autocomplete.c - ide-definitions.c ide-highlights.c ide-hover.c ide-signature-help.c ide-usages.c - lsp.c lsp-json.c lsp-parse.c lsp-write.c main.c menu.c node.c os.c session.c - stb_image.c stb_truetype.c syntax.c tags.c ted.c text.c ui.c util.c macro.c) + ide-document-link.c ide-definitions.c ide-highlights.c ide-hover.c ide-signature-help.c + ide-usages.c lsp.c lsp-json.c lsp-parse.c lsp-write.c main.c menu.c node.c os.c + session.c stb_image.c stb_truetype.c syntax.c tags.c ted.c text.c ui.c util.c macro.c) else() set(SOURCES main.c) endif() diff --git a/ide-document-link.c b/ide-document-link.c new file mode 100644 index 0000000..d0e1ef8 --- /dev/null +++ b/ide-document-link.c @@ -0,0 +1,49 @@ +#include "ted.h" + +void document_link_frame(Ted *ted) { + DocumentLink *document_link = &ted->document_link; + + bool key_down = ted_is_ctrl_down(ted); + if (!key_down) { + document_link->key_press_time = 0.0; + } else if (document_link->key_press_time == 0.0) { + document_link->key_press_time = ted->frame_time; + } + + bool show_links = document_link->key_press_time != 0.0 + && ted->frame_time - document_link->key_press_time > 1.0; + + if (!show_links) { + ted_cancel_lsp_request(ted, &document_link->last_request); + return; + } + + TextBuffer *buffer = ted->active_buffer; + if (!buffer) + return; + + LSP *lsp = buffer_lsp(buffer); + if (!lsp) + return; + + if (!document_link->last_request.id) { + // send the request + LSPRequest request = {.type = LSP_REQUEST_DOCUMENT_LINK}; + LSPRequestDocumentLink *lnk = &request.data.document_link; + lnk->document = buffer_lsp_document_id(buffer); + document_link->last_request = lsp_send_request(lsp, &request); + } +} + +void document_link_process_lsp_response(Ted *ted, const LSPResponse *response) { + if (response->request.type != LSP_REQUEST_DOCUMENT_LINK) + return; + + (void)ted;//TODO + const LSPResponseDocumentLink *document_link = &response->data.document_link; + + arr_foreach_ptr(document_link->links, const LSPDocumentLink, link) { + printf("target: %s\n", lsp_response_string(response, link->target)); + + } +} diff --git a/lsp-json.c b/lsp-json.c index 08f926a..4884c8c 100644 --- a/lsp-json.c +++ b/lsp-json.c @@ -663,6 +663,7 @@ void json_debug_print(const JSON *json) { printf("%u values (capacity %u, text length %zu)\n", arr_len(json->values), arr_cap(json->values), strlen(json->text)); json_debug_print_value(json, json->values[0]); + printf("\n"); } // e.g. converts "Hello\nworld" to "Hello\\nworld" diff --git a/lsp-parse.c b/lsp-parse.c index 6c5ccec..c07679a 100644 --- a/lsp-parse.c +++ b/lsp-parse.c @@ -236,6 +236,12 @@ static void parse_capabilities(LSP *lsp, const JSON *json, JSONObject capabiliti 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) { + cap->document_link_support = true; + } + // check for workspace/symbol support JSONValue workspace_symbol_value = json_object_get(json, capabilities, "workspaceSymbolProvider"); if (workspace_symbol_value.type != JSON_UNDEFINED && workspace_symbol_value.type != JSON_FALSE) { @@ -275,7 +281,7 @@ static bool parse_text_edit(LSP *lsp, LSPResponse *response, const JSON *json, J return true; } -static bool parse_completion(LSP *lsp, const JSON *json, LSPResponse *response) { +static bool parse_completion_response(LSP *lsp, const JSON *json, LSPResponse *response) { // deal with textDocument/completion response. // result: CompletionItem[] | CompletionList | null LSPResponseCompletion *completion = &response->data.completion; @@ -418,7 +424,7 @@ static bool parse_completion(LSP *lsp, const JSON *json, LSPResponse *response) return true; } -static bool parse_signature_help(LSP *lsp, const JSON *json, LSPResponse *response) { +static bool parse_signature_help_response(LSP *lsp, const JSON *json, LSPResponse *response) { JSONObject result = json_force_object(json_get(json, "result")); //json_debug_print_object(json, result);printf("\n"); LSPResponseSignatureHelp *help = &response->data.signature_help; @@ -516,7 +522,7 @@ static bool parse_signature_help(LSP *lsp, const JSON *json, LSPResponse *respon return true; } -static bool parse_hover(LSP *lsp, const JSON *json, LSPResponse *response) { +static bool parse_hover_response(LSP *lsp, const JSON *json, LSPResponse *response) { LSPResponseHover *hover = &response->data.hover; JSONValue result_value = json_get(json, "result"); if (result_value.type == JSON_NULL) @@ -593,7 +599,7 @@ static bool parse_location(LSP *lsp, const JSON *json, JSONValue value, LSPLocat return true; } -static bool parse_definition(LSP *lsp, const JSON *json, LSPResponse *response) { +static bool parse_definition_response(LSP *lsp, const JSON *json, LSPResponse *response) { JSONValue result = json_get(json, "result"); if (result.type == JSON_NULL) { // no location @@ -660,7 +666,7 @@ static bool parse_symbol_information(LSP *lsp, const JSON *json, JSONValue value return true; } -static bool parse_workspace_symbols(LSP *lsp, const JSON *json, LSPResponse *response) { +static bool parse_workspace_symbols_response(LSP *lsp, const JSON *json, LSPResponse *response) { LSPResponseWorkspaceSymbols *syms = &response->data.workspace_symbols; JSONArray result = json_force_array(json_get(json, "result")); arr_set_len(syms->symbols, result.len); @@ -845,12 +851,12 @@ static bool parse_workspace_edit(LSP *lsp, LSPResponse *response, const JSON *js return true; } -static bool parse_rename(LSP *lsp, const JSON *json, LSPResponse *response) { +static bool parse_rename_response(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 bool parse_highlight(LSP *lsp, const JSON *json, LSPResponse *response) { +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")); for (u32 h = 0; h < result.len; ++h) { @@ -906,7 +912,7 @@ static int references_location_cmp(void *context, const void *av, const void *bv return 0; } -static bool parse_references(LSP *lsp, const JSON *json, LSPResponse *response) { +static bool parse_references_response(LSP *lsp, const JSON *json, LSPResponse *response) { LSPResponseReferences *refs = &response->data.references; JSONArray result = json_force_array(json_get(json, "result")); for (u32 r = 0; r < result.len; ++r) { @@ -919,6 +925,31 @@ static bool parse_references(LSP *lsp, const JSON *json, LSPResponse *response) return true; } +static bool parse_document_link_response(LSP *lsp, const JSON *json, LSPResponse *response) { + LSPResponseDocumentLink *data = &response->data.document_link; + JSONArray result = json_force_array(json_get(json, "result")); + + for (u32 i = 0; i < result.len; ++i) { + JSONObject link_object = json_array_get_object(json, result, i); + JSONString target = json_object_get_string(json, link_object, "target"); + JSONValue range = json_object_get(json, link_object, "range"); + JSONString tooltip = json_object_get_string(json, link_object, "tooltip"); + if (!target.len) { + // technically this can be omitted and force us to send + // a resolve request, but idk if any servers out there + // actually do that + continue; + } + + LSPDocumentLink *link = arr_addp(data->links); + if (!parse_range(lsp, json, range, &link->range)) + return false; + link->target = lsp_response_add_json_string(response, json, target); + link->tooltip = lsp_response_add_json_string(response, json, tooltip); + } + return true; +} + void process_message(LSP *lsp, JSON *json) { #if 0 @@ -969,31 +1000,34 @@ void process_message(LSP *lsp, JSON *json) { add_to_messages = true; } else switch (response.request.type) { case LSP_REQUEST_COMPLETION: - add_to_messages = parse_completion(lsp, json, &response); + add_to_messages = parse_completion_response(lsp, json, &response); break; case LSP_REQUEST_SIGNATURE_HELP: - add_to_messages = parse_signature_help(lsp, json, &response); + add_to_messages = parse_signature_help_response(lsp, json, &response); break; case LSP_REQUEST_HOVER: - add_to_messages = parse_hover(lsp, json, &response); + add_to_messages = parse_hover_response(lsp, json, &response); break; case LSP_REQUEST_DEFINITION: case LSP_REQUEST_DECLARATION: case LSP_REQUEST_TYPE_DEFINITION: case LSP_REQUEST_IMPLEMENTATION: - add_to_messages = parse_definition(lsp, json, &response); + add_to_messages = parse_definition_response(lsp, json, &response); break; case LSP_REQUEST_HIGHLIGHT: - add_to_messages = parse_highlight(lsp, json, &response); + add_to_messages = parse_highlight_response(lsp, json, &response); break; case LSP_REQUEST_REFERENCES: - add_to_messages = parse_references(lsp, json, &response); + add_to_messages = parse_references_response(lsp, json, &response); break; case LSP_REQUEST_WORKSPACE_SYMBOLS: - add_to_messages = parse_workspace_symbols(lsp, json, &response); + add_to_messages = parse_workspace_symbols_response(lsp, json, &response); break; case LSP_REQUEST_RENAME: - add_to_messages = parse_rename(lsp, json, &response); + add_to_messages = parse_rename_response(lsp, json, &response); + break; + case LSP_REQUEST_DOCUMENT_LINK: + add_to_messages = parse_document_link_response(lsp, json, &response); break; case LSP_REQUEST_INITIALIZE: if (!lsp->initialized) { diff --git a/lsp-write.c b/lsp-write.c index df695a6..85c5781 100644 --- a/lsp-write.c +++ b/lsp-write.c @@ -288,6 +288,8 @@ static const char *lsp_request_method(LSPRequest *request) { return "textDocument/implementation"; case LSP_REQUEST_HIGHLIGHT: return "textDocument/documentHighlight"; + case LSP_REQUEST_DOCUMENT_LINK: + return "textDocument/documentLink"; case LSP_REQUEST_RENAME: return "textDocument/rename"; case LSP_REQUEST_WORKSPACE_FOLDERS: @@ -447,6 +449,11 @@ void write_request(LSP *lsp, LSPRequest *request) { write_key_obj_start(o, "definition"); // NOTE: LocationLink support doesn't seem useful to us right now. write_obj_end(o); + + // document link capabilities + write_key_obj_start(o, "documentLink"); + write_key_bool(o, "tooltipSupport", true); + write_obj_end(o); write_obj_end(o); write_key_obj_start(o, "workspace"); write_key_bool(o, "workspaceFolders", true); @@ -579,6 +586,14 @@ void write_request(LSP *lsp, LSPRequest *request) { write_obj_end(o); write_obj_end(o); } break; + case LSP_REQUEST_DOCUMENT_LINK: { + const LSPRequestDocumentLink *lnk = &request->data.document_link; + write_key_obj_start(o, "params"); + write_key_obj_start(o, "textDocument"); + write_key_file_uri(o, "uri", lnk->document); + write_obj_end(o); + write_obj_end(o); + } break; case LSP_REQUEST_RENAME: { const LSPRequestRename *rename = &request->data.rename; write_key_obj_start(o, "params"); diff --git a/lsp.c b/lsp.c index cac2bea..77e1454 100644 --- a/lsp.c +++ b/lsp.c @@ -54,6 +54,7 @@ void lsp_request_free(LSPRequest *r) { case LSP_REQUEST_HIGHLIGHT: case LSP_REQUEST_DID_CLOSE: case LSP_REQUEST_WORKSPACE_FOLDERS: + case LSP_REQUEST_DOCUMENT_LINK: break; case LSP_REQUEST_CONFIGURATION: { LSPRequestConfiguration *config = &r->data.configuration; @@ -112,6 +113,9 @@ void lsp_response_free(LSPResponse *r) { case LSP_REQUEST_REFERENCES: arr_free(r->data.references.locations); break; + case LSP_REQUEST_DOCUMENT_LINK: + arr_free(r->data.document_link.links); + break; default: break; } @@ -193,6 +197,8 @@ static bool lsp_supports_request(LSP *lsp, const LSPRequest *request) { return cap->highlight_support; case LSP_REQUEST_REFERENCES: return cap->references_support; + case LSP_REQUEST_DOCUMENT_LINK: + return cap->document_link_support; } assert(0); return false; @@ -232,6 +238,7 @@ static bool request_type_is_notification(LSPRequestType type) { case LSP_REQUEST_RENAME: case LSP_REQUEST_WORKSPACE_SYMBOLS: case LSP_REQUEST_WORKSPACE_FOLDERS: + case LSP_REQUEST_DOCUMENT_LINK: return false; } assert(0); diff --git a/lsp.h b/lsp.h index fe74703..4e5cf03 100644 --- a/lsp.h +++ b/lsp.h @@ -85,6 +85,7 @@ typedef enum { LSP_REQUEST_HIGHLIGHT, //< textDocument/documentHighlight LSP_REQUEST_REFERENCES, //< textDocument/references LSP_REQUEST_RENAME, //< textDocument/rename + LSP_REQUEST_DOCUMENT_LINK, //< textDocument/documentLink LSP_REQUEST_WORKSPACE_SYMBOLS, //< workspace/symbol LSP_REQUEST_DID_CHANGE_WORKSPACE_FOLDERS, //< workspace/didChangeWorkspaceFolders @@ -190,6 +191,10 @@ typedef struct { bool include_declaration; } LSPRequestReferences; +typedef struct { + LSPDocumentID document; +} LSPRequestDocumentLink; + typedef struct { // string to filter the symbols by char *query; @@ -233,6 +238,7 @@ typedef struct { LSPRequestMessage message; LSPRequestDidChangeWorkspaceFolders change_workspace_folders; LSPRequestRename rename; + LSPRequestDocumentLink document_link; } data; } LSPRequest; @@ -474,6 +480,16 @@ typedef struct { } LSPWorkspaceEdit; typedef LSPWorkspaceEdit LSPResponseRename; +typedef struct { + LSPRange range; + LSPString target; + LSPString tooltip; +} LSPDocumentLink; + +typedef struct { + LSPDocumentLink *links; +} LSPResponseDocumentLink; + typedef struct { LSPRequest request; // the request which this is a response to char *error; // if not NULL, the data field will just be zeroed @@ -492,6 +508,7 @@ typedef struct { LSPResponseRename rename; LSPResponseHighlight highlight; LSPResponseReferences references; + LSPResponseDocumentLink document_link; } data; } LSPResponse; @@ -524,6 +541,7 @@ typedef struct { bool workspace_folders_support; bool rename_support; bool references_support; + bool document_link_support; } LSPCapabilities; typedef struct LSP { diff --git a/main.c b/main.c index 0045e98..8109df8 100644 --- a/main.c +++ b/main.c @@ -1,8 +1,6 @@ /* FUTURE FEATURES: - autodetect indentation (tabs vs spaces) -- font setting & support for multiple fonts to cover more characters -- support for variable-width fonts - robust find (results shouldn't move around when you type things) - document links using LSP textDocument/documentLink request - rename using LSP (textDocument/rename) @@ -81,6 +79,7 @@ FUTURE FEATURES: #include "ide-definitions.c" #include "ide-highlights.c" #include "ide-usages.c" +#include "ide-d.c" #include "command.c" #include "macro.c" #include "config.c" @@ -900,6 +899,7 @@ int main(int argc, char **argv) { definitions_process_lsp_response(ted, lsp, r); highlights_process_lsp_response(ted, r); usages_process_lsp_response(ted, r); + document_link_process_lsp_response(ted, r); } break; } lsp_message_free(&message); @@ -1009,6 +1009,7 @@ int main(int argc, char **argv) { definitions_frame(ted); highlights_frame(ted); usages_frame(ted); + document_link_frame(ted); } else { autocomplete_close(ted); if (!ted->build_shown) { diff --git a/ted.h b/ted.h index 2cbf305..ea2fd68 100644 --- a/ted.h +++ b/ted.h @@ -665,6 +665,13 @@ typedef struct { Signature signatures[SIGNATURE_HELP_MAX]; } SignatureHelp; +/// "document link" information (LSP) +typedef struct { + LSPServerRequestID last_request; + // time when activation key was pressed + double key_press_time; +} DocumentLink; + /// "hover" information from LSP server typedef struct { LSPServerRequestID last_request; @@ -825,6 +832,7 @@ typedef struct Ted { bool building; Autocomplete autocomplete; SignatureHelp signature_help; + DocumentLink document_link; Hover hover; Definitions definitions; Highlights highlights; @@ -1498,6 +1506,10 @@ void definitions_selector_render(Ted *ted, Rect bounds); void definitions_selector_close(Ted *ted); void definitions_frame(Ted *ted); +// === ide-document-link.c === +void document_link_frame(Ted *ted); +void document_link_process_lsp_response(Ted *ted, const LSPResponse *response); + // === ide-highlights.c === void highlights_close(Ted *ted); void highlights_process_lsp_response(Ted *ted, LSPResponse *response); diff --git a/test/lsp/go/main.go b/test/lsp/go/main.go index d60bcd1..1cf4073 100644 --- a/test/lsp/go/main.go +++ b/test/lsp/go/main.go @@ -7,6 +7,7 @@ type X struct { y int } +/// https://example.com func main() { _, err := fmt.Println("hello world"); if err != nil { diff --git a/test/lsp/rust/.gitignore b/test/lsp/rust/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/test/lsp/rust/.gitignore @@ -0,0 +1 @@ +target diff --git a/test/lsp/rust/Cargo.lock b/test/lsp/rust/Cargo.lock new file mode 100644 index 0000000..b21cc6a --- /dev/null +++ b/test/lsp/rust/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "rust" +version = "0.1.0" diff --git a/test/lsp/rust/Cargo.toml b/test/lsp/rust/Cargo.toml new file mode 100644 index 0000000..1ec6963 --- /dev/null +++ b/test/lsp/rust/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "rust" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test/lsp/rust/src/main.rs b/test/lsp/rust/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/test/lsp/rust/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/test/test.html b/test/test.html new file mode 100644 index 0000000..7677c8f --- /dev/null +++ b/test/test.html @@ -0,0 +1,6 @@ + + + +example + + -- cgit v1.2.3