diff --git a/doc/man/mcedit.1.in b/doc/man/mcedit.1.in index 33e5f34b7..39019ac8d 100644 --- a/doc/man/mcedit.1.in +++ b/doc/man/mcedit.1.in @@ -586,6 +586,10 @@ deleting, navigating, typing) Search autocomplete candidates in entire file (1) or just from beginning of file to cursor position (0). .TP +.I editor_wordcompletion_collect_all_files +Search autocomplete candidates from all loaded files (1, default), not only from +the currently edited one (0). +.TP .I spell_language Spelling language (en, en\-variant_0, ru, etc) installed with aspell package (a full list can be obtained using 'aspell' utility). diff --git a/src/editor/editcmd.c b/src/editor/editcmd.c index 0d2caa923..4f3f06e35 100644 --- a/src/editor/editcmd.c +++ b/src/editor/editcmd.c @@ -6,7 +6,7 @@ Written by: Paul Sheer, 1996, 1997 - Andrew Borodin , 2012-2014 + Andrew Borodin , 2012-2021 Ilia Maslakov , 2012 This file is part of the Midnight Commander. @@ -96,8 +96,6 @@ gboolean option_drop_selection_on_copy = TRUE; #define TEMP_BUF_LEN 1024 -#define MAX_WORD_COMPLETIONS 100 /* in listbox */ - /*** file scope type declarations ****************************************************************/ typedef struct @@ -1181,48 +1179,167 @@ edit_find_word_start (const edit_buffer_t * buf, off_t * word_start, gsize * wor * @return newly allocated string or NULL if no any words under cursor */ -static char * +static GString * edit_collect_completions_get_current_word (edit_search_status_msg_t * esm, mc_search_t * srch, off_t word_start) { WEdit *edit = esm->edit; gsize len = 0; - off_t i; - GString *temp; + GString *temp = NULL; - if (!mc_search_run (srch, (void *) esm, word_start, edit->buffer.size, &len)) - return NULL; - - temp = g_string_sized_new (len); - - for (i = 0; i < (off_t) len; i++) + if (mc_search_run (srch, (void *) esm, word_start, edit->buffer.size, &len)) { - int chr; + off_t i; - chr = edit_buffer_get_byte (&edit->buffer, word_start + i); - if (!isspace (chr)) - g_string_append_c (temp, chr); + for (i = 0; i < (off_t) len; i++) + { + int chr; + + chr = edit_buffer_get_byte (&edit->buffer, word_start + i); + if (!isspace (chr)) + { + if (temp == NULL) + temp = g_string_sized_new (len); + + g_string_append_c (temp, chr); + } + } } - return g_string_free (temp, temp->len == 0); + return temp; } /* --------------------------------------------------------------------------------------------- */ -/** collect the possible completions */ -static gsize -edit_collect_completions (WEdit * edit, off_t word_start, gsize word_len, - char *match_expr, GString ** compl, gsize * num) +static void +edit_completion_string_free (gpointer data) { + g_string_free ((GString *) data, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * collect the possible completions from one buffer + */ + +static void +edit_collect_completion_from_one_buffer (gboolean active_buffer, GQueue ** compl, + mc_search_t * srch, edit_search_status_msg_t * esm, + off_t word_start, gsize word_len, off_t last_byte, + GString * current_word, int *max_width) +{ + GString *temp = NULL; gsize len = 0; - gsize max_len = 0; - gsize i; - int skip; - GString *temp; + off_t start = -1; + + while (mc_search_run (srch, (void *) esm, start + 1, last_byte, &len)) + { + gsize i; + int width; + + if (temp == NULL) + temp = g_string_sized_new (8); + else + g_string_set_size (temp, 0); + + start = srch->normal_offset; + + /* add matched completion if not yet added */ + for (i = 0; i < len; i++) + { + int ch; + + ch = edit_buffer_get_byte (&esm->edit->buffer, start + i); + if (isspace (ch)) + continue; + + /* skip current word */ + if (start + (off_t) i == word_start) + break; + + g_string_append_c (temp, ch); + } + + if (temp->len == 0) + continue; + + if (current_word != NULL && g_string_equal (current_word, temp)) + continue; + + if (*compl == NULL) + *compl = g_queue_new (); + else + { + GList *l; + + for (l = g_queue_peek_head_link (*compl); l != NULL; l = g_list_next (l)) + { + GString *s = (GString *) l->data; + + /* skip if already added */ + if (strncmp (s->str + word_len, temp->str + word_len, + MAX (len, s->len) - word_len) == 0) + break; + } + + if (l != NULL) + { + /* resort completion in main buffer only: + * these completions must be at the top of list in the completion dialog */ + if (!active_buffer && l != g_queue_peek_tail_link (*compl)) + { + /* move to the end */ + g_queue_unlink (*compl, l); + g_queue_push_tail_link (*compl, l); + } + + continue; + } + } + +#ifdef HAVE_CHARSET + { + GString *recoded; + + recoded = str_convert_to_display (temp->str); + if (recoded->len != 0) + mc_g_string_copy (temp, recoded); + + g_string_free (recoded, TRUE); + } +#endif + if (active_buffer) + g_queue_push_tail (*compl, temp); + else + g_queue_push_head (*compl, temp); + + start += len; + + /* note the maximal length needed for the completion dialog */ + width = str_term_width1 (temp->str); + *max_width = MAX (*max_width, width); + + temp = NULL; + } + + if (temp != NULL) + g_string_free (temp, TRUE); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * collect the possible completions from all buffers + */ + +static GQueue * +edit_collect_completions (WEdit * edit, off_t word_start, gsize word_len, + const char *match_expr, int *max_width) +{ + GQueue *compl = NULL; mc_search_t *srch; - off_t last_byte, start = -1; - char *current_word; - gboolean entire_file; + off_t last_byte; + GString *current_word; + gboolean entire_file, all_files; edit_search_status_msg_t esm; #ifdef HAVE_CHARSET @@ -1231,11 +1348,11 @@ edit_collect_completions (WEdit * edit, off_t word_start, gsize word_len, srch = mc_search_new (match_expr, NULL); #endif if (srch == NULL) - return 0; + return NULL; entire_file = mc_config_get_bool (mc_global.main_config, CONFIG_APP_SECTION, - "editor_wordcompletion_collect_entire_file", 0); + "editor_wordcompletion_collect_entire_file", FALSE); last_byte = entire_file ? edit->buffer.size : word_start; @@ -1253,87 +1370,58 @@ edit_collect_completions (WEdit * edit, off_t word_start, gsize word_len, current_word = edit_collect_completions_get_current_word (&esm, srch, word_start); - temp = g_string_new (""); + *max_width = 0; - /* collect max MAX_WORD_COMPLETIONS completions */ - while (mc_search_run (srch, (void *) &esm, start + 1, last_byte, &len)) + /* collect completions from current buffer at first */ + edit_collect_completion_from_one_buffer (TRUE, &compl, srch, &esm, word_start, word_len, + last_byte, current_word, max_width); + + /* collect completions from other buffers */ + all_files = + mc_config_get_bool (mc_global.main_config, CONFIG_APP_SECTION, + "editor_wordcompletion_collect_all_files", TRUE); + if (all_files) { - g_string_set_size (temp, 0); - start = srch->normal_offset; + const WGroup *owner = CONST_GROUP (CONST_WIDGET (edit)->owner); + gboolean saved_verbose; + GList *w; - /* add matched completion if not yet added */ - for (i = 0; i < len; i++) + /* don't show incorrect percentage in edit_search_status_update_cb() */ + saved_verbose = verbose; + verbose = FALSE; + + for (w = owner->widgets; w != NULL; w = g_list_next (w)) { - skip = edit_buffer_get_byte (&edit->buffer, start + i); - if (isspace (skip)) + Widget *ww = WIDGET (w->data); + WEdit *e; + + if (!edit_widget_is_editor (ww)) continue; - /* skip current word */ - if (start + (off_t) i == word_start) - break; + e = (WEdit *) ww; - g_string_append_c (temp, skip); + if (e == edit) + continue; + + /* search in entire file */ + word_start = 0; + last_byte = e->buffer.size; + esm.edit = e; + esm.offset = 0; + + edit_collect_completion_from_one_buffer (FALSE, &compl, srch, &esm, word_start, + word_len, last_byte, current_word, max_width); } - if (temp->len == 0) - continue; - - if (current_word != NULL && strcmp (current_word, temp->str) == 0) - continue; - - skip = 0; - - for (i = 0; i < *num; i++) - { - if (strncmp - ((char *) &compl[i]->str[word_len], - (char *) &temp->str[word_len], MAX (len, compl[i]->len) - word_len) == 0) - { - GString *this = compl[i]; - - for (++i; i < *num; i++) - compl[i - 1] = compl[i]; - compl[*num - 1] = this; - - skip = 1; - break; /* skip it, already added */ - } - } - if (skip != 0) - continue; - - if (*num == MAX_WORD_COMPLETIONS) - { - g_string_free (compl[0], TRUE); - for (i = 1; i < *num; i++) - compl[i - 1] = compl[i]; - (*num)--; - } -#ifdef HAVE_CHARSET - { - GString *recoded; - - recoded = str_convert_to_display (temp->str); - if (recoded->len != 0) - g_string_assign (temp, recoded->str); - - g_string_free (recoded, TRUE); - } -#endif - compl[(*num)++] = g_string_new_len (temp->str, temp->len); - start += len; - - /* note the maximal length needed for the completion dialog */ - if (len > max_len) - max_len = len; + verbose = saved_verbose; } status_msg_deinit (STATUS_MSG (&esm)); mc_search_free (srch); - g_string_free (temp, TRUE); - g_free (current_word); + if (current_word != NULL) + g_string_free (current_word, TRUE); - return max_len; + return compl; } /* --------------------------------------------------------------------------------------------- */ @@ -3334,10 +3422,12 @@ edit_mail_dialog (WEdit * edit) void edit_complete_word_cmd (WEdit * edit) { - gsize i, max_len, word_len = 0, num_compl = 0; off_t word_start = 0; + gsize word_len = 0; GString *match_expr; - GString *compl[MAX_WORD_COMPLETIONS]; /* completions */ + gsize i; + GQueue *compl; /* completions: list of GString* */ + int max_width; /* search start of word to be completed */ if (!edit_find_word_start (&edit->buffer, &word_start, &word_len)) @@ -3351,43 +3441,39 @@ edit_complete_word_cmd (WEdit * edit) g_string_append (match_expr, "[^\\s\\.=\\+\\[\\]\\(\\)\\,\\;\\:\\\"\\'\\-\\?\\/\\|\\\\\\{\\}\\*\\&\\^\\%%\\$#@\\!]+"); - /* collect the possible completions */ - /* start search from begin to end of file */ - max_len = - edit_collect_completions (edit, word_start, word_len, match_expr->str, (GString **) & compl, - &num_compl); + /* collect possible completions */ + compl = edit_collect_completions (edit, word_start, word_len, match_expr->str, &max_width); - if (num_compl > 0) + g_string_free (match_expr, TRUE); + + if (compl == NULL) + return; + + if (g_queue_get_length (compl) == 1) { /* insert completed word if there is only one match */ - if (num_compl == 1) - edit_complete_word_insert_recoded_completion (edit, compl[0]->str, word_len); + + GString *curr_compl; + + curr_compl = (GString *) g_queue_peek_head (compl); + edit_complete_word_insert_recoded_completion (edit, curr_compl->str, word_len); + } + else + { /* more than one possible completion => ask the user */ - else + + char *curr_compl; + + /* let the user select the preferred completion */ + curr_compl = editcmd_dialog_completion_show (edit, compl, max_width); + if (curr_compl != NULL) { - char *curr_compl; - - /* !!! usually only a beep is expected and when is !!! */ - /* !!! pressed again the selection dialog pops up, but that !!! */ - /* !!! seems to require a further internal state !!! */ - /*tty_beep (); */ - - /* let the user select the preferred completion */ - curr_compl = editcmd_dialog_completion_show (edit, max_len, - (GString **) & compl, num_compl); - - if (curr_compl != NULL) - { - edit_complete_word_insert_recoded_completion (edit, curr_compl, word_len); - g_free (curr_compl); - } + edit_complete_word_insert_recoded_completion (edit, curr_compl, word_len); + g_free (curr_compl); } } - g_string_free (match_expr, TRUE); - /* release memory before return */ - for (i = 0; i < num_compl; i++) - g_string_free (compl[i], TRUE); + g_queue_free_full (compl, edit_completion_string_free); } /* --------------------------------------------------------------------------------------------- */ diff --git a/src/editor/editcmd_dialogs.c b/src/editor/editcmd_dialogs.c index 8b3634f23..b0caf0036 100644 --- a/src/editor/editcmd_dialogs.c +++ b/src/editor/editcmd_dialogs.c @@ -345,19 +345,20 @@ editcmd_dialog_raw_key_query (const char *heading, const char *query, gboolean c /* let the user select its preferred completion */ char * -editcmd_dialog_completion_show (const WEdit * edit, int max_len, GString ** compl, int num_compl) +editcmd_dialog_completion_show (const WEdit * edit, GQueue * compl, int max_width) { const Widget *we = CONST_WIDGET (edit); - int start_x, start_y, offset, i; + int start_x, start_y, offset; char *curr = NULL; WDialog *compl_dlg; WListbox *compl_list; int compl_dlg_h; /* completion dialog height */ int compl_dlg_w; /* completion dialog width */ + GList *i; /* calculate the dialog metrics */ - compl_dlg_h = num_compl + 2; - compl_dlg_w = max_len + 4; + compl_dlg_h = g_queue_get_length (compl) + 2; + compl_dlg_w = max_width + 4; start_x = we->x + edit->curs_col + edit->start_col + EDIT_TEXT_HORIZONTAL_OFFSET + (edit->fullscreen ? 0 : 1) + option_line_state_width; start_y = we->y + edit->curs_row + EDIT_TEXT_VERTICAL_OFFSET + (edit->fullscreen ? 0 : 1) + 1; @@ -386,14 +387,13 @@ editcmd_dialog_completion_show (const WEdit * edit, int max_len, GString ** comp /* create the listbox */ compl_list = listbox_new (1, 1, compl_dlg_h - 2, compl_dlg_w - 2, FALSE, NULL); - /* add the dialog */ - group_add_widget (GROUP (compl_dlg), compl_list); - - /* fill the listbox with the completions */ - for (i = num_compl - 1; i >= 0; i--) /* reverse order */ - listbox_add_item (compl_list, LISTBOX_APPEND_AT_END, 0, (char *) compl[i]->str, NULL, + /* fill the listbox with the completions in the reverse order */ + for (i = g_queue_peek_tail_link (compl); i != NULL; i = g_list_previous (i)) + listbox_add_item (compl_list, LISTBOX_APPEND_AT_END, 0, ((GString *) i->data)->str, NULL, FALSE); + group_add_widget (GROUP (compl_dlg), compl_list); + /* pop up the dialog and apply the chosen completion */ if (dlg_run (compl_dlg) == B_ENTER) { diff --git a/src/editor/editcmd_dialogs.h b/src/editor/editcmd_dialogs.h index f691c857e..173e2ac6e 100644 --- a/src/editor/editcmd_dialogs.h +++ b/src/editor/editcmd_dialogs.h @@ -25,8 +25,7 @@ gboolean editcmd_dialog_search_show (WEdit * edit); int editcmd_dialog_raw_key_query (const char *heading, const char *query, gboolean cancel); -char *editcmd_dialog_completion_show (const WEdit * edit, int max_len, GString ** compl, - int num_compl); +char *editcmd_dialog_completion_show (const WEdit * edit, GQueue * compl, int max_width); void editcmd_dialog_select_definition_show (WEdit *, char *, int, int, struct etags_hash_struct *, int); diff --git a/tests/src/editor/editcmd__edit_complete_word_cmd.c b/tests/src/editor/editcmd__edit_complete_word_cmd.c index 37f933407..e7d6b44b0 100644 --- a/tests/src/editor/editcmd__edit_complete_word_cmd.c +++ b/tests/src/editor/editcmd__edit_complete_word_cmd.c @@ -6,6 +6,7 @@ Written by: Slava Zanko , 2013 + Andrew Borodin , 2021 This file is part of the Midnight Commander. @@ -41,7 +42,7 @@ #include "src/editor/editwidget.h" #include "src/editor/editcmd_dialogs.h" - +static WGroup owner; static WEdit *test_edit; /* --------------------------------------------------------------------------------------------- */ @@ -89,32 +90,33 @@ edit_load_macro_cmd (WEdit * _edit) /* @CapturedValue */ static const WEdit *editcmd_dialog_completion_show__edit; /* @CapturedValue */ -static int editcmd_dialog_completion_show__max_len; +static int editcmd_dialog_completion_show__max_width; /* @CapturedValue */ -static GString **editcmd_dialog_completion_show__compl; -/* @CapturedValue */ -static int editcmd_dialog_completion_show__num_compl; +static GQueue *editcmd_dialog_completion_show__compl; /* @ThenReturnValue */ static char *editcmd_dialog_completion_show__return_value; /* @Mock */ char * -editcmd_dialog_completion_show (const WEdit * edit, int max_len, GString ** compl, int num_compl) +editcmd_dialog_completion_show (const WEdit * edit, GQueue * compl, int max_width) { editcmd_dialog_completion_show__edit = edit; - editcmd_dialog_completion_show__max_len = max_len; - editcmd_dialog_completion_show__num_compl = num_compl; + editcmd_dialog_completion_show__max_width = max_width; { - int iterator; + GList *i; - editcmd_dialog_completion_show__compl = g_new0 (GString *, num_compl); + editcmd_dialog_completion_show__compl = g_queue_new (); - for (iterator = 0; iterator < editcmd_dialog_completion_show__num_compl; iterator++) - editcmd_dialog_completion_show__compl[iterator] = - g_string_new_len (compl[iterator]->str, compl[iterator]->len); + for (i = g_queue_peek_tail_link (compl); i != NULL; i = g_list_previous (i)) + { + GString *s = (GString *) i->data; + + g_queue_push_tail (editcmd_dialog_completion_show__compl, + g_string_new_len (s->str, s->len)); + } } return editcmd_dialog_completion_show__return_value; @@ -124,27 +126,25 @@ static void editcmd_dialog_completion_show__init (void) { editcmd_dialog_completion_show__edit = NULL; - editcmd_dialog_completion_show__max_len = 0; + editcmd_dialog_completion_show__max_width = 0; editcmd_dialog_completion_show__compl = NULL; - editcmd_dialog_completion_show__num_compl = 0; editcmd_dialog_completion_show__return_value = NULL; } +static void +editcmd_dialog_completion_show__string_free (gpointer data) +{ + g_string_free ((GString *) data, TRUE); +} + static void editcmd_dialog_completion_show__deinit (void) { if (editcmd_dialog_completion_show__compl != NULL) - { - int iterator; - - for (iterator = 0; iterator < editcmd_dialog_completion_show__num_compl; iterator++) - g_string_free (editcmd_dialog_completion_show__compl[iterator], TRUE); - - g_free (editcmd_dialog_completion_show__compl); - } + g_queue_free_full (editcmd_dialog_completion_show__compl, + editcmd_dialog_completion_show__string_free); } - /* --------------------------------------------------------------------------------------------- */ /* @Before */ @@ -162,9 +162,15 @@ my_setup (void) load_codepages_list (); #endif /* HAVE_CHARSET */ + mc_global.main_config = mc_config_init ("editcmd__edit_complete_word_cmd.ini", FALSE); + mc_config_set_bool (mc_global.main_config, CONFIG_APP_SECTION, + "editor_wordcompletion_collect_all_files", TRUE); + option_filesize_threshold = (char *) "64M"; test_edit = edit_init (NULL, 0, 0, 24, 80, vfs_path_from_str ("test-data.txt"), 1); + memset (&owner, 0, sizeof (owner)); + group_add_widget (&owner, WIDGET (test_edit)); editcmd_dialog_completion_show__init (); } @@ -176,8 +182,11 @@ my_teardown (void) { editcmd_dialog_completion_show__deinit (); edit_clean (test_edit); + group_remove_widget (test_edit); g_free (test_edit); + mc_config_deinit (mc_global.main_config); + #ifdef HAVE_CHARSET free_codepages_list (); #endif /* HAVE_CHARSET */ @@ -201,7 +210,7 @@ static const struct test_autocomplete_ds int input_display_codepage_id; const char *input_completed_word; - int expected_max_len; + int expected_max_width; int expected_compl_word_count; int input_completed_word_start_pos; const char *expected_completed_word; @@ -259,9 +268,9 @@ START_PARAMETRIZED_TEST (test_autocomplete, test_autocomplete_ds) /* then */ mctest_assert_ptr_eq (editcmd_dialog_completion_show__edit, test_edit); - mctest_assert_int_eq (editcmd_dialog_completion_show__num_compl, + mctest_assert_int_eq (g_queue_get_length (editcmd_dialog_completion_show__compl), data->expected_compl_word_count); - mctest_assert_int_eq (editcmd_dialog_completion_show__max_len, data->expected_max_len); + mctest_assert_int_eq (editcmd_dialog_completion_show__max_width, data->expected_max_width); { off_t i = 0;