From efe5ae8cec7221779b9a6395eeb9b10b8974dd44 Mon Sep 17 00:00:00 2001 From: pommicket Date: Mon, 14 Aug 2023 12:59:35 -0300 Subject: start new selector system - search not working yet --- config.c | 15 ++- ide-definitions.c | 68 ++++---------- main.c | 5 +- menu.c | 68 ++++++-------- ted-internal.h | 47 +--------- ted.h | 97 ++++++++++++++++---- ui.c | 270 +++++++++++++++++++++++++++++++----------------------- util.c | 1 + 8 files changed, 302 insertions(+), 269 deletions(-) diff --git a/config.c b/config.c index ff5f25c..665f124 100644 --- a/config.c +++ b/config.c @@ -1245,7 +1245,7 @@ static char *last_separator(char *path) { return NULL; } -char *settings_get_root_dir(Settings *settings, const char *path) { +char *settings_get_root_dir(const Settings *settings, const char *path) { char best_path[TED_PATH_MAX]; *best_path = '\0'; u32 best_path_score = 0; @@ -1300,3 +1300,16 @@ char *settings_get_root_dir(Settings *settings, const char *path) { return str_dup(pathbuf); } } + +u32 settings_color(const Settings *settings, ColorSetting color) { + if (color >= COLOR_COUNT) { + assert(0); + return 0xff00ffff; + } + return settings->colors[color]; +} + +void settings_color_floats(const Settings *settings, ColorSetting color, float f[4]) { + rgba_u32_to_floats(settings_color(settings, color), f); + +} diff --git a/ide-definitions.c b/ide-definitions.c index fd06ebc..b853207 100644 --- a/ide-definitions.c +++ b/ide-definitions.c @@ -9,7 +9,7 @@ struct Definitions { /// last query string which we sent a request for char *last_request_query; /// for "go to definition of..." menu - Selector selector; + Selector *selector; /// an array of all definitions (gotten from workspace/symbols) for "go to definition" menu SymbolInfo *all_definitions; }; @@ -114,8 +114,7 @@ static void definitions_clear_entries(Definitions *defs) { free(def->detail); } arr_clear(defs->all_definitions); - arr_clear(defs->selector.entries); - defs->selector.n_entries = 0; + selector_clear(defs->selector); } static int definition_entry_qsort_cmp(const void *av, const void *bv) { @@ -134,41 +133,6 @@ static int definition_entry_qsort_cmp(const void *av, const void *bv) { return strcmp(a->detail, b->detail); } -// put the entries matching the search term into the selector. -static void definitions_selector_filter_entries(Ted *ted) { - Definitions *defs = ted->definitions; - Selector *sel = &defs->selector; - - // 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); - - for (u32 i = 0; i < arr_len(defs->all_definitions); ++i) { - SymbolInfo *info = &defs->all_definitions[i]; - if (!search_term || strstr_case_insensitive(info->name, search_term)) { - SelectorEntry *entry = arr_addp(sel->entries); - entry->name = info->name; - entry->color = info->color; - entry->detail = info->detail; - // this isn't exactly ideal but we're sorting these entries so - // it's probably the nicest way of keeping track of the definition - // this corresponds to - entry->userdata = i; - } - // don't try to display too many entries - if (arr_len(sel->entries) >= 1000) - break; - } - free(search_term); - - arr_qsort(sel->entries, definition_entry_qsort_cmp); - - sel->n_entries = arr_len(sel->entries); - sel->cursor = clamp_u32(sel->cursor, 0, sel->n_entries); -} - - void definitions_process_lsp_response(Ted *ted, LSP *lsp, const LSPResponse *response) { Definitions *defs = ted->definitions; if (response->request.id != defs->last_request.id) { @@ -242,8 +206,6 @@ void definitions_process_lsp_response(Ted *ted, LSP *lsp, const LSPResponse *res def->position.pos.line + 1); } - definitions_selector_filter_entries(ted); - } break; default: debug_println("?? bad request type in %s : %u:%u", __func__, response->request.id, response->request.type); @@ -286,7 +248,7 @@ static void definitions_selector_open(Ted *ted) { } ted_switch_to_buffer(ted, ted->line_buffer); buffer_select_all(ted->active_buffer); - defs->selector.cursor = 0; + selector_set_cursor(defs->selector, 0); } @@ -301,11 +263,8 @@ static bool definitions_selector_close(Ted *ted) { static void definitions_selector_update(Ted *ted) { Definitions *defs = ted->definitions; - Selector *sel = &defs->selector; - sel->enable_cursor = true; + Selector *sel = defs->selector; - definitions_selector_filter_entries(ted); - // send new request if search term has changed. // this is needed because e.g. clangd gives an incomplete list definitions_send_request_if_needed(ted); @@ -315,11 +274,13 @@ static void definitions_selector_update(Ted *ted) { // for LSP go-to-definition, we ignore `chosen` and use the cursor instead. // this is because a single symbol can have multiple definitions, // e.g. with overloading. - if (sel->cursor >= sel->n_entries) { - assert(0); + SelectorEntry cursor_entry = {0}; + if (!selector_get_cursor_entry(sel, &cursor_entry)) { + assert(0); // shouldn't happen since `chosen` is true. return; } - u64 def_idx = sel->entries[sel->cursor].userdata; + + u64 def_idx = cursor_entry.userdata; if (def_idx >= arr_len(defs->all_definitions)) { assert(0); return; @@ -343,8 +304,8 @@ static void definitions_selector_update(Ted *ted) { static void definitions_selector_render(Ted *ted) { Rect bounds = selection_menu_render_bg(ted); Definitions *defs = ted->definitions; - Selector *sel = &defs->selector; - sel->bounds = bounds; + Selector *sel = defs->selector; + selector_set_bounds(sel, bounds); selector_render(ted, sel); } @@ -358,10 +319,13 @@ void definitions_init(Ted *ted) { strbuf_cpy(info.name, MENU_GOTO_DEFINITION); menu_register(ted, &info); - ted->definitions = calloc(1, sizeof *ted->definitions); + Definitions *defs = ted->definitions = ted_calloc(ted, 1, sizeof *ted->definitions); + defs->selector = selector_new(); } void definitions_quit(Ted *ted) { - free(ted->definitions); + Definitions *defs = ted->definitions; + selector_free(defs->selector); + free(defs); ted->definitions = NULL; } diff --git a/main.c b/main.c index 230d987..65501c2 100644 --- a/main.c +++ b/main.c @@ -1,6 +1,6 @@ /* TODO: -- public Selector/FileSelector API +- selector search - public Settings API FUTURE FEATURES: @@ -495,6 +495,7 @@ int main(int argc, char **argv) { } #endif + ted->file_selector = file_selector_new(); gl_geometry_init(); text_init(); menu_init(ted); @@ -1203,6 +1204,8 @@ int main(int argc, char **argv) { definitions_quit(ted); menu_quit(ted); arr_free(ted->edit_notifys); + + file_selector_free(ted->file_selector); ted->file_selector = NULL; for (int i = 0; i < TED_LSP_MAX; ++i) { diff --git a/menu.c b/menu.c index e126fd1..a1efad5 100644 --- a/menu.c +++ b/menu.c @@ -167,11 +167,11 @@ void menu_shell_down(Ted *ted) { static void open_menu_open(Ted *ted) { ted_switch_to_buffer(ted, ted->line_buffer); - ted->file_selector.create_menu = false; + file_selector_set_create(ted->file_selector, false); } static void open_menu_update(Ted *ted) { - char *selected_file = file_selector_update(ted, &ted->file_selector); + char *selected_file = file_selector_update(ted, ted->file_selector); if (selected_file) { // open that file! menu_close(ted); @@ -181,21 +181,21 @@ static void open_menu_update(Ted *ted) { } static void open_menu_render(Ted *ted) { - FileSelector *fs = &ted->file_selector; - strbuf_cpy(fs->title, "Open..."); - fs->bounds = selection_menu_render_bg(ted); + FileSelector *fs = ted->file_selector; + file_selector_set_title(fs, "Open..."); + file_selector_set_bounds(fs, selection_menu_render_bg(ted)); file_selector_render(ted, fs); } static bool open_menu_close(Ted *ted) { - file_selector_free(&ted->file_selector); + file_selector_clear(ted->file_selector); buffer_clear(ted->line_buffer); return true; } static void save_as_menu_open(Ted *ted) { ted_switch_to_buffer(ted, ted->line_buffer); - ted->file_selector.create_menu = true; + file_selector_set_create(ted->file_selector, true); } static void save_as_menu_update(Ted *ted) { @@ -223,7 +223,7 @@ static void save_as_menu_update(Ted *ted) { break; } } else { - char *selected_file = file_selector_update(ted, &ted->file_selector); + char *selected_file = file_selector_update(ted, ted->file_selector); if (selected_file) { TextBuffer *buffer = ted->prev_active_buffer; if (buffer) { @@ -253,14 +253,14 @@ static void save_as_menu_render(Ted *ted) { return; } - FileSelector *fs = &ted->file_selector; - strbuf_cpy(fs->title, "Save as..."); - fs->bounds = selection_menu_render_bg(ted); + FileSelector *fs = ted->file_selector; + file_selector_set_title(fs, "Save as..."); + file_selector_set_bounds(fs, selection_menu_render_bg(ted)); file_selector_render(ted, fs); } static bool save_as_menu_close(Ted *ted) { - file_selector_free(&ted->file_selector); + file_selector_clear(ted->file_selector); buffer_clear(ted->line_buffer); return true; } @@ -351,33 +351,15 @@ static bool ask_reload_menu_close(Ted *ted) { static void command_selector_open(Ted *ted) { ted_switch_to_buffer(ted, ted->line_buffer); buffer_insert_char_at_cursor(ted->argument_buffer, '1'); - Selector *selector = &ted->command_selector; - selector->enable_cursor = true; - selector->cursor = 0; + Selector *selector = ted->command_selector; + selector_set_cursor(selector, 0); } static void command_selector_update(Ted *ted) { - const Settings *settings = ted_active_settings(ted); - const u32 *colors = settings->colors; TextBuffer *line_buffer = ted->line_buffer; - Selector *selector = &ted->command_selector; - SelectorEntry *entries = selector->entries = calloc(CMD_COUNT, sizeof *selector->entries); + Selector *selector = ted->command_selector; char *search_term = str32_to_utf8_cstr(buffer_get_line(line_buffer, 0)); - if (entries) { - SelectorEntry *entry = entries; - for (Command c = 0; c < CMD_COUNT; ++c) { - const char *name = command_to_str(c); - if (c != CMD_UNKNOWN && *name && strstr_case_insensitive(name, search_term)) { - entry->name = name; - entry->color = colors[COLOR_TEXT]; - ++entry; - } - } - selector->n_entries = (u32)(entry - entries); - selector_sort_entries_by_name(selector); - } - - char *chosen_command = selector_update(ted, &ted->command_selector); + char *chosen_command = selector_update(ted, selector); if (chosen_command) { Command c = command_from_str(chosen_command); if (c != CMD_UNKNOWN) { @@ -423,18 +405,16 @@ static void command_selector_render(Ted *ted) { y1 += line_buffer_height + padding; - Selector *selector = &ted->command_selector; - selector->bounds = rect4(x1, y1, x2, y2); + Selector *selector = ted->command_selector; + selector_set_bounds(selector, rect4(x1, y1, x2, y2)); selector_render(ted, selector); text_render(font_bold); } static bool command_selector_close(Ted *ted) { - Selector *selector = &ted->command_selector; buffer_clear(ted->line_buffer); buffer_clear(ted->argument_buffer); - free(selector->entries); selector->entries = NULL; selector->n_entries = 0; return true; } @@ -564,6 +544,17 @@ void menu_init(Ted *ted) { ted_add_edit_notify(ted, menu_edit_notify, ted); + ted->command_selector = selector_new(); + for (Command c = 0; c < CMD_COUNT; ++c) { + const char *name = command_to_str(c); + if (c != CMD_UNKNOWN && *name) { + SelectorEntry entry = { + .name = name + }; + selector_add_entry(ted->command_selector, &entry); + } + } + MenuInfo save_as_menu = { .open = save_as_menu_open, .update = save_as_menu_update, @@ -630,4 +621,5 @@ void menu_init(Ted *ted) { void menu_quit(Ted *ted) { arr_clear(ted->all_menus); + selector_free(ted->command_selector); } diff --git a/ted-internal.h b/ted-internal.h index d35b14c..406ce55 100644 --- a/ted-internal.h +++ b/ted-internal.h @@ -179,49 +179,6 @@ typedef struct EditNotifyInfo { EditNotifyID id; } EditNotifyInfo; -/// an entry in a selector menu (e.g. the "open" menu) -typedef struct { - /// label - const char *name; - /// if not NULL, this will show on the right side of the entry. - const char *detail; - /// color to draw text in - u32 color; - /// use this for whatever you want - u64 userdata; -} SelectorEntry; - -struct Selector { - SelectorEntry *entries; - u32 n_entries; - Rect bounds; - /// index where the selector thing is - u32 cursor; - float scroll; - /// whether or not we should let the user select entries using a cursor. - bool enable_cursor; -}; - -/// file entries for file selectors -typedef struct { - /// just the file name - char *name; - /// full path - char *path; - FsType type; -} FileEntry; - -struct FileSelector { - char title[32]; - Selector sel; - Rect bounds; - u32 n_entries; - FileEntry *entries; - char cwd[TED_PATH_MAX]; - /// indicates that this is for creating files, not opening files - bool create_menu; -}; - /// max tabs per node #define TED_MAX_TABS 100 /// max strings in all config files @@ -334,8 +291,8 @@ struct Ted { /// index of currently open menu, or 0 if no menu is open u32 menu_open_idx; void *menu_context; - FileSelector file_selector; - Selector command_selector; + FileSelector *file_selector; + Selector *command_selector; /// general-purpose line buffer for inputs -- used for menus TextBuffer *line_buffer; /// use for "find" term in find/find+replace diff --git a/ted.h b/ted.h index d754e3e..8434501 100644 --- a/ted.h +++ b/ted.h @@ -132,6 +132,28 @@ typedef struct Selector Selector; /// a selector menu for files (e.g. the "open" menu) typedef struct FileSelector FileSelector; +/// an entry in a \ref Selector +/// +/// only `name` needs to be filled in; everything else can be zeroed. +typedef struct SelectorEntry { + /// color to draw text in + /// + /// if this is zero, \ref COLOR_TEXT will be used. + ColorSetting color; + /// label + /// + /// a copy of this string will be made, so you can free the pointer immediately after calling \ref selector_add_entry + const char *name; + /// if not NULL, this will show on the right side of the entry. + /// + /// a copy of this string will be made, so you can free the pointer immediately after calling \ref selector_add_entry + const char *detail; + /// use this for whatever you want + u64 userdata; + /// reserved for future use -- must be zeroed. + char reserved[32]; +} SelectorEntry; + /// a split or collection of tabs /// /// this handles ted's split-screen and tab features. @@ -786,7 +808,12 @@ void command_execute_string_argument(Ted *ted, Command c, const char *string); /// returns the best guess for the root directory of the project containing absolute path `path`. /// /// the return value should be freed. -char *settings_get_root_dir(Settings *settings, const char *path); +char *settings_get_root_dir(const Settings *settings, const char *path); +/// get color in `0xRRGGBBAA` format +u32 settings_color(const Settings *settings, ColorSetting color); +/// get color as four floats +void settings_color_floats(const Settings *settings, ColorSetting color, float f[4]); + // === find.c === /// which buffer will be searched? @@ -1168,10 +1195,48 @@ EditNotifyID ted_add_edit_notify(Ted *ted, EditNotify notify, void *context); void ted_remove_edit_notify(Ted *ted, EditNotifyID id); // === ui.c === +/// get a good size of button for this text +vec2 button_get_size(Ted *ted, const char *text); +/// render button +void button_render(Ted *ted, Rect button, const char *text, u32 color); +/// returns `true` if the button was clicked on. +bool button_update(Ted *ted, Rect button); +/// returns selected option, or 0 if none was selected +PopupOption popup_update(Ted *ted, u32 options); +/// render popup menu. +/// +/// `options` should be a bitwise-or of the `POPUP_*` constants. +void popup_render(Ted *ted, u32 options, const char *title, const char *body); +/// update and render checkbox +vec2 checkbox_frame(Ted *ted, bool *value, const char *label, vec2 pos); +/// create a new selector +Selector *selector_new(void); +/// set location where selector will be rendered. +void selector_set_bounds(Selector *s, Rect bounds); +/// add a new entry to this selector +void selector_add_entry(Selector *s, const SelectorEntry *entry); +/// set cursor position +void selector_set_cursor(Selector *s, u32 cursor); +/// get cursor position +u32 selector_get_cursor(Selector *s); +/// get entry at index in selector. +Status selector_get_entry(Selector *s, u32 index, SelectorEntry *entry); +/// get entry at selector cursor +/// +/// note that this can fail, e.g. if `s` has no entries. +Status selector_get_cursor_entry(Selector *s, SelectorEntry *entry); +/// clear all entries from this selector, WITHOUT resetting scroll or cursor position +void selector_clear_entries(Selector *s); +/// reset selector to \ref selector_new state +void selector_clear(Selector *s); +/// free resources used by selector +void selector_free(Selector *s); /// move selector cursor up by `n` entries void selector_up(Ted *ted, Selector *s, i64 n); /// move selector cursor down by `n` entries void selector_down(Ted *ted, Selector *s, i64 n); +/// sort entries by comparison function +void selector_sort_entries(Selector *s, int (*compar)(void *context, const SelectorEntry *e1, const SelectorEntry *e2), void *context); /// sort entries alphabetically void selector_sort_entries_by_name(Selector *s); /// returns a null-terminated UTF-8 string of the entry selected, or `NULL` if none was. @@ -1181,8 +1246,18 @@ void selector_sort_entries_by_name(Selector *s); char *selector_update(Ted *ted, Selector *s); /// render selector /// -/// NOTE: also renders the line buffer +/// make sure you call \ref selector_set_bounds before this. void selector_render(Ted *ted, Selector *s); +/// create a new file selector +FileSelector *file_selector_new(void); +/// set whether this file selector should allow inputting non-existent files +void file_selector_set_create(FileSelector *s, bool create); +/// free resources used by file selector +void file_selector_free(FileSelector *s); +/// set bounds for file selector +void file_selector_set_bounds(FileSelector *s, Rect bounds); +/// set file selector title (displayed above the selector in bold) +void file_selector_set_title(FileSelector *s, const char *title); /// free resources used by file selector void file_selector_free(FileSelector *fs); /// returns the name of the selected file, or `NULL` if none was selected. @@ -1190,21 +1265,11 @@ void file_selector_free(FileSelector *fs); /// the returned pointer should be freed. char *file_selector_update(Ted *ted, FileSelector *fs); /// render file selector -void file_selector_render(Ted *ted, FileSelector *fs); -/// get a good size of button for this text -vec2 button_get_size(Ted *ted, const char *text); -/// render button -void button_render(Ted *ted, Rect button, const char *text, u32 color); -/// returns `true` if the button was clicked on. -bool button_update(Ted *ted, Rect button); -/// returns selected option, or 0 if none was selected -PopupOption popup_update(Ted *ted, u32 options); -/// render popup menu. /// -/// `options` should be a bitwise-or of the `POPUP_*` constants. -void popup_render(Ted *ted, u32 options, const char *title, const char *body); -/// update and render checkbox -vec2 checkbox_frame(Ted *ted, bool *value, const char *label, vec2 pos); +/// make sure you call \ref file_selector_set_bounds before this. +void file_selector_render(Ted *ted, FileSelector *fs); +/// clear cwd, etc. +void file_selector_clear(FileSelector *fs); #ifdef __cplusplus diff --git a/ui.c b/ui.c index 9481df6..4f06b78 100644 --- a/ui.c +++ b/ui.c @@ -7,8 +7,100 @@ #include #endif +struct Selector { + SelectorEntry *entries; + Rect bounds; + u32 cursor; + float scroll; + bool enable_cursor; +}; + +struct FileSelector { + char title[32]; + Selector sel; + Rect bounds; + char cwd[TED_PATH_MAX]; + /// indicates that this is for creating files, not opening files + bool create_menu; +}; + static Status file_selector_cd_(Ted *ted, FileSelector *fs, const char *path, int symlink_depth); +static void selector_init(Selector *s) { + s->enable_cursor = true; +} + +Selector *selector_new(void) { + Selector *s = calloc(1, sizeof *s); + selector_init(s); + return s; +} + +void selector_free(Selector *s) { + selector_clear(s); + free(s); +} + +void selector_clear_entries(Selector *s) { + arr_foreach_ptr(s->entries, SelectorEntry, e) { + free((void *)e->name); + free((void *)e->detail); + } + arr_clear(s->entries); +} + +void selector_clear(Selector *s) { + selector_clear_entries(s); + s->scroll = 0; + s->cursor = 0; +} + +void selector_set_cursor(Selector *s, u32 pos) { + s->cursor = pos; +} + +u32 selector_get_cursor(Selector *s) { + return s->cursor; +} + +Status selector_get_entry(Selector *s, u32 index, SelectorEntry *entry) { + if (index >= arr_len(s->entries)) + return false; + *entry = s->entries[index]; + return true; +} + +void selector_set_bounds(Selector *s, Rect bounds) { + s->bounds = bounds; +} + +Status selector_get_cursor_entry(Selector *s, SelectorEntry *entry) { + return selector_get_entry(s, s->cursor, entry); +} + +FileSelector *file_selector_new(void) { + FileSelector *s = calloc(1, sizeof *s); + selector_init(&s->sel); + return s; +} + +void file_selector_set_create(FileSelector *s, bool create) { + s->create_menu = create; +} + +void file_selector_free(FileSelector *s) { + file_selector_clear(s); + free(s); +} + +void file_selector_set_bounds(FileSelector *s, Rect bounds) { + s->bounds = bounds; +} + +void file_selector_set_title(FileSelector *s, const char *title) { + strbuf_cpy(s->title, title); +} + static float selector_entries_start_y(Ted *ted, const Selector *s) { float padding = ted_active_settings(ted)->padding; @@ -24,7 +116,7 @@ static u32 selector_n_display_entries(Ted *ted, const Selector *s) { } static void selector_clamp_scroll(Ted *ted, Selector *s) { - float max_scroll = (float)s->n_entries - (float)selector_n_display_entries(ted, s); + float max_scroll = (float)arr_len(s->entries) - (float)selector_n_display_entries(ted, s); if (max_scroll < 0) max_scroll = 0; s->scroll = clampf(s->scroll, 0, max_scroll); } @@ -51,11 +143,11 @@ static bool selector_entry_pos(Ted *ted, const Selector *s, u32 i, Rect *r) { } void selector_up(Ted *ted, Selector *s, i64 n) { - if (!s->enable_cursor || s->n_entries == 0) { + if (!s->enable_cursor || arr_len(s->entries) == 0) { // can't do anything return; } - s->cursor = (u32)mod_i64(s->cursor - n, s->n_entries); + s->cursor = (u32)mod_i64(s->cursor - n, arr_len(s->entries)); selector_scroll_to_cursor(ted, s); } @@ -69,7 +161,7 @@ static int selectory_entry_cmp_name(const void *av, const void *bv) { } void selector_sort_entries_by_name(Selector *s) { - qsort(s->entries, s->n_entries, sizeof *s->entries, selectory_entry_cmp_name); + qsort(s->entries, arr_len(s->entries), sizeof *s->entries, selectory_entry_cmp_name); } char *selector_update(Ted *ted, Selector *s) { @@ -77,7 +169,7 @@ char *selector_update(Ted *ted, Selector *s) { TextBuffer *line_buffer = ted->line_buffer; ted->selector_open = s; - for (u32 i = 0; i < s->n_entries; ++i) { + for (u32 i = 0; i < arr_len(s->entries); ++i) { // check if this entry was clicked on Rect entry_rect; if (selector_entry_pos(ted, s, i, &entry_rect)) { @@ -95,7 +187,7 @@ char *selector_update(Ted *ted, Selector *s) { if (!ret) { if (s->enable_cursor) { // select this option - if (s->cursor < s->n_entries) + if (s->cursor < arr_len(s->entries)) ret = str_dup(s->entries[s->cursor].name); } else { @@ -114,7 +206,6 @@ char *selector_update(Ted *ted, Selector *s) { void selector_render(Ted *ted, Selector *s) { const Settings *settings = ted_active_settings(ted); - const u32 *colors = settings->colors; Font *font = ted->font; float padding = settings->padding; @@ -123,24 +214,25 @@ void selector_render(Ted *ted, Selector *s) { float x1, y1, x2, y2; rect_coords(bounds, &x1, &y1, &x2, &y2); - for (u32 i = 0; i < s->n_entries; ++i) { + for (u32 i = 0; i < arr_len(s->entries); ++i) { // highlight entry user is hovering over/selecting Rect entry_rect; if (selector_entry_pos(ted, s, i, &entry_rect)) { rect_clip_to_rect(&entry_rect, bounds); if (rect_contains_point(entry_rect, ted->mouse_pos) || (s->enable_cursor && s->cursor == i)) { // highlight it - gl_geometry_rect(entry_rect, colors[COLOR_MENU_HL]); + gl_geometry_rect(entry_rect, settings_color(settings, COLOR_MENU_HL)); } } } gl_geometry_draw(); - // search buffer - float line_buffer_height = ted_line_buffer_height(ted); - buffer_render(ted->line_buffer, rect4(x1, y1, x2, y1 + line_buffer_height)); - y1 += line_buffer_height; - + { + float line_buffer_height = ted_line_buffer_height(ted); + buffer_render(ted->line_buffer, rect4(x1, y1, x2, y1 + line_buffer_height)); + y1 += line_buffer_height; + } + TextRenderState text_state = text_render_state_default; text_state.min_x = x1; text_state.max_x = x2; @@ -149,8 +241,8 @@ void selector_render(Ted *ted, Selector *s) { text_state.render = true; // render entries themselves - SelectorEntry *entries = s->entries; - for (u32 i = 0; i < s->n_entries; ++i) { + SelectorEntry *const entries = s->entries; + for (u32 i = 0; i < arr_len(entries); ++i) { Rect r; if (selector_entry_pos(ted, s, i, &r)) { SelectorEntry *entry = &entries[i]; @@ -158,7 +250,7 @@ void selector_render(Ted *ted, Selector *s) { text_state.x = x; text_state.y = y; // draw name - rgba_u32_to_floats(entry->color, text_state.color); + settings_color_floats(settings, entry->color ? entry->color : COLOR_TEXT, text_state.color); text_state_break_kerning(&text_state); text_utf8_with_state(font, &text_state, entry->name); @@ -168,7 +260,7 @@ void selector_render(Ted *ted, Selector *s) { TextRenderState detail_state = text_state; detail_state.x = maxd(text_state.x + 2 * padding, x2 - detail_size); - rgba_u32_to_floats(colors[COLOR_COMMENT], detail_state.color); + settings_color_floats(settings, COLOR_COMMENT, detail_state.color); text_utf8_with_state(font, &detail_state, entry->detail); } } @@ -176,45 +268,21 @@ void selector_render(Ted *ted, Selector *s) { text_render(font); } - -// clear the entries in the file selector -static void file_selector_clear_entries(FileSelector *fs) { - for (u32 i = 0; i < fs->n_entries; ++i) { - free(fs->entries[i].name); - free(fs->entries[i].path); - } - free(fs->entries); - arr_free(fs->sel.entries); - fs->entries = NULL; - fs->n_entries = fs->sel.n_entries = 0; -} - -void file_selector_free(FileSelector *fs) { - file_selector_clear_entries(fs); +void file_selector_clear(FileSelector *fs) { + selector_clear(&fs->sel); memset(fs, 0, sizeof *fs); } -static int qsort_file_entry_cmp(void *search_termv, const void *av, const void *bv) { - const char *search_term = search_termv; - const FileEntry *a = av, *b = bv; +static int file_selector_entry_cmp(void *context, const SelectorEntry *a, const SelectorEntry *b) { + (void)context; + FsType a_type = (FsType)a->userdata, b_type = (FsType)b->userdata; // put directories first - if (a->type == FS_DIRECTORY && b->type != FS_DIRECTORY) { + if (a_type == FS_DIRECTORY && b_type != FS_DIRECTORY) { return -1; } - if (a->type != FS_DIRECTORY && b->type == FS_DIRECTORY) { + if (a_type != FS_DIRECTORY && b_type == FS_DIRECTORY) { return +1; } - if (search_term) { - bool a_prefix = str_has_prefix(a->name, search_term); - bool b_prefix = str_has_prefix(b->name, search_term); - if (a_prefix && !b_prefix) { - return -1; - } - if (b_prefix && !a_prefix) { - return +1; - } - } - return strcmp_case_insensitive(a->name, b->name); } @@ -335,6 +403,18 @@ static bool file_selector_cd(Ted *ted, FileSelector *fs, const char *path) { return file_selector_cd_(ted, fs, path, 0); } +static ColorSetting color_setting_for_file_type(FsType type) { + switch (type) { + case FS_FILE: return COLOR_TEXT; + case FS_DIRECTORY: return COLOR_TEXT_FOLDER; + default: return COLOR_TEXT_OTHER; + } +} + +void selector_sort_entries(Selector *s, int (*compar)(void *context, const SelectorEntry *e1, const SelectorEntry *e2), void *context) { + qsort_with_context(s->entries, arr_len(s->entries), sizeof *s->entries, (int (*) (void *, const void *, const void *))compar, context); +} + char *file_selector_update(Ted *ted, FileSelector *fs) { TextBuffer *line_buffer = ted->line_buffer; String32 search_term32 = buffer_get_line(line_buffer, 0); @@ -412,7 +492,8 @@ char *file_selector_update(Ted *ted, FileSelector *fs) { } // free previous entries - file_selector_clear_entries(fs); + selector_clear_entries(&fs->sel); + // get new entries FsDirectoryEntry **files; // if the directory we're in gets deleted, go back a directory. @@ -428,55 +509,23 @@ char *file_selector_update(Ted *ted, FileSelector *fs) { file_selector_cd(ted, fs, ".."); } - char *search_term = str32_to_utf8_cstr(buffer_get_line(line_buffer, 0)); if (files) { - u32 nfiles; - for (nfiles = 0; files[nfiles]; ++nfiles); - - // filter entries - bool increment = true; - for (u32 i = 0; i < nfiles; i += increment, increment = true) { - // remove if the file name does not contain the search term, - bool remove = search_term && *search_term && !strstr_case_insensitive(files[i]->name, search_term); - // or if this is just the current directory - remove |= streq(files[i]->name, "."); - if (remove) { - // remove this one - free(files[i]); - --nfiles; - if (nfiles) { - files[i] = files[nfiles]; - } - increment = false; - } - } - - if (nfiles) { - FileEntry *entries = ted_calloc(ted, nfiles, sizeof *entries); - if (entries) { - fs->n_entries = nfiles; - if (fs->sel.cursor >= fs->n_entries) fs->sel.cursor = nfiles - 1; - fs->entries = entries; - for (u32 i = 0; i < nfiles; ++i) { - char *name = files[i]->name; - entries[i].name = str_dup(name); - entries[i].type = files[i]->type; - // add cwd to start of file name - size_t path_size = strlen(name) + strlen(cwd) + 3; - char *path = ted_calloc(ted, 1, path_size); - if (path) { - path_full(cwd, name, path, path_size); - entries[i].path = path; - } else { - entries[i].path = NULL; // what can we do? - } - free(files[i]); - files[i] = NULL; - } + for (u32 i = 0; files[i]; ++i) { + char *name = files[i]->name; + if (streq(name, ".")) { + continue; } - qsort_with_context(entries, nfiles, sizeof *entries, qsort_file_entry_cmp, search_term); + SelectorEntry entry = { + .color = color_setting_for_file_type(files[i]->type), + .name = name, + .userdata = files[i]->type, + }; + selector_add_entry(&fs->sel, &entry); } + selector_sort_entries(&fs->sel, file_selector_entry_cmp, NULL); + for (u32 i = 0; files[i]; ++i) + free(files[i]); free(files); // set cwd to this (if no buffers are open, the "open" menu should use the last file selector's cwd) strbuf_cpy(ted->cwd, cwd); @@ -484,7 +533,6 @@ char *file_selector_update(Ted *ted, FileSelector *fs) { ted_error(ted, "Couldn't list directory '%s'.", cwd); } - free(search_term); return NULL; } @@ -524,26 +572,6 @@ void file_selector_render(Ted *ted, FileSelector *fs) { // render selector Selector *sel = &fs->sel; sel->bounds = bounds; - arr_clear(sel->entries); - for (u32 i = 0; i < fs->n_entries; ++i) { - ColorSetting color = 0; - switch (fs->entries[i].type) { - case FS_FILE: - color = COLOR_TEXT; - break; - case FS_DIRECTORY: - color = COLOR_TEXT_FOLDER; - break; - default: - color = COLOR_TEXT_OTHER; - break; - } - SelectorEntry entry = {.name = fs->entries[i].name, .color = colors[color]}; - arr_add(sel->entries, entry); - } - if (sel->entries) - sel->n_entries = fs->n_entries; - selector_render(ted, sel); text_render(font_bold); } @@ -690,3 +718,13 @@ vec2 checkbox_frame(Ted *ted, bool *value, const char *label, vec2 pos) { text_render(font); return vec2_add(size, (vec2){checkbox_size + padding * 0.5f, 0}); } + +void selector_add_entry(Selector *s, const SelectorEntry *entry) { + SelectorEntry s_entry = { + .color = entry->color, + .name = str_dup(entry->name), + .detail = str_dup(entry->detail), + .userdata = entry->userdata, + }; + arr_add(s->entries, s_entry); +} diff --git a/util.c b/util.c index 2be0f19..b146c82 100644 --- a/util.c +++ b/util.c @@ -167,6 +167,7 @@ size_t strn_len(const char *src, size_t n) { // duplicates at most n characters from src char *strn_dup(const char *src, size_t n) { + if (!src) return NULL; size_t len = strn_len(src, n); if (n > len) n = len; -- cgit v1.2.3