From 25f6cd6f5ffb9ff34d9e22c38e9d72cdadce2dc9 Mon Sep 17 00:00:00 2001 From: pommicket Date: Fri, 30 Dec 2022 23:39:27 -0500 Subject: basic find usages --- build.c | 156 +++++++++++++++++++++++++++++++---------------------------- command.c | 5 +- command.h | 2 + ide-usages.c | 68 ++++++++++++++++++++++++++ lsp.c | 1 + main.c | 7 ++- ted.cfg | 2 + ted.h | 7 +++ 8 files changed, 172 insertions(+), 76 deletions(-) create mode 100644 ide-usages.c diff --git a/build.c b/build.c index 9c5ee7a..04a9488 100644 --- a/build.c +++ b/build.c @@ -61,12 +61,16 @@ static bool build_run_next_command_in_queue(Ted *ted) { } } -// make sure you set ted->build_dir before running this! -static void build_queue_finish(Ted *ted) { +void build_setup_buffer(Ted *ted) { // new empty build output buffer TextBuffer *build_buffer = &ted->build_buffer; buffer_new_file(build_buffer, NULL); build_buffer->store_undo_events = false; // don't need undo events for build output buffer +} + +// make sure you set ted->build_dir before running this! +static void build_queue_finish(Ted *ted) { + build_setup_buffer(ted); build_run_next_command_in_queue(ted); // run the first command } @@ -160,7 +164,82 @@ static bool is_source_path(char32_t c) { || strchr(allowed_ascii_symbols_in_path, (char)c); } -static void build_frame(Ted *ted, float x1, float y1, float x2, float y2) { +// make sure you set ted->build_dir before running this! +void build_check_for_errors(Ted *ted) { + TextBuffer *buffer = &ted->build_buffer; + arr_clear(ted->build_errors); + for (u32 line_idx = 0; line_idx < buffer->nlines; ++line_idx) { + Line *line = &buffer->lines[line_idx]; + if (line->len < 3) { + continue; + } + bool is_error = true; + char32_t *p = line->str, *end = p + line->len; + + { + // rust errors look like: + // " --> file:line:column" + while (p != end && *p == ' ') { + ++p; + } + if (end - p >= 4 && p[0] == '-' && p[1] == '-' && p[2] == '>' && p[3] == ' ') { + p += 4; + } + } + + // check if we have something like main.c:5 or main.c(5) + + // get file name + char32_t *filename_start = p; + while (p != end) { + if ((*p == ':' || *p == '(') + && p != line->str + 1) // don't catch "C:\thing\whatever.c" as "filename: C, line number: \thing\whatever.c" + break; + if (!is_source_path(*p)) { + is_error = false; + break; + } + ++p; + } + if (p == end) is_error = false; + u32 filename_len = (u32)(p - filename_start); + if (filename_len == 0) is_error = false; + + if (is_error) { + ++p; // move past : or ( + int line_number = parse_nonnegative_integer(&p, end); + if (p != end && line_number > 0) { + // it's an error + line_number -= 1; // line numbers in output start from 1. + int column_number = 0; + // check if there's a column number + if (*p == ':') { + ++p; // move past : + int num = parse_nonnegative_integer(&p, end); + if (num > 0) { + column_number = num - 1; // column numbers in output start from 1 + } + } + char *filename = str32_to_utf8_cstr(str32(filename_start, filename_len)); + if (filename) { + char full_path[TED_PATH_MAX]; + path_full(ted->build_dir, filename, full_path, sizeof full_path); + BuildError error = { + .filename = str_dup(full_path), + .pos = {.line = (u32)line_number, .index = (u32)column_number}, + .build_output_line = line_idx + }; + arr_add(ted->build_errors, error); + } + } + } + } + // go to the first error (if there is one) + ted->build_error = 0; + build_go_to_error(ted); +} + +void build_frame(Ted *ted, float x1, float y1, float x2, float y2) { TextBuffer *buffer = &ted->build_buffer; Process *process = &ted->build_process; assert(ted->build_shown); @@ -230,76 +309,7 @@ static void build_frame(Ted *ted, float x1, float y1, float x2, float y2) { if (!build_run_next_command_in_queue(ted)) { ted->building = false; // done command queue; check for errors - for (u32 line_idx = 0; line_idx < buffer->nlines; ++line_idx) { - Line *line = &buffer->lines[line_idx]; - if (line->len < 3) { - continue; - } - bool is_error = true; - char32_t *p = line->str, *end = p + line->len; - - { - // rust errors look like: - // " --> file:line:column" - while (p != end && *p == ' ') { - ++p; - } - if (end - p >= 4 && p[0] == '-' && p[1] == '-' && p[2] == '>' && p[3] == ' ') { - p += 4; - } - } - - // check if we have something like main.c:5 or main.c(5) - - // get file name - char32_t *filename_start = p; - while (p != end) { - if ((*p == ':' || *p == '(') - && p != line->str + 1) // don't catch "C:\thing\whatever.c" as "filename: C, line number: \thing\whatever.c" - break; - if (!is_source_path(*p)) { - is_error = false; - break; - } - ++p; - } - if (p == end) is_error = false; - u32 filename_len = (u32)(p - filename_start); - if (filename_len == 0) is_error = false; - - if (is_error) { - ++p; // move past : or ( - int line_number = parse_nonnegative_integer(&p, end); - if (p != end && line_number > 0) { - // it's an error - line_number -= 1; // line numbers in output start from 1. - int column_number = 0; - // check if there's a column number - if (*p == ':') { - ++p; // move past : - int num = parse_nonnegative_integer(&p, end); - if (num > 0) { - column_number = num - 1; // column numbers in output start from 1 - } - } - char *filename = str32_to_utf8_cstr(str32(filename_start, filename_len)); - if (filename) { - char full_path[TED_PATH_MAX]; - path_full(ted->build_dir, filename, full_path, sizeof full_path); - BuildError error = { - .filename = str_dup(full_path), - .pos = {.line = (u32)line_number, .index = (u32)column_number}, - .build_output_line = line_idx - }; - arr_add(ted->build_errors, error); - } - } - } - } - - // go to the first error (if there is one) - ted->build_error = 0; - build_go_to_error(ted); + build_check_for_errors(ted); } } buffer->view_only = true; diff --git a/command.c b/command.c index 4f2d643..d49f57a 100644 --- a/command.c +++ b/command.c @@ -293,7 +293,9 @@ void command_execute(Ted *ted, Command c, i64 argument) { if (ted->autocomplete.open) autocomplete_prev(ted); break; - + case CMD_FIND_USAGES: + usages_find(ted); + break; case CMD_UNDO: if (buffer) buffer_undo(buffer, argument); break; @@ -395,6 +397,7 @@ void command_execute(Ted *ted, Command c, i64 argument) { case CMD_ESCAPE: definition_cancel_lookup(ted); + usages_cancel_lookup(ted); if (*ted->error_shown) { // dismiss error box *ted->error_shown = '\0'; diff --git a/command.h b/command.h index b84f7ad..000b050 100644 --- a/command.h +++ b/command.h @@ -63,6 +63,7 @@ ENUM_U16 { CMD_SET_LANGUAGE, CMD_AUTOCOMPLETE, CMD_AUTOCOMPLETE_BACK, + CMD_FIND_USAGES, CMD_COPY, CMD_CUT, @@ -161,6 +162,7 @@ static CommandName const command_names[] = { {"paste", CMD_PASTE}, {"autocomplete", CMD_AUTOCOMPLETE}, {"autocomplete-back", CMD_AUTOCOMPLETE_BACK}, + {"find-usages", CMD_FIND_USAGES}, {"find", CMD_FIND}, {"find-replace", CMD_FIND_REPLACE}, {"tab-close", CMD_TAB_CLOSE}, diff --git a/ide-usages.c b/ide-usages.c new file mode 100644 index 0000000..21999be --- /dev/null +++ b/ide-usages.c @@ -0,0 +1,68 @@ +void usages_cancel_lookup(Ted *ted) { + Usages *usages = &ted->usages; + if (usages->last_request_id) { + LSP *lsp = ted_get_lsp_by_id(ted, usages->last_request_lsp); + lsp_cancel_request(lsp, usages->last_request_id); + usages->last_request_id = 0; + } +} + +void usages_find(Ted *ted) { + Usages *usages = &ted->usages; + TextBuffer *buffer = ted->active_buffer; + if (!buffer) return; + LSP *lsp = buffer_lsp(buffer); + if (!lsp) return; + + // send the request + LSPRequest request = {.type = LSP_REQUEST_REFERENCES}; + LSPRequestReferences *refs = &request.data.references; + refs->include_declaration = true; + refs->position = buffer_cursor_pos_as_lsp_document_position(buffer); + usages_cancel_lookup(ted); + usages->last_request_lsp = lsp->id; + usages->last_request_id = lsp_send_request(lsp, &request); + usages->last_request_time = ted->frame_time; +} + + +void usages_process_lsp_response(Ted *ted, LSPResponse *response) { + Usages *usages = &ted->usages; + if (response->request.type != LSP_REQUEST_REFERENCES) + return; // not for us + if (response->request.id != usages->last_request_id) + return; + LSP *lsp = ted_get_lsp_by_id(ted, usages->last_request_lsp); + LSPResponseReferences *refs = &response->data.references; + if (lsp && arr_len(refs->locations)) { + TextBuffer *buffer = &ted->build_buffer; + build_setup_buffer(ted); + ted->build_shown = true; + arr_foreach_ptr(refs->locations, LSPLocation, location) { + char text[1024]; + strbuf_printf(text, "%s:%u: \n", + lsp_document_path(lsp, location->document), + location->range.start.line + 1); + buffer_insert_utf8_at_cursor(buffer, text); + buffer_cursor_move_to_end_of_file(buffer); + } + buffer->view_only = true; + + // the build_dir doesn't really matter since we're using absolute paths + // but might as well set it to something reasonable. + char *root = ted_get_root_dir(ted); + strbuf_cpy(ted->build_dir, root); + free(root); + + build_check_for_errors(ted); + } else { + ted->cursor_error_time = time_get_seconds(); + } + usages->last_request_id = 0; +} + +void usages_frame(Ted *ted) { + Usages *usages = &ted->usages; + if (usages->last_request_id && timespec_sub(ted->frame_time, usages->last_request_time) > 0.2) + ted->cursor = ted->cursor_wait; // this request is takin a while +} diff --git a/lsp.c b/lsp.c index 81cb4c9..16e0087 100644 --- a/lsp.c +++ b/lsp.c @@ -604,6 +604,7 @@ bool lsp_covers_path(LSP *lsp, const char *path) { void lsp_cancel_request(LSP *lsp, LSPRequestID id) { if (!id) return; + if (!lsp) return; LSPRequest request = {.type = LSP_REQUEST_CANCEL}; request.data.cancel.id = id; diff --git a/main.c b/main.c index dca6fb5..38d4e4b 100644 --- a/main.c +++ b/main.c @@ -1,8 +1,8 @@ /* @TODO: -- find usages (textDocument/references) -- different highlight colors +- show line containing usage - framerate-cap setting +- change frame_time to a double - highlight-enabled, and highlight-auto - handle multiple symbols with same name in go-to-definition menu - :go-to-cursor-definition @@ -156,6 +156,7 @@ bool tag_goto(Ted *ted, char const *tag); #include "ide-hover.c" #include "ide-definitions.c" #include "ide-highlights.c" +#include "ide-usages.c" #include "command.c" #include "config.c" #include "session.c" @@ -920,6 +921,7 @@ int main(int argc, char **argv) { hover_process_lsp_response(ted, r); definitions_process_lsp_response(ted, lsp, r); highlights_process_lsp_response(ted, r); + usages_process_lsp_response(ted, r); } break; } lsp_message_free(&message); @@ -1028,6 +1030,7 @@ int main(int argc, char **argv) { hover_frame(ted, frame_dt); definitions_frame(ted); highlights_frame(ted); + usages_frame(ted); } else { autocomplete_close(ted); text_utf8_anchored(font, "Press Ctrl+O to open a file or Ctrl+N to create a new one.", diff --git a/ted.cfg b/ted.cfg index f81c079..88204db 100644 --- a/ted.cfg +++ b/ted.cfg @@ -201,9 +201,11 @@ Ctrl+Shift+r = :reload-all # 16 GLSL # -1 Guess from the file extension (default) +# IDE features Ctrl+Space = :autocomplete # go to previous completion Ctrl+Shift+Space = :autocomplete-back +Ctrl+u = :find-usages Ctrl+z = :undo Ctrl+Shift+z = :redo diff --git a/ted.h b/ted.h index 45a9531..41f7bbc 100644 --- a/ted.h +++ b/ted.h @@ -384,6 +384,12 @@ typedef struct { Rect rect; // rectangle where the autocomplete menu is (needed to avoid interpreting autocomplete clicks as other clicks) } Autocomplete; +typedef struct { + LSPID last_request_lsp; + LSPRequestID last_request_id; + struct timespec last_request_time; +} Usages; + typedef struct { // displayed normal char *label_pre; @@ -502,6 +508,7 @@ typedef struct Ted { Hover hover; Definitions definitions; Highlights highlights; + Usages usages; FILE *log; -- cgit v1.2.3