From e0901a1efb05727111eb88d1b27b7d1a23a87365 Mon Sep 17 00:00:00 2001 From: Albert Cervin Date: Sun, 23 Jun 2024 22:30:37 +0200 Subject: Fix buffer list switch and search/replace Fix the buffer list return key action when buffers have the same name. Previously, it would pick the first it could find in the buffer list with the correct buffer name instead of the selected one. Now it uses text properties to pass the actual buffer pointer along instead. This however exposed a problem with the clearing of properties and where in the frame it happens. Search and replace highlighting assumed that they could color things in their respective command executions. However, ideally coloring should happen in update functions so now both search and replace implement the coloring in update hooks for the buffer they are operating on. For replace, this was already kinda how it worked and could be adapted with minimal effort. Search on the other hand needed a bit more rework. --- src/dged/buffer.c | 6 +- src/dged/buffer_view.c | 4 - src/dged/text.h | 2 + src/main/cmds.c | 33 ++--- src/main/cmds.h | 1 + src/main/main.c | 12 ++ src/main/search-replace.c | 302 ++++++++++++++++++++++++++++------------------ src/main/search-replace.h | 10 ++ 8 files changed, 231 insertions(+), 139 deletions(-) diff --git a/src/dged/buffer.c b/src/dged/buffer.c index 9eab505..0fac694 100644 --- a/src/dged/buffer.c +++ b/src/dged/buffer.c @@ -61,7 +61,9 @@ static struct kill_ring { uint64_t found_at = -1; \ VEC_FOR_EACH_INDEXED(hooks, struct name##_hook *h, idx) { \ if (h->id == id) { \ - callback(h->userdata); \ + if (callback != NULL) { \ + callback(h->userdata); \ + } \ found_at = idx; \ break; \ } \ @@ -1073,6 +1075,8 @@ static void apply_properties(struct command_list *cmds, } break; } + case TextProperty_Data: + break; } } } diff --git a/src/dged/buffer_view.c b/src/dged/buffer_view.c index 2d0b6b8..4e67d78 100644 --- a/src/dged/buffer_view.c +++ b/src/dged/buffer_view.c @@ -479,8 +479,4 @@ void buffer_view_update(struct buffer_view *view, // draw buffer commands nested inside this command list command_list_draw_command_list(params->commands, buf_cmds); timer_stop(render_buffer_timer); - - // TODO: move to somewhere where more correct if buffers - // are in more than one view (same with buffer hooks). - buffer_clear_text_properties(view->buffer); } diff --git a/src/dged/text.h b/src/dged/text.h index 922014e..8b49ef4 100644 --- a/src/dged/text.h +++ b/src/dged/text.h @@ -59,6 +59,7 @@ bool text_line_contains_unicode(struct text *text, uint32_t line); enum text_property_type { TextProperty_Colors, + TextProperty_Data, }; struct text_property_colors { @@ -72,6 +73,7 @@ struct text_property { enum text_property_type type; union { struct text_property_colors colors; + void *userdata; }; }; diff --git a/src/main/cmds.c b/src/main/cmds.c index 04e42b4..4da8346 100644 --- a/src/main/cmds.c +++ b/src/main/cmds.c @@ -22,6 +22,7 @@ int32_t _abort(struct command_ctx ctx, int argc, const char *argv[]) { abort_replace(); + abort_search(); abort_completion(); disable_completion(minibuffer_buffer()); minibuffer_abort_prompt(); @@ -253,6 +254,12 @@ void buffer_to_list_line(struct buffer *buffer, void *userdata) { .set_fg = true, .fg = Color_Blue, }}); + + buffer_add_text_property( + listbuf, (struct location){.line = begin.line, .col = 0}, + (struct location){.line = begin.line, + .col = buffer_num_chars(listbuf, begin.line)}, + (struct text_property){.type = TextProperty_Data, .userdata = buffer}); } } @@ -261,22 +268,18 @@ int32_t buflist_visit_cmd(struct command_ctx ctx, int argc, struct window *w = ctx.active_window; struct buffer_view *bv = window_buffer_view(w); - struct text_chunk text = buffer_line(bv->buffer, bv->dot.line); - - char *end = (char *)memchr(text.text, ' ', text.nbytes); - - if (end != NULL) { - uint32_t len = end - (char *)text.text; - char *bufname = (char *)malloc(len + 1); - strncpy(bufname, (const char *)text.text, len); - bufname[len] = '\0'; - - struct buffer *target = buffers_find(ctx.buffers, bufname); - free(bufname); - if (target != NULL) { - window_set_buffer(w, target); + struct text_property *props[16] = {0}; + uint32_t nprops; + buffer_get_text_properties(bv->buffer, bv->dot, props, 16, &nprops); + + for (uint32_t propi = 0; propi < nprops; ++propi) { + struct text_property *p = props[propi]; + if (p->type == TextProperty_Data) { + window_set_buffer(w, p->userdata); + return 0; } } + return 0; } @@ -497,6 +500,8 @@ void register_global_commands(struct commands *commands, register_search_replace_commands(commands); } +void teardown_global_commands(void) { cleanup_search_replace(); } + #define BUFFER_VIEW_WRAPCMD(fn) \ static int32_t fn##_cmd(struct command_ctx ctx, int argc, \ const char *argv[]) { \ diff --git a/src/main/cmds.h b/src/main/cmds.h index a392e06..a258ce5 100644 --- a/src/main/cmds.h +++ b/src/main/cmds.h @@ -2,6 +2,7 @@ struct commands; void register_global_commands(struct commands *commands, void (*terminate_cb)()); +void teardown_global_commands(void); void register_buffer_commands(struct commands *commands); diff --git a/src/main/main.c b/src/main/main.c index 7dab26b..ee954f9 100644 --- a/src/main/main.c +++ b/src/main/main.c @@ -68,6 +68,13 @@ void segfault() { } #define INVALID_WATCH -1 + +static void clear_buffer_props(struct buffer *buffer, void *userdata) { + (void)userdata; + + buffer_clear_text_properties(buffer); +} + struct watched_file { uint32_t watch_id; struct buffer *buffer; @@ -344,6 +351,10 @@ int main(int argc, char *argv[]) { display_resized = false; } + // TODO: maybe this should be hidden behind something + // The placement is correct though. + buffers_for_each(&buflist, clear_buffer_props, NULL); + /* Update all windows together with the buffers in them. */ struct timer *update_windows = timer_start("update-windows"); windows_update(frame_alloc, frame_time); @@ -459,6 +470,7 @@ int main(int argc, char *argv[]) { } timers_destroy(); + teardown_global_commands(); destroy_completion(); windows_destroy(); minibuffer_destroy(); diff --git a/src/main/search-replace.c b/src/main/search-replace.c index 4def77a..50beb1e 100644 --- a/src/main/search-replace.c +++ b/src/main/search-replace.c @@ -7,6 +7,7 @@ #include "dged/buffer_view.h" #include "dged/command.h" #include "dged/minibuffer.h" +#include "dged/s8.h" #include "dged/window.h" #include "bindings.h" @@ -29,17 +30,62 @@ static struct replace { uint32_t nmatches; uint32_t current_match; buffer_keymap_id keymap_id; + uint32_t highlight_hook; struct window *window; } g_current_replace = {0}; -void abort_replace() { +static struct search { + bool active; + char *pattern; + struct region *matches; + struct buffer *buffer; + uint32_t nmatches; + uint32_t current_match; + uint32_t highlight_hook; + buffer_keymap_id keymap_id; +} g_current_search = {0}; + +static void clear_replace(void) { buffer_remove_keymap(g_current_replace.keymap_id); free(g_current_replace.matches); free(g_current_replace.replace); g_current_replace.matches = NULL; g_current_replace.replace = NULL; g_current_replace.nmatches = 0; + + if (g_current_replace.window != NULL) { + buffer_remove_update_hook(window_buffer(g_current_replace.window), + g_current_replace.highlight_hook, NULL); + } + g_current_replace.highlight_hook = 0; g_current_replace.window = NULL; +} + +void abort_replace(void) { + clear_replace(); + minibuffer_abort_prompt(); +} + +static void clear_search(void) { + // n.b. leak the pattern on purpose so + // it can be used to recall previous searches. + free(g_current_search.matches); + g_current_search.matches = NULL; + g_current_search.nmatches = 0; + + if (g_current_search.buffer != NULL && + g_current_search.highlight_hook != (uint32_t)-1) { + buffer_remove_update_hook(g_current_search.buffer, + g_current_search.highlight_hook, NULL); + } + g_current_search.highlight_hook = -1; + g_current_search.active = false; +} + +void abort_search(void) { + clear_search(); + + buffer_remove_keymap(g_current_search.keymap_id); minibuffer_abort_prompt(); } @@ -59,36 +105,52 @@ uint64_t matchdist(struct region *match, struct location loc) { return (linedist * linedist) * 1e6 + coldist * coldist; } -static void highlight_matches(struct buffer *buffer, struct match *matches, - uint32_t nmatches, uint32_t current) { - for (uint32_t matchi = 0; matchi < nmatches; ++matchi) { - struct match *m = &matches[matchi]; +static void highlight_match(struct buffer *buffer, struct region match, + bool current) { + if (current) { + buffer_add_text_property( + buffer, match.begin, match.end, + (struct text_property){.type = TextProperty_Colors, + .colors = (struct text_property_colors){ + .set_bg = true, + .bg = 3, + .set_fg = true, + .fg = 0, + }}); + + } else { + buffer_add_text_property( + buffer, match.begin, match.end, + (struct text_property){.type = TextProperty_Colors, + .colors = (struct text_property_colors){ + .set_bg = true, + .bg = 6, + .set_fg = true, + .fg = 0, + }}); + } +} + +static void search_highlight_hook(struct buffer *buffer, void *userdata) { + (void)userdata; + + for (uint32_t matchi = 0; matchi < g_current_search.nmatches; ++matchi) { + highlight_match(buffer, g_current_search.matches[matchi], + matchi == g_current_search.current_match); + } +} + +static void replace_highlight_hook(struct buffer *buffer, void *userdata) { + (void)userdata; + + for (uint32_t matchi = 0; matchi < g_current_replace.nmatches; ++matchi) { + struct match *m = &g_current_replace.matches[matchi]; if (m->state != Todo) { continue; } - if (matchi == current) { - buffer_add_text_property( - buffer, m->region.begin, m->region.end, - (struct text_property){.type = TextProperty_Colors, - .colors = (struct text_property_colors){ - .set_bg = true, - .bg = 3, - .set_fg = true, - .fg = 0, - }}); - - } else { - buffer_add_text_property( - buffer, m->region.begin, m->region.end, - (struct text_property){.type = TextProperty_Colors, - .colors = (struct text_property_colors){ - .set_bg = true, - .bg = 6, - .set_fg = true, - .fg = 0, - }}); - } + highlight_match(buffer, m->region, + matchi == g_current_replace.current_match); } } @@ -139,8 +201,6 @@ static int32_t replace_next(struct command_ctx ctx, int argc, buffer_view_goto(buffer_view, (struct location){.line = m->region.begin.line, .col = m->region.begin.col}); - highlight_matches(buffer_view->buffer, state->matches, state->nmatches, - state->current_match); } return 0; @@ -165,8 +225,6 @@ static int32_t skip_next(struct command_ctx ctx, int argc, const char *argv[]) { buffer_view_goto(buffer_view, (struct location){.line = m->region.begin.line, .col = m->region.begin.col}); - highlight_matches(buffer_view->buffer, state->matches, state->nmatches, - state->current_match); } return 0; @@ -242,8 +300,6 @@ static int32_t replace(struct command_ctx ctx, int argc, const char *argv[]) { struct region *m = &g_current_replace.matches[0].region; buffer_view_goto(buffer_view, (struct location){.line = m->begin.line, .col = m->begin.col}); - highlight_matches(buffer_view->buffer, g_current_replace.matches, - g_current_replace.nmatches, 0); struct binding bindings[] = { ANONYMOUS_BINDING(None, 'y', &replace_next_command), @@ -253,14 +309,12 @@ static int32_t replace(struct command_ctx ctx, int argc, const char *argv[]) { struct keymap km = keymap_create("replace", 8); keymap_bind_keys(&km, bindings, sizeof(bindings) / sizeof(bindings[0])); g_current_replace.keymap_id = buffer_add_keymap(minibuffer_buffer(), km); + g_current_replace.highlight_hook = + buffer_add_update_hook(buffer_view->buffer, replace_highlight_hook, NULL); return minibuffer_prompt(ctx, "replace? [yn] "); } -static char *g_last_search = NULL; -static bool g_last_search_interactive = false; -static buffer_keymap_id g_search_keymap; - const char *search_prompt(bool reverse) { const char *txt = "search (down): "; if (reverse) { @@ -275,96 +329,98 @@ struct closest_match { struct region closest; }; -static struct closest_match find_closest(struct buffer_view *view, - const char *pattern, bool highlight, - bool reverse) { - struct region *matches = NULL; - uint32_t nmatches = 0; - struct closest_match res = { - .found = false, - .closest = {0}, - }; - - buffer_find(view->buffer, pattern, &matches, &nmatches); - - // find the "nearest" match - if (nmatches > 0) { - res.found = true; - struct region *closest = &matches[0]; - int64_t closest_dist = INT64_MAX; - for (uint32_t matchi = 0; matchi < nmatches; ++matchi) { - struct region *m = &matches[matchi]; - - if (highlight) { - buffer_add_text_property( - view->buffer, m->begin, m->end, - (struct text_property){.type = TextProperty_Colors, - .colors = (struct text_property_colors){ - .set_bg = true, - .bg = 6, - .set_fg = true, - .fg = 0, - }}); - } - int res = location_compare(m->begin, view->dot); - uint64_t dist = matchdist(m, view->dot); - if (((res < 0 && reverse) || (res > 0 && !reverse)) && - dist < closest_dist) { - closest_dist = dist; - closest = m; - } - } - - if (highlight) { - buffer_add_text_property( - view->buffer, closest->begin, closest->end, - (struct text_property){.type = TextProperty_Colors, - .colors = (struct text_property_colors){ - .set_bg = true, - .bg = 3, - .set_fg = true, - .fg = 0, - }}); +static struct region *find_closest(struct region *matches, uint32_t nmatches, + struct location dot, bool reverse, + uint32_t *closest_idx) { + struct region *closest = &matches[0]; + *closest_idx = 0; + uint64_t closest_dist = UINT64_MAX; + for (uint32_t matchi = 0; matchi < nmatches; ++matchi) { + struct region *m = &matches[matchi]; + int res = location_compare(m->begin, dot); + uint64_t dist = matchdist(m, dot); + if (((res < 0 && reverse) || (res > 0 && !reverse)) && + dist < closest_dist) { + closest_dist = dist; + closest = m; + *closest_idx = matchi; } - res.closest = *closest; } - free(matches); - return res; + return closest; } -void do_search(struct buffer_view *view, const char *pattern, bool reverse) { - g_last_search = strdup(pattern); +static void do_search(struct buffer_view *view, const char *pattern, + bool reverse) { + if (view->buffer != g_current_search.buffer) { + clear_search(); + } + + g_current_search.buffer = view->buffer; + g_current_search.active = true; - struct closest_match m = find_closest(view, pattern, true, reverse); + // if we are in a new buffer, add the update hook for it. + if (g_current_search.highlight_hook == (uint32_t)-1) { + g_current_search.highlight_hook = + buffer_add_update_hook(view->buffer, search_highlight_hook, NULL); + } + + // replace the pattern if needed + if (g_current_search.pattern == NULL || + !s8eq(s8(g_current_search.pattern), s8(pattern))) { + char *new_pattern = strdup(pattern); + free(g_current_search.pattern); + g_current_search.pattern = new_pattern; + } - // find the "nearest" match - if (m.found) { - buffer_view_goto(view, (struct location){.line = m.closest.begin.line, - .col = m.closest.begin.col}); + // clear out any old search results first + if (g_current_search.matches != NULL) { + free(g_current_search.matches); + g_current_search.matches = NULL; + g_current_search.nmatches = 0; + } + + buffer_find(view->buffer, g_current_search.pattern, &g_current_search.matches, + &g_current_search.nmatches); + + if (g_current_search.nmatches > 0) { + // find the "nearest" match + uint32_t closest_idx = 0; + struct region *closest = + find_closest(g_current_search.matches, g_current_search.nmatches, + view->dot, reverse, &closest_idx); + buffer_view_goto(view, closest->begin); + g_current_search.current_match = closest_idx; } else { + abort_search(); minibuffer_echo_timeout(4, "%s not found", pattern); } } +static const char *get_pattern() { + struct text_chunk content = minibuffer_content(); + char *p = malloc(content.nbytes + 1); + memcpy(p, (const char *)content.text, content.nbytes); + p[content.nbytes] = '\0'; + return (const char *)p; +} + int32_t search_interactive(struct command_ctx ctx, int argc, const char *argv[]) { - g_last_search_interactive = true; + (void)argc; + (void)argv; + const char *pattern = NULL; if (minibuffer_content().nbytes == 0) { // recall the last search, if any - if (g_last_search != NULL) { - struct buffer_view *view = window_buffer_view(minibuffer_window()); - buffer_set_text(view->buffer, (uint8_t *)g_last_search, - strlen(g_last_search)); - pattern = g_last_search; + if (g_current_search.pattern != NULL) { + buffer_set_text(window_buffer(minibuffer_window()), + (uint8_t *)g_current_search.pattern, + strlen(g_current_search.pattern)); + pattern = strdup(g_current_search.pattern); } } else { - struct text_chunk content = minibuffer_content(); - char *p = malloc(content.nbytes + 1); - strncpy(p, (const char *)content.text, content.nbytes); - p[content.nbytes] = '\0'; - pattern = p; + pattern = get_pattern(); } minibuffer_set_prompt(search_prompt(*(bool *)ctx.userdata)); @@ -388,31 +444,27 @@ COMMAND_FN("search-backward", search_backward, search_interactive, int32_t find(struct command_ctx ctx, int argc, const char *argv[]) { bool reverse = *(bool *)ctx.userdata; if (argc == 0) { - g_last_search_interactive = false; struct binding bindings[] = { ANONYMOUS_BINDING(Ctrl, 'S', &search_forward_command), ANONYMOUS_BINDING(Ctrl, 'R', &search_backward_command), }; struct keymap m = keymap_create("search", 8); keymap_bind_keys(&m, bindings, sizeof(bindings) / sizeof(bindings[0])); - g_search_keymap = buffer_add_keymap(minibuffer_buffer(), m); + g_current_search.keymap_id = buffer_add_keymap(minibuffer_buffer(), m); return minibuffer_prompt(ctx, search_prompt(reverse)); } - buffer_remove_keymap(g_search_keymap); - if (g_last_search_interactive) { - g_last_search_interactive = false; + if (g_current_search.active) { + abort_search(); return 0; } - struct text_chunk content = minibuffer_content(); - char *pattern = malloc(content.nbytes + 1); - strncpy(pattern, (const char *)content.text, content.nbytes); - pattern[content.nbytes] = '\0'; - - do_search(window_buffer_view(ctx.active_window), pattern, reverse); - free(pattern); + buffer_remove_keymap(g_current_search.keymap_id); + do_search(window_buffer_view(ctx.active_window), argv[0], reverse); + if (g_current_search.active) { + abort_search(); + } return 0; } @@ -427,3 +479,13 @@ void register_search_replace_commands(struct commands *commands) { sizeof(search_replace_commands) / sizeof(search_replace_commands[0])); } + +void cleanup_search_replace(void) { + clear_search(); + if (g_current_search.pattern != NULL) { + free(g_current_search.pattern); + g_current_search.pattern = NULL; + } + + clear_replace(); +} diff --git a/src/main/search-replace.h b/src/main/search-replace.h index d0b2012..16869fc 100644 --- a/src/main/search-replace.h +++ b/src/main/search-replace.h @@ -5,6 +5,11 @@ struct commands; */ void abort_replace(); +/** + * Abort a search currently in progress. + */ +void abort_search(void); + /** * Register search and replace commands * @@ -12,3 +17,8 @@ void abort_replace(); * replace commands in. */ void register_search_replace_commands(struct commands *commands); + +/** + * Clear persistent search and replace data. + */ +void cleanup_search_replace(void); -- cgit v1.2.3