From 1631f38d4dba64577c8f064225599273148ad83d Mon Sep 17 00:00:00 2001 From: Leo Tenenbaum Date: Mon, 22 Feb 2021 12:36:50 -0500 Subject: go to definition menu --- buffer.c | 2 +- command.c | 11 +++++++--- command.h | 3 +++ main.c | 15 ++++++++----- menu.c | 66 +++++++++++++++++++++++++++++++++++-------------------- tags.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- ted.cfg | 2 ++ ted.h | 4 ++++ ui.c | 1 + util.c | 19 +++++++++++----- 10 files changed, 156 insertions(+), 42 deletions(-) diff --git a/buffer.c b/buffer.c index 0bf78d2..e00377e 100644 --- a/buffer.c +++ b/buffer.c @@ -2124,7 +2124,7 @@ void buffer_render(TextBuffer *buffer, Rect r) { buffer->center_cursor_next_frame = false; } - if (rect_contains_point(rect4(x1, y1, x2, y2), ted->mouse_pos)) { + if (rect_contains_point(rect4(x1, y1, x2, y2), ted->mouse_pos) && !ted->menu) { // scroll with mouse wheel double scroll_speed = 2.5; buffer_scroll(buffer, ted->scroll_total_x * scroll_speed, ted->scroll_total_y * scroll_speed); diff --git a/command.c b/command.c index 78d5b24..4488289 100644 --- a/command.c +++ b/command.c @@ -125,7 +125,7 @@ void command_execute(Ted *ted, Command c, i64 argument) { else find_prev(ted); } - } else { + } else if (buffer) { buffer_newline(buffer); } break; @@ -269,10 +269,12 @@ void command_execute(Ted *ted, Command c, i64 argument) { break; case CMD_FIND: - find_open(ted, false); + if (buffer) + find_open(ted, false); break; case CMD_FIND_REPLACE: - find_open(ted, true); + if (buffer) + find_open(ted, true); break; case CMD_ESCAPE: @@ -299,5 +301,8 @@ void command_execute(Ted *ted, Command c, i64 argument) { case CMD_BUILD_PREV_ERROR: build_prev_error(ted); break; + case CMD_OPEN_DEFINITION_MENU: + menu_open(ted, MENU_GOTO_DEFINITION); + break; } } diff --git a/command.h b/command.h index 61fd04b..8f61424 100644 --- a/command.h +++ b/command.h @@ -68,6 +68,8 @@ ENUM_U16 { CMD_BUILD, CMD_BUILD_PREV_ERROR, CMD_BUILD_NEXT_ERROR, + + CMD_OPEN_DEFINITION_MENU, // "go to definition of..." CMD_ESCAPE, // by default this is the escape key. closes menus, etc. @@ -135,6 +137,7 @@ static CommandName const command_names[CMD_COUNT] = { {"build", CMD_BUILD}, {"build-prev-error", CMD_BUILD_PREV_ERROR}, {"build-next-error", CMD_BUILD_NEXT_ERROR}, + {"open-definition-menu", CMD_OPEN_DEFINITION_MENU}, {"escape", CMD_ESCAPE}, }; diff --git a/main.c b/main.c index 0de7610..4a0f420 100644 --- a/main.c +++ b/main.c @@ -1,5 +1,4 @@ // @TODO: -// - Ctrl+D = :find-definition --- tag menu (see all tags, select one) // - fix automatic horizontal scrolling // - goto line @@ -65,10 +64,10 @@ no_warn_end bool tag_goto(Ted *ted, char const *tag); #include "buffer.c" #include "ted.c" -#include "tags.c" #include "ui.c" #include "find.c" #include "node.c" +#include "tags.c" #include "menu.c" #include "build.c" #include "command.c" @@ -588,6 +587,10 @@ int main(int argc, char **argv) { buffer_scroll(active_buffer, +scroll_amount_x, 0); } + if (ted->menu) { + menu_update(ted); + } + ted_update_window_dimensions(ted); float window_width = ted->window_width, window_height = ted->window_height; @@ -607,6 +610,7 @@ int main(int argc, char **argv) { // default window title strcpy(ted->window_title, "ted"); + if (ted->active_node) { float const padding = settings->padding; float x1 = padding, y = window_height-padding, x2 = window_width-padding; @@ -632,12 +636,10 @@ int main(int argc, char **argv) { text_render(font); } - Menu menu = ted->menu; - if (menu) { - menu_frame(ted, menu); + if (ted->menu) { + menu_render(ted); } - if (text_has_err()) { ted_seterr(ted, "Couldn't render text: %s", text_get_err()); } @@ -742,6 +744,7 @@ int main(int argc, char **argv) { SDL_DestroyWindow(window); SDL_Quit(); find_close(ted); + tag_selector_close(ted); for (u16 i = 0; i < TED_MAX_BUFFERS; ++i) if (ted->buffers_used[i]) buffer_free(&ted->buffers[i]); diff --git a/menu.c b/menu.c index c4502e0..e9c9488 100644 --- a/menu.c +++ b/menu.c @@ -20,6 +20,9 @@ static void menu_open(Ted *ted, Menu menu) { case MENU_ASK_RELOAD: assert(*ted->ask_reload); break; + case MENU_GOTO_DEFINITION: + tag_selector_open(ted); + break; } } @@ -40,6 +43,9 @@ static void menu_close(Ted *ted) { case MENU_ASK_RELOAD: *ted->ask_reload = 0; break; + case MENU_GOTO_DEFINITION: + tag_selector_close(ted); + break; } ted->menu = MENU_NONE; } @@ -71,7 +77,9 @@ static Rect menu_rect(Ted *ted) { ); } -static void menu_update(Ted *ted, Menu menu) { +static void menu_update(Ted *ted) { + Menu menu = ted->menu; + assert(menu); switch (menu) { case MENU_NONE: break; case MENU_SAVE_AS: { @@ -179,10 +187,20 @@ static void menu_update(Ted *ted, Menu menu) { break; } break; + case MENU_GOTO_DEFINITION: { + char *chosen_tag = tag_selector_update(ted); + if (chosen_tag) { + menu_close(ted); + tag_goto(ted, chosen_tag); + free(chosen_tag); + } + } break; } } -static void menu_render(Ted *ted, Menu menu) { +static void menu_render(Ted *ted) { + Menu menu = ted->menu; + assert(menu); Settings const *settings = &ted->settings; u32 const *colors = settings->colors; float window_width = ted->window_width, window_height = ted->window_height; @@ -202,6 +220,26 @@ static void menu_render(Ted *ted, Menu menu) { popup_render(ted, POPUP_YES_NO_CANCEL, title, body); return; } + + + float padding = settings->padding; + Rect rect = menu_rect(ted); + float menu_x1, menu_y1, menu_x2, menu_y2; + rect_coords(rect, &menu_x1, &menu_y1, &menu_x2, &menu_y2); + + if (menu == MENU_OPEN || menu == MENU_SAVE_AS || menu == MENU_GOTO_DEFINITION) { + // menu rectangle & border + gl_geometry_rect(rect, colors[COLOR_MENU_BG]); + gl_geometry_rect_border(rect, settings->border_thickness, colors[COLOR_BORDER]); + gl_geometry_draw(); + + menu_x1 += padding; + menu_y1 += padding; + menu_x2 -= padding; + menu_y2 -= padding; + } + + switch (menu) { case MENU_NONE: assert(0); break; case MENU_WARN_UNSAVED: { @@ -218,21 +256,6 @@ static void menu_render(Ted *ted, Menu menu) { } break; case MENU_OPEN: case MENU_SAVE_AS: { - float padding = settings->padding; - Rect rect = menu_rect(ted); - float menu_x1, menu_y1, menu_x2, menu_y2; - rect_coords(rect, &menu_x1, &menu_y1, &menu_x2, &menu_y2); - - // menu rectangle & border - gl_geometry_rect(rect, colors[COLOR_MENU_BG]); - gl_geometry_rect_border(rect, settings->border_thickness, colors[COLOR_BORDER]); - gl_geometry_draw(); - - menu_x1 += padding; - menu_y1 += padding; - menu_x2 -= padding; - menu_y2 -= padding; - if (menu == MENU_OPEN) { text_utf8(font_bold, "Open...", menu_x1, menu_y1, colors[COLOR_TEXT]); @@ -247,11 +270,8 @@ static void menu_render(Ted *ted, Menu menu) { fs->bounds = rect4(menu_x1, menu_y1, menu_x2, menu_y2); file_selector_render(ted, fs); } break; + case MENU_GOTO_DEFINITION: { + tag_selector_render(ted, rect4(menu_x1, menu_y1, menu_x2, menu_y2)); + } break; } } - -static void menu_frame(Ted *ted, Menu menu) { - menu_update(ted, menu); - if (ted->menu) - menu_render(ted, ted->menu); -} diff --git a/tags.c b/tags.c index c7330b3..5fe192e 100644 --- a/tags.c +++ b/tags.c @@ -1,3 +1,5 @@ +static char const TED_ERR_NO_TAGS[] = "No tags file. Try running ctags."; + static int tag_try(FILE *fp, char const *tag) { if (ftell(fp) != 0) { while (1) { @@ -29,7 +31,7 @@ bool tag_goto(Ted *ted, char const *tag) { char const *tags_filename = settings->tags_filename; FILE *file = fopen(tags_filename, "rb"); if (!file) { - ted_seterr(ted, "No tags file. Try running ctags."); + ted_seterr(ted, TED_ERR_NO_TAGS); return false; } fseek(file, 0, SEEK_END); @@ -97,9 +99,10 @@ bool tag_goto(Ted *ted, char const *tag) { TextBuffer *buffer = ted->active_buffer; int line_number = atoi(address); if (line_number > 0) { - // the tags file gives us a line number - BufferPos pos = {.line = (u32)line_number, .index = 0}; + // the tags file gives us a (1-indexed) line number + BufferPos pos = {.line = (u32)line_number - 1, .index = 0}; buffer_cursor_move_to_pos(buffer, pos); + buffer->center_cursor_next_frame = true; success = true; } else if (address[0] == '/') { // the tags file gives us a pattern to look for @@ -149,8 +152,8 @@ bool tag_goto(Ted *ted, char const *tag) { } else { ted_seterr(ted, "Unrecognized tag address: %s", address); } - break; } + break; } } } @@ -159,3 +162,67 @@ bool tag_goto(Ted *ted, char const *tag) { fclose(file); return success; } + +static void tag_selector_open(Ted *ted) { + // read tags file and extract tag names + FILE *fp = fopen("tags", "r"); + arr_clear(ted->tag_selector_entries); + if (fp) { + char line[1024]; + while (fgets(line, sizeof line, fp)) { + size_t len = strcspn(line, "\t"); + arr_add(ted->tag_selector_entries, strn_dup(line, len)); + } + ted->active_buffer = &ted->line_buffer; + buffer_select_all(ted->active_buffer); + + ted->tag_selector.cursor = 0; + + fclose(fp); + } else { + ted_seterr(ted, TED_ERR_NO_TAGS); + } +} + +static void tag_selector_close(Ted *ted) { + Selector *sel = &ted->tag_selector; + arr_clear(sel->entries); + sel->n_entries = 0; + arr_foreach_ptr(ted->tag_selector_entries, char *, entry) { + free(*entry); + } + arr_clear(ted->tag_selector_entries); +} + +// returns tag selected (should be free'd), or NULL if none was. +static char *tag_selector_update(Ted *ted) { + Selector *sel = &ted->tag_selector; + u32 color = ted->settings.colors[COLOR_TEXT]; + sel->enable_cursor = true; + + // create selector entries based on search term + char *search_term = str32_to_utf8_cstr(buffer_get_line(&ted->line_buffer, 0)); + + arr_clear(sel->entries); + + arr_foreach_ptr(ted->tag_selector_entries, char *, tagp) { + char const *tag = *tagp; + if (!search_term || stristr(tag, search_term)) { + SelectorEntry entry = { + .name = tag, + .color = color + }; + arr_add(sel->entries, entry); + } + } + + sel->n_entries = arr_len(sel->entries); + + return selector_update(ted, sel); +} + +static void tag_selector_render(Ted *ted, Rect bounds) { + Selector *sel = &ted->tag_selector; + sel->bounds = bounds; + selector_render(ted, sel); +} diff --git a/ted.cfg b/ted.cfg index ed665e0..e66c93e 100644 --- a/ted.cfg +++ b/ted.cfg @@ -118,6 +118,8 @@ F4 = :build Ctrl+[ = :build-prev-error Ctrl+] = :build-next-error +Ctrl+d = :open-definition-menu + Escape = :escape [colors] diff --git a/ted.h b/ted.h index 0ad4887..18c8d15 100644 --- a/ted.h +++ b/ted.h @@ -167,6 +167,7 @@ ENUM_U16 { MENU_SAVE_AS, MENU_WARN_UNSAVED, // warn about unsaved changes MENU_ASK_RELOAD, // prompt about whether to reload file which has ben changed by another program + MENU_GOTO_DEFINITION, } ENUM_U16_END(Menu); typedef struct { @@ -248,6 +249,7 @@ typedef struct Ted { int scroll_total_x, scroll_total_y; // total amount scrolled in the x and y direction this frame Menu menu; FileSelector file_selector; + Selector tag_selector; // for "go to definition of..." menu TextBuffer line_buffer; // general-purpose line buffer for inputs -- used for menus TextBuffer find_buffer; // use for "find" term in find/find+replace TextBuffer replace_buffer; // "replace" for find+replace @@ -271,6 +273,8 @@ typedef struct Ted { BuildError *build_errors; // dynamic array of build errors u32 build_error; // build error we are currently "on" + char **tag_selector_entries; // an array of all tags (see tag_selector_open) + // points to a selector if any is open, otherwise NULL. Selector *selector_open; diff --git a/ui.c b/ui.c index 737b7ae..d2f0d23 100644 --- a/ui.c +++ b/ui.c @@ -97,6 +97,7 @@ static char *selector_update(Ted *ted, Selector *s) { // apply scroll float scroll_speed = 2.5f; s->scroll += scroll_speed * (float)ted->scroll_total_y; + selector_clamp_scroll(ted, s); return ret; } diff --git a/util.c b/util.c index 81c4ce3..9ceb4cc 100644 --- a/util.c +++ b/util.c @@ -65,15 +65,24 @@ static bool streq(char const *a, char const *b) { return strcmp(a, b) == 0; } -// duplicates a null-terminated string. the returned string should be passed to free() -static char *str_dup(char const *src) { +// duplicates at most n characters from src +static char *strn_dup(char const *src, size_t n) { size_t len = strlen(src); - char *ret = malloc(len + 1); - if (ret) - memcpy(ret, src, len + 1); + if (n > len) + n = len; + char *ret = malloc(n + 1); + if (ret) { + memcpy(ret, src, n); + ret[n] = 0; + } return ret; } +// duplicates a null-terminated string. the returned string should be passed to free() +static char *str_dup(char const *src) { + return strn_dup(src, SIZE_MAX); +} + // like snprintf, but not screwed up on windows #define str_printf(str, size, ...) (str)[(size) - 1] = '\0', snprintf((str), (size) - 1, __VA_ARGS__) // like snprintf, but the size is taken to be the length of the array str. -- cgit v1.2.3