diff --git a/cmd.c b/cmd.c index e703784..4dd780d 100644 --- a/cmd.c +++ b/cmd.c @@ -506,6 +506,66 @@ int cmd_replace(cmd_context_t *ctx) { return cursor_replace(ctx->cursor, 1, NULL, NULL); } +// Interactive search and replace on all buffers +int cmd_replace_all(cmd_context_t *ctx) { + bview_t *bview, *bview_orig; + int num_bviews, do_replace_all, num_replacements, num_replacements_tmp; + int cancelled, buffer_done_i, i, already_done; + char *regex, *replacement; + buffer_t **buffer_done; + + bview_orig = ctx->bview; + do_replace_all = 0; + num_replacements = 0; + cancelled = 0; + regex = NULL; + replacement = NULL; + num_bviews = 0; + CDL_COUNT2(ctx->editor->all_bviews, bview, num_bviews, all_next); + buffer_done = calloc(num_bviews, sizeof(buffer_t *)); + buffer_done_i = 0; + + do { + editor_prompt(ctx->editor, "replace_all: Search regex?", NULL, ®ex); + if (!regex) break; + editor_prompt(ctx->editor, "replace_all: Replacement string?", NULL, &replacement); + if (!replacement) break; + + CDL_FOREACH2(ctx->editor->all_bviews, bview, all_next) { + if (!MLE_BVIEW_IS_EDIT(bview)) continue; + + // Check if buffer in done list + // TODO inefficient, could replace with hash + for (i = 0, already_done = 0; i < buffer_done_i; i++) { + if (buffer_done[i] == bview->buffer) { + already_done = 1; + break; + } + } + if (already_done) continue; + + // Search and replace on this bview + editor_set_active(ctx->editor, bview); + cursor_replace_ex(bview->active_cursor, 1, regex, replacement, "replace_all", &do_replace_all, &num_replacements_tmp, &cancelled); + num_replacements += num_replacements_tmp; + + // Add buffer to done list + buffer_done[buffer_done_i++] = bview->buffer; + if (cancelled) break; + } + } while (0); + + editor_set_active(ctx->editor, bview_orig); + + MLE_SET_INFO(ctx->editor, "replace_all: Replaced %d instance(s) in %d buffer(s)", num_replacements, buffer_done_i); + + if (regex) free(regex); + if (replacement) free(replacement); + if (buffer_done) free(buffer_done); + + return MLE_OK; +} + // Repeat last cmd int cmd_repeat(cmd_context_t *ctx) { // This is a special case in _editor_loop diff --git a/cursor.c b/cursor.c index 9b5c23e..fff0945 100644 --- a/cursor.c +++ b/cursor.c @@ -275,6 +275,11 @@ int cursor_uncut_last(cursor_t *cursor) { // Regex search and replace int cursor_replace(cursor_t *cursor, int interactive, char *opt_regex, char *opt_replacement) { + return cursor_replace_ex(cursor, interactive, opt_regex, opt_replacement, NULL, NULL, NULL, NULL); +} + +// Regex search and replace (extended params) +int cursor_replace_ex(cursor_t *cursor, int interactive, char *opt_regex, char *opt_replacement, char *opt_cmd_name, int *inout_all, int *optret_num_replacements, int *optret_cancelled) { char *regex; char *replacement; int wrapped; @@ -295,9 +300,12 @@ int cursor_replace(cursor_t *cursor, int interactive, char *opt_regex, char *opt PCRE2_SIZE pcre_ovector[30]; str_t repl_backref = {0}; int num_replacements; + char *cmd_name; if (!interactive && (!opt_regex || !opt_replacement)) { return MLE_ERR; + } else if ((opt_regex && !opt_replacement) || (!opt_regex && opt_replacement)) { + return MLE_ERR; } regex = NULL; @@ -309,19 +317,20 @@ int cursor_replace(cursor_t *cursor, int interactive, char *opt_regex, char *opt search_mark = NULL; search_mark_end = NULL; anchored_before = 0; - all = interactive ? 0 : 1; + all = interactive ? (inout_all ? *inout_all : 0) : 1; num_replacements = 0; mark_set_pcre_capture(&pcre_rc, pcre_ovector, 30); orig_viewport_y = -1; + cmd_name = opt_cmd_name ? opt_cmd_name : "replace"; do { - if (!interactive) { + if (!interactive || (opt_regex && opt_replacement)) { regex = strdup(opt_regex); replacement = strdup(opt_replacement); } else { - editor_prompt(cursor->bview->editor, "replace: Search regex?", NULL, ®ex); + editor_prompt_fmt(cursor->bview->editor, NULL, ®ex, "%s: Search regex?", cmd_name); if (!regex) break; - editor_prompt(cursor->bview->editor, "replace: Replacement string?", NULL, &replacement); + editor_prompt_fmt(cursor->bview->editor, NULL, &replacement, "%s: Replacement string?", cmd_name); if (!replacement) break; } orig_mark = buffer_add_mark(cursor->bview->buffer, NULL, 0); @@ -360,14 +369,16 @@ int cursor_replace(cursor_t *cursor, int interactive, char *opt_regex, char *opt buffer_add_srule(cursor->bview->buffer, highlight); bview_rectify_viewport(cursor->bview); bview_draw(cursor->bview); - editor_prompt(cursor->bview->editor, "replace: OK to replace? (y=yes, n=no, a=all, C-c=stop)", - &(editor_prompt_params_t) { .kmap = cursor->bview->editor->kmap_prompt_yna }, &yn + editor_prompt_fmt(cursor->bview->editor, + &(editor_prompt_params_t) { .kmap = cursor->bview->editor->kmap_prompt_yna }, &yn, + "%s: OK to replace? (y=yes, n=no, a=all, C-c=stop)", cmd_name ); buffer_remove_srule(cursor->bview->buffer, highlight); srule_destroy(highlight); bview_draw(cursor->bview); } if (!yn) { + if (optret_cancelled) *optret_cancelled = 1; break; } else if (0 == strcmp(yn, MLE_PROMPT_YES) || 0 == strcmp(yn, MLE_PROMPT_ALL)) { str_append_replace_with_backrefs(&repl_backref, search_mark->bline->data, replacement, pcre_rc, pcre_ovector, 30); @@ -404,7 +415,11 @@ int cursor_replace(cursor_t *cursor, int interactive, char *opt_regex, char *opt if (search_mark_end) mark_destroy(search_mark_end); if (interactive) { - MLE_SET_INFO(cursor->bview->editor, "replace: Replaced %d instance(s)", num_replacements); + if (optret_num_replacements) { + *optret_num_replacements = num_replacements; + } else { + MLE_SET_INFO(cursor->bview->editor, "%s: Replaced %d instance(s)", cmd_name, num_replacements); + } if (orig_viewport_y >= 0) { bview_set_viewport_y(cursor->bview, orig_viewport_y, 1); } else { @@ -413,6 +428,8 @@ int cursor_replace(cursor_t *cursor, int interactive, char *opt_regex, char *opt bview_draw(cursor->bview); } + if (inout_all) *inout_all = all; + return MLE_OK; } diff --git a/editor.c b/editor.c index 4992e59..536e432 100644 --- a/editor.c +++ b/editor.c @@ -310,6 +310,21 @@ int editor_prompt(editor_t *editor, char *prompt, editor_prompt_params_t *params return MLE_OK; } +// A printf version of editor_prompt +int editor_prompt_fmt(editor_t *editor, editor_prompt_params_t *params, char **optret_answer, char *prompt_fmt, ...) { + char prompt[512]; + va_list vl; + int rv; + + va_start(vl, prompt_fmt); + rv = vsnprintf(prompt, sizeof(prompt), prompt_fmt, vl); + va_end(vl); + + if (rv < 0 || rv >= (int)sizeof(prompt)) return MLE_ERR; + + return editor_prompt(editor, prompt, params, optret_answer); +} + // Open dialog menu int editor_menu(editor_t *editor, cmd_func_t callback, char *opt_buf_data, int opt_buf_data_len, aproc_t *opt_aproc, bview_t **optret_menu) { bview_t *menu; @@ -1729,6 +1744,7 @@ static void _editor_register_cmds(editor_t *editor) { _editor_register_cmd_fn(editor, "cmd_remove_extra_cursors", cmd_remove_extra_cursors); _editor_register_cmd_fn(editor, "cmd_repeat", cmd_repeat); _editor_register_cmd_fn(editor, "cmd_replace", cmd_replace); + _editor_register_cmd_fn(editor, "cmd_replace_all", cmd_replace_all); _editor_register_cmd_fn(editor, "cmd_rfind_word", cmd_rfind_word); _editor_register_cmd_fn(editor, "cmd_rsearch", cmd_rsearch); _editor_register_cmd_fn(editor, "cmd_save_as", cmd_save_as); @@ -1825,6 +1841,7 @@ static void _editor_init_kmaps(editor_t *editor) { MLE_KBINDING_DEF("cmd_isearch", "C-r"), MLE_KBINDING_DEF("cmd_repeat", "f5"), MLE_KBINDING_DEF("cmd_replace", "C-t"), + MLE_KBINDING_DEF("cmd_replace_all", "CM-t"), MLE_KBINDING_DEF("cmd_cut", "C-k"), MLE_KBINDING_DEF("cmd_copy", "M-k"), MLE_KBINDING_DEF("cmd_uncut", "C-u"), diff --git a/mle.h b/mle.h index 9494ac9..b6e5172 100644 --- a/mle.h +++ b/mle.h @@ -423,6 +423,7 @@ int editor_init(editor_t *editor, int argc, char **argv); int editor_run(editor_t *editor); int editor_deinit(editor_t *editor); int editor_prompt(editor_t *editor, char *prompt, editor_prompt_params_t *params, char **optret_answer); +int editor_prompt_fmt(editor_t *editor, editor_prompt_params_t *params, char **optret_answer, char *prompt_fmt, ...); int editor_menu(editor_t *editor, cmd_func_t fn_callback, char *opt_buf_data, int opt_buf_data_len, aproc_t *opt_aproc, bview_t **optret_menu); int editor_open_bview(editor_t *editor, bview_t *opt_parent, int type, char *opt_path, int opt_path_len, int make_active, bint_t linenum, int skip_resize, buffer_t *opt_buffer, bview_t **optret_bview); int editor_close_bview(editor_t *editor, bview_t *bview, int *optret_num_closed); @@ -477,6 +478,7 @@ int cursor_get_mark(cursor_t *cursor, mark_t **ret_mark); int cursor_get_anchor(cursor_t *cursor, mark_t **ret_anchor); int cursor_lift_anchor(cursor_t *cursor); int cursor_replace(cursor_t *cursor, int interactive, char *opt_regex, char *opt_replacement); +int cursor_replace_ex(cursor_t *cursor, int interactive, char *opt_regex, char *opt_replacement, char *opt_cmd_name, int *inout_all, int *optret_num_replacements, int *optret_cancelled); int cursor_select_between(cursor_t *cursor, mark_t *a, mark_t *b, int use_srules); int cursor_select_by(cursor_t *cursor, const char *strat, int use_srules); int cursor_select_by_bracket(cursor_t *cursor, int use_srules); @@ -564,6 +566,7 @@ int cmd_redraw(cmd_context_t *ctx); int cmd_remove_extra_cursors(cmd_context_t *ctx); int cmd_repeat(cmd_context_t *ctx); int cmd_replace(cmd_context_t *ctx); +int cmd_replace_all(cmd_context_t *ctx); int cmd_rfind_word(cmd_context_t *ctx); int cmd_rsearch(cmd_context_t *ctx); int cmd_save_as(cmd_context_t *ctx); diff --git a/tests/func/test_search_replace.sh b/tests/func/test_search_replace.sh index e31f391..44c6f52 100755 --- a/tests/func/test_search_replace.sh +++ b/tests/func/test_search_replace.sh @@ -89,3 +89,19 @@ declare -A expected expected[history_line]='^bview.0.cursor.0.mark.line_index=1$' expected[history_col ]='^bview.0.cursor.0.mark.col=0$' source 'test.sh' + +# cmd_replace_all (all) +macro='1 a C-n 2 b C-n 3 c CM-t ( \ d ) ( \ w ) enter $ 2 $ 1 enter a' +declare -A expected +expected[replace_all_1_1]='^a1$' +expected[replace_all_1_2]='^b2$' +expected[replace_all_1_3]='^c3$' +source 'test.sh' + +# cmd_replace_all (no, yes, cancel) +macro='1 a C-n 2 b C-n 3 c CM-t ( \ d ) ( \ w ) enter $ 2 $ 1 enter n y C-c' +declare -A expected +expected[replace_all_2_1]='^1a$' +expected[replace_all_2_2]='^b2$' +expected[replace_all_2_3]='^3c$' +source 'test.sh'