From 0135ebfa879af50f19ddc1495a36be3c030a6326 Mon Sep 17 00:00:00 2001 From: Leo Tenenbaum Date: Tue, 9 Feb 2021 14:38:59 -0500 Subject: find + replace seems to be working --- arr.c | 7 +++ command.c | 7 ++- command.h | 2 + find.c | 146 ++++++++++++++++++++++++++++++++++++++++++++++++++------------ main.c | 1 + ted.cfg | 1 + ted.h | 3 +- 7 files changed, 137 insertions(+), 30 deletions(-) diff --git a/arr.c b/arr.c index dea2760..0e545f7 100644 --- a/arr.c +++ b/arr.c @@ -157,6 +157,12 @@ static void arr_set_len_(void **arr, size_t member_size, size_t n) { } } +static void arr_remove_(void *arr, size_t member_size, size_t index) { + ArrHeader *hdr = arr_hdr_(arr); + assert(index < hdr->len); + memmove((char *)arr + index * member_size, (char *)arr + (index+1) * member_size, (hdr->len - (index+1)) * member_size); + --hdr->len; +} #ifdef __cplusplus #define arr_cast_typeof(a) (decltype(a)) @@ -183,6 +189,7 @@ static void arr_set_len_(void **arr, size_t member_size, size_t n) { // the newly-added elements are zero-initialized. #define arr_qsort(a, cmp) qsort((a), arr_len(a), sizeof *(a), (cmp)) #define arr_remove_last(a) do { assert(a); if (--arr_hdr_(a)->len == 0) arr_free(a); } while (0) +#define arr_remove(a, i) arr_remove_((a), sizeof *(a), (i)) #define arr_pop_last(a) ((a)[--arr_hdr_(a)->len]) #define arr_size_in_bytes(a) (arr_len(a) * sizeof *(a)) #define arr_lastp(a) ((a) ? &(a)[arr_len(a)-1] : NULL) diff --git a/command.c b/command.c index 593779d..19c291e 100644 --- a/command.c +++ b/command.c @@ -107,7 +107,7 @@ void command_execute(Ted *ted, Command c, i64 argument) { switch (ted->menu) { case MENU_NONE: if (ted->find) { - if (buffer == &ted->find_buffer) { + if (buffer == &ted->find_buffer || buffer == &ted->replace_buffer) { if (c == CMD_NEWLINE) find_next(ted); else @@ -262,7 +262,10 @@ void command_execute(Ted *ted, Command c, i64 argument) { break; case CMD_FIND: - find_open(ted); + find_open(ted, false); + break; + case CMD_FIND_REPLACE: + find_open(ted, true); break; case CMD_ESCAPE: diff --git a/command.h b/command.h index 74741c4..fcdde38 100644 --- a/command.h +++ b/command.h @@ -52,6 +52,7 @@ ENUM_U16 { CMD_CUT, CMD_PASTE, CMD_FIND, + CMD_FIND_REPLACE, CMD_TAB_CLOSE, CMD_TAB_SWITCH, // argument = index of tab (starting at 0) @@ -115,6 +116,7 @@ static CommandName const command_names[CMD_COUNT] = { {"cut", CMD_CUT}, {"paste", CMD_PASTE}, {"find", CMD_FIND}, + {"find-replace", CMD_FIND_REPLACE}, {"tab-close", CMD_TAB_CLOSE}, {"tab-switch", CMD_TAB_SWITCH}, {"tab-next", CMD_TAB_NEXT}, diff --git a/find.c b/find.c index 0d3d3f4..a4db362 100644 --- a/find.c +++ b/find.c @@ -5,6 +5,20 @@ static u32 find_compilation_flags(Ted *ted) { | (ted->find_regex ? 0 : PCRE2_LITERAL); } +static u32 find_replace_flags(Ted *ted) { + return (ted->find_regex ? 0 : PCRE2_SUBSTITUTE_LITERAL); +} + +static void ted_seterr_to_pcre2_err(Ted *ted, int err) { + char32_t buf[256] = {0}; + size_t len = (size_t)pcre2_get_error_message(err, buf, arr_count(buf) - 1); + char *error_cstr = str32_to_utf8_cstr(str32(buf, len)); + if (error_cstr) { + ted_seterr(ted, "Search error: %s.", error_cstr); + free(error_cstr); + } +} + static bool find_compile_pattern(Ted *ted) { TextBuffer *find_buffer = &ted->find_buffer; String32 term = buffer_get_line(find_buffer, 0); @@ -21,16 +35,6 @@ static bool find_compile_pattern(Ted *ted) { return true; } else { ted->find_invalid_pattern = true; - #if 0 - // @TODO: write this to a buffer and check it in find_next_in_direction - char32_t buf[256] = {0}; - size_t len = (size_t)pcre2_get_error_message(error, buf, sizeof buf - 1); - char *error_cstr = str32_to_utf8_cstr(str32(buf, len)); - if (error_cstr) { - ted_seterr(ted, "Invalid search term (position %zu): %s.", (size_t)error_pos, error_cstr); - free(error_cstr); - } - #endif } pcre2_match_data_free(match_data); } else { @@ -54,13 +58,17 @@ static void find_free_pattern(Ted *ted) { arr_clear(ted->find_results); } -static void find_open(Ted *ted) { +static void find_open(Ted *ted, bool replace) { if (!ted->find && ted->active_buffer) { ted->prev_active_buffer = ted->active_buffer; ted->active_buffer = &ted->find_buffer; ted->find = true; buffer_clear(&ted->find_buffer); } + if (!ted->replace && replace) { + ted->replace = true; + buffer_clear(&ted->replace_buffer); + } } static void find_close(Ted *ted) { @@ -75,7 +83,7 @@ static float find_menu_height(Ted *ted) { Settings const *settings = &ted->settings; float padding = settings->padding; - return 2 * char_height + 5 * padding; + return 2 * char_height + (padding + char_height) * ted->replace + 5 * padding; } // finds the next match in the buffer, returning false if there is no match this line. @@ -158,6 +166,19 @@ static void find_update(Ted *ted) { } } +// returns the index of the match we are "on", or U32_MAX for none. +static u32 find_match_idx(Ted *ted) { + TextBuffer *buffer = ted->prev_active_buffer; + if (!buffer->selection) return U32_MAX; + u32 match_idx = U32_MAX; + arr_foreach_ptr(ted->find_results, FindResult, result) { + if (buffer_pos_eq(result->start, buffer->selection_pos) + && buffer_pos_eq(result->end, buffer->cursor_pos)) + match_idx = (u32)(result - ted->find_results); + } + return match_idx; +} + static void find_menu_frame(Ted *ted) { Font *font = ted->font, *font_bold = ted->font_bold; float const char_height = text_font_char_height(font), @@ -169,7 +190,7 @@ static void find_menu_frame(Ted *ted) { float const window_width = ted->window_width, window_height = ted->window_height; u32 const *colors = settings->colors; - TextBuffer *buffer = ted->prev_active_buffer, *find_buffer = &ted->find_buffer; + TextBuffer *buffer = ted->prev_active_buffer, *find_buffer = &ted->find_buffer, *replace_buffer = &ted->replace_buffer; assert(buffer); u32 first_rendered_line = buffer_first_rendered_line(buffer); @@ -177,14 +198,9 @@ static void find_menu_frame(Ted *ted) { find_update(ted); - u32 match_pos = U32_MAX; // index of result we are on arr_foreach_ptr(ted->find_results, FindResult, result) { // highlight matches BufferPos p1 = result->start, p2 = result->end; - if (buffer->selection - && buffer_pos_eq(p1, buffer->selection_pos) - && buffer_pos_eq(p2, buffer->cursor_pos)) - match_pos = (u32)(result - ted->find_results); if (p2.line >= first_rendered_line && p1.line <= last_rendered_line) { v2 pos1 = buffer_pos_to_pixels(buffer, p1); v2 pos2 = buffer_pos_to_pixels(buffer, p2); @@ -195,12 +211,13 @@ static void find_menu_frame(Ted *ted) { } } - + bool replace = ted->replace; float x1 = padding, y1 = window_height - menu_height + padding, x2 = window_width - padding, y2 = window_height - padding; - char const *find_text = "Find..."; - float find_text_width = 0; - text_get_size(font_bold, find_text, &find_text_width, NULL); + char const *find_text = "Find...", *replace_text = "Replace with"; + float text_width = 0; + text_get_size(font_bold, replace ? replace_text : find_text, &text_width, NULL); + Rect menu_bounds = rect4(x1, y1, x2, y2); @@ -209,17 +226,19 @@ static void find_menu_frame(Ted *ted) { x2 -= padding; y2 -= padding; - Rect find_buffer_bounds = rect4(x1 + find_text_width + padding, y1, x2 - padding, y1 + char_height); + Rect find_buffer_bounds = rect4(x1 + text_width + padding, y1, x2 - padding, y1 + char_height); + Rect replace_buffer_bounds = rect_translate(find_buffer_bounds, V2(0, char_height + padding)); gl_geometry_rect(menu_bounds, colors[COLOR_MENU_BG]); gl_geometry_rect_border(menu_bounds, 1, colors[COLOR_BORDER]); { float w = 0, h = 0; char str[32]; - if (match_pos == U32_MAX) { + u32 match_idx = find_match_idx(ted); + if (match_idx == U32_MAX) { strbuf_printf(str, U32_FMT " matches", arr_len(ted->find_results)); } else if (buffer->selection) { - strbuf_printf(str, U32_FMT " of " U32_FMT, match_pos + 1, arr_len(ted->find_results)); + strbuf_printf(str, U32_FMT " of " U32_FMT, match_idx + 1, arr_len(ted->find_results)); } text_get_size(font, str, &w, &h); text_utf8(font, str, x2 - w, rect_ymid(find_buffer_bounds) - h * 0.5f, colors[COLOR_TEXT]); @@ -227,9 +246,14 @@ static void find_menu_frame(Ted *ted) { find_buffer_bounds.size.x -= w; } - text_utf8(font_bold, "Find...", x1, y1, colors[COLOR_TEXT]); + text_utf8(font_bold, find_text, x1, y1, colors[COLOR_TEXT]); y1 += char_height_bold + padding; + if (replace) { + text_utf8(font_bold, replace_text, x1, y1, colors[COLOR_TEXT]); + y1 += char_height_bold + padding; + } + gl_geometry_draw(); text_render(font_bold); @@ -237,7 +261,20 @@ static void find_menu_frame(Ted *ted) { x += checkbox_frame(ted, &ted->find_case_sensitive, "Case sensitive", V2(x, y1)).x + 2*padding; x += checkbox_frame(ted, &ted->find_regex, "Regular expression", V2(x, y1)).x + 2*padding; + if (replace) { + // check if the find or replace line buffer was clicked on + for (u32 i = 0; i < ted->nmouse_clicks[SDL_BUTTON_LEFT]; ++i) { + v2 point = ted->mouse_clicks[SDL_BUTTON_LEFT][i]; + if (rect_contains_point(find_buffer_bounds, point)) + ted->active_buffer = find_buffer; + else if (rect_contains_point(replace_buffer_bounds, point)) + ted->active_buffer = replace_buffer; + + } + } + buffer_render(find_buffer, find_buffer_bounds); + if (replace) buffer_render(replace_buffer, replace_buffer_bounds); String32 term = buffer_get_line(find_buffer, 0); @@ -256,7 +293,6 @@ static void find_next_in_direction(Ted *ted, int direction) { BufferPos pos = direction == +1 || !buffer->selection ? buffer->cursor_pos : buffer->selection_pos; u32 nlines = buffer->nlines; - find_update(ted); // we need to search the starting line twice, because we might start at a non-zero index for (size_t nsearches = 0; nsearches < nlines + 1; ++nsearches) { @@ -273,10 +309,66 @@ static void find_next_in_direction(Ted *ted, int direction) { // go to next find result static void find_next(Ted *ted) { + find_update(ted); + if (!ted->find_code) return; + + if (ted->replace) { + TextBuffer *buffer = ted->prev_active_buffer; + u32 match_idx = find_match_idx(ted); + if (match_idx != U32_MAX) { + FindResult match = ted->find_results[match_idx]; + assert(match.start.line == match.end.line); + String32 line = buffer_get_line(buffer, match.start.line); + String32 replacement = buffer_get_line(&ted->replace_buffer, 0); + // we are currently highlighting the find pattern, let's replace it + + // get size of buffer needed. + PCRE2_SIZE output_size = 0; + u32 flags = find_replace_flags(ted); + char32_t *str = line.str + match.start.index; + u32 len = match.end.index - match.start.index; + + int ret = pcre2_substitute(ted->find_code, str, len, 0, + PCRE2_SUBSTITUTE_OVERFLOW_LENGTH|flags, ted->find_match_data, NULL, replacement.str, + replacement.len, NULL, &output_size); + char32_t *output_buffer = output_size + ? calloc(output_size, sizeof *output_buffer) + : NULL; + if (output_buffer || !output_size) { + ret = pcre2_substitute(ted->find_code, str, len, 0, + flags, ted->find_match_data, NULL, replacement.str, + replacement.len, output_buffer, &output_size); + if (ret > 0) { + buffer->selection = false; // stop selecting match + buffer_delete_chars_at_pos(buffer, match.start, len); + if (output_buffer) + buffer_insert_text_at_pos(buffer, match.start, str32(output_buffer, output_size)); + + // remove this match + arr_remove(ted->find_results, match_idx); + + i64 diff = (i64)output_size - len; // change in number of characters + arr_foreach_ptr(ted->find_results, FindResult, result) { + if (result->start.line == match.start.line && result->start.index > match.end.index) { + // fix indices of other find results + result->start.index = (u32)(result->start.index + diff); + result->end.index = (u32)(result->end.index + diff); + } + } + } else if (ret < 0) { + ted_seterr_to_pcre2_err(ted, ret); + } + free(output_buffer); + } else { + ted_seterr(ted, "Out of memory."); + } + } + } find_next_in_direction(ted, +1); } static void find_prev(Ted *ted) { + find_update(ted); find_next_in_direction(ted, -1); } diff --git a/main.c b/main.c index e5f6bb9..ac3e618 100644 --- a/main.c +++ b/main.c @@ -642,6 +642,7 @@ int main(int argc, char **argv) { text_state.wrap = true; rgba_u32_to_floats(colors[COLOR_ERROR_TEXT], text_state.color); text_utf8_with_state(font, &text_state, ted->error_shown); + gl_geometry_draw(); text_render(font); } } diff --git a/ted.cfg b/ted.cfg index 75da8bf..24d2e56 100644 --- a/ted.cfg +++ b/ted.cfg @@ -85,6 +85,7 @@ Ctrl+q = :quit Ctrl+z = :undo Ctrl+Shift+z = :redo Ctrl+f = :find +Ctrl+Shift+f = :find-replace Ctrl+c = :copy Ctrl+x = :cut Ctrl+v = :paste diff --git a/ted.h b/ted.h index be41ac8..b1d5554 100644 --- a/ted.h +++ b/ted.h @@ -226,7 +226,8 @@ typedef struct Ted { KeyAction key_actions[KEY_COMBO_COUNT]; bool search_cwd; // should the working directory be searched for files? set to true if the executable isn't "installed" bool quit; // if set to true, the window will close next frame. NOTE: this doesn't check for unsaved changes!! - bool find; // is the find menu open? + bool find; // is the find or find+replace menu open? + bool replace; // is the find+replace menu open? bool find_regex, find_case_sensitive; // find options u32 find_flags; // flags used last time search term was compiled pcre2_code *find_code; -- cgit v1.2.3