Ticket #4169: 0001-Selectable-listing-of-various-object-types-from-TAGS__no_MultiSearch.patch

File 0001-Selectable-listing-of-various-object-types-from-TAGS__no_MultiSearch.patch, 33.8 KB (added by psprint, 3 years ago)
Line 
1From 41b925152221c864d37f425b7a73ea354cbe197e Mon Sep 17 00:00:00 2001
2From: Sebastian Gniazdowski <sgniazdowski@gmail.com>
3Date: Sun, 10 Jan 2021 10:41:55 -0600
4Subject: [PATCH] Selectable listing of various object types from TAGS file.
5
6---
7 lib/keybind.c                |   5 +
8 lib/keybind.h                |   5 +
9 lib/strutil.h                |   2 +
10 lib/strutil/strutil.c        |  32 ++++++
11 misc/mc.default.keymap       |   5 +
12 src/editor/edit-impl.h       |   3 +-
13 src/editor/edit.c            |  17 ++-
14 src/editor/editcmd.c         | 183 ++++++++++++++++++++++++--------
15 src/editor/editcmd_dialogs.c | 122 +++++++++-------------
16 src/editor/editcmd_dialogs.h |   6 +-
17 src/editor/etags.c           | 197 +++++++++++++++++++++++++++++++++++
18 src/editor/etags.h           |  36 ++++++-
19 src/keybind-defaults.c       |   5 +
20 13 files changed, 491 insertions(+), 127 deletions(-)
21
22diff --git a/lib/keybind.c b/lib/keybind.c
23index abd44d3e2..41a39579b 100644
24--- a/lib/keybind.c
25+++ b/lib/keybind.c
26@@ -152,6 +152,11 @@ static name_keymap_t command_names[] = {
27     ADD_KEYMAP_NAME (ViewRaw),
28     ADD_KEYMAP_NAME (ViewFile),
29     ADD_KEYMAP_NAME (ViewFiltered),
30+    ADD_KEYMAP_NAME (SelectFunction),
31+    ADD_KEYMAP_NAME (SelectVariable),
32+    ADD_KEYMAP_NAME (SelectType),
33+    ADD_KEYMAP_NAME (SelectOther),
34+    ADD_KEYMAP_NAME (SelectAllKinds),
35     ADD_KEYMAP_NAME (Find),
36     ADD_KEYMAP_NAME (DirSize),
37     ADD_KEYMAP_NAME (CompareDirs),
38diff --git a/lib/keybind.h b/lib/keybind.h
39index 9638bd651..92f496479 100644
40--- a/lib/keybind.h
41+++ b/lib/keybind.h
42@@ -139,6 +139,11 @@ enum
43     CK_ViewRaw,
44     CK_ViewFile,
45     CK_ViewFiltered,
46+    CK_SelectFunction,
47+    CK_SelectVariable,
48+    CK_SelectType,
49+    CK_SelectOther,
50+    CK_SelectAllKinds,
51     CK_Find,
52     CK_DirSize,
53     CK_HotListAdd,
54diff --git a/lib/strutil.h b/lib/strutil.h
55index a091c25aa..abfe4014d 100644
56--- a/lib/strutil.h
57+++ b/lib/strutil.h
58@@ -582,6 +582,8 @@ strtol_error_t xstrtoumax (const char *s, char **ptr, int base, uintmax_t * val,
59                            const char *valid_suffixes);
60 uintmax_t parse_integer (const char *str, gboolean * invalid);
61 
62+char *str_collapse_whitespace(char *s, char overwrite_char);
63+
64 /* --------------------------------------------------------------------------------------------- */
65 /*** inline functions ****************************************************************************/
66 /* --------------------------------------------------------------------------------------------- */
67diff --git a/lib/strutil/strutil.c b/lib/strutil/strutil.c
68index cf11d00d8..adc29c406 100644
69--- a/lib/strutil/strutil.c
70+++ b/lib/strutil/strutil.c
71@@ -29,6 +29,7 @@
72 #include <langinfo.h>
73 #include <string.h>
74 #include <errno.h>
75+#include <ctype.h>
76 
77 #include "lib/global.h"
78 #include "lib/util.h"           /* MC_PTR_FREE */
79@@ -1021,3 +1022,34 @@ parse_integer (const char *str, gboolean * invalid)
80 }
81 
82 /* --------------------------------------------------------------------------------------------- */
83+
84+char *str_collapse_whitespace(char *s, char overwrite_char)
85+{
86+    int i, wi=0, size, span=0;
87+    size=strlen(s);
88+
89+    /* Skip leading whitespace. */
90+    for (i=0; i<size; i++) {
91+        if (!isspace(s[i]))
92+            break;
93+    }
94+
95+    /* Collapse remaining whitespace. */
96+    for (; i<size; i++) {
97+        if (isspace(s[i])) // == ' ')
98+            span=1;
99+        else {
100+            if(span)
101+                s[wi++] = overwrite_space;
102+
103+            s[wi++] = s[i];
104+            span = 0;
105+        }
106+    }
107+
108+    s[wi]='\0';
109+
110+    return s;
111+}
112+
113+/* --------------------------------------------------------------------------------------------- */
114diff --git a/misc/mc.default.keymap b/misc/mc.default.keymap
115index 1bf68b2df..721e85022 100644
116--- a/misc/mc.default.keymap
117+++ b/misc/mc.default.keymap
118@@ -387,6 +387,11 @@ SpellCheckCurrentWord = ctrl-p
119 # WindowNext =
120 # WindowPrev =
121 # ExtendedKeyMap =
122+SelectFunction = alt-shift-f
123+SelectVariable = alt-shift-v
124+SelectType = alt-shift-t
125+SelectOther = alt-shift-o
126+SelectAllKinds = alt-shift-a
127 
128 [viewer]
129 Help = f1
130diff --git a/src/editor/edit-impl.h b/src/editor/edit-impl.h
131index 3ad04dbea..dd996face 100644
132--- a/src/editor/edit-impl.h
133+++ b/src/editor/edit-impl.h
134@@ -18,6 +18,7 @@
135 #include "lib/vfs/vfs.h"        /* vfs_path_t */
136 
137 #include "edit.h"
138+#include "etags.h"
139 
140 /*** typedefs(not structures) and defined constants **********************************************/
141 
142@@ -202,7 +203,7 @@ mc_search_cbret_t edit_search_cmd_callback (const void *user_data, gsize char_of
143 mc_search_cbret_t edit_search_update_callback (const void *user_data, gsize char_offset);
144 
145 void edit_complete_word_cmd (WEdit * edit);
146-void edit_get_match_keyword_cmd (WEdit * edit);
147+void edit_select_object_from_tags(WEdit * edit, etags_jump_type_t type);
148 
149 #ifdef HAVE_ASPELL
150 int edit_suggest_current_word (WEdit * edit);
151diff --git a/src/editor/edit.c b/src/editor/edit.c
152index e13be389a..0e7e75867 100644
153--- a/src/editor/edit.c
154+++ b/src/editor/edit.c
155@@ -3860,8 +3860,23 @@ edit_execute_cmd (WEdit * edit, long command, int char_for_insertion)
156         else
157             edit_complete_word_cmd (edit);
158         break;
159+    case CK_SelectFunction:
160+        edit_select_object_from_tags(edit, TAG_JUMP_KIND_FUNCTION_LIST);
161+        break;
162+    case CK_SelectVariable:
163+        edit_select_object_from_tags(edit, TAG_JUMP_KIND_VAR_LIST);
164+        break;
165+    case CK_SelectType:
166+        edit_select_object_from_tags(edit, TAG_JUMP_KIND_TYPE_LIST);
167+        break;
168+    case CK_SelectOther:
169+        edit_select_object_from_tags(edit, TAG_JUMP_KIND_OTHER_LIST);
170+        break;
171+    case CK_SelectAllKinds:
172+        edit_select_object_from_tags(edit, TAG_JUMP_KIND_ANY_LIST);
173+        break;
174     case CK_Find:
175-        edit_get_match_keyword_cmd (edit);
176+        edit_select_object_from_tags(edit, TAG_JUMP_KIND_MATCH_WORD);
177         break;
178 
179 #ifdef HAVE_ASPELL
180diff --git a/src/editor/editcmd.c b/src/editor/editcmd.c
181index e2b904604..4265cd8fc 100644
182--- a/src/editor/editcmd.c
183+++ b/src/editor/editcmd.c
184@@ -1140,7 +1140,7 @@ edit_find_word_start (const edit_buffer_t * buf, off_t * word_start, gsize * wor
185         return FALSE;
186 
187     c = edit_buffer_get_previous_byte (buf);
188-    /* return if not at end or in word */
189+    /* return if the word is empty */
190     if (is_break_char (c))
191         return FALSE;
192 
193@@ -1169,6 +1169,37 @@ edit_find_word_start (const edit_buffer_t * buf, off_t * word_start, gsize * wor
194     return TRUE;
195 }
196 
197+/* Gets the word on the left of the cursor.
198+ *
199+ * @param buf The edit buffer.
200+ * @param initial Initial contents of the result.
201+ * @param release_on_empty Should the initial g_string be released when returning NULL.
202+ * @return g_string with the word or NULL if the word is empty.
203+*/
204+static GString *
205+edit_get_left_whole_word(const edit_buffer_t * buf, GString *initial, gboolean release_on_empty)
206+{
207+    GString *ret = initial;
208+    gsize i, word_len = 0;
209+    off_t word_start = 0;
210+
211+    /* Search start of word left of cursor. */
212+    if (!edit_find_word_start (buf, &word_start, &word_len)) {
213+        if (initial && release_on_empty)
214+            g_string_free(initial, TRUE);
215+        return NULL;
216+    }
217+
218+    /* ret = g_strdup_printf ("\\b%.*s[a-zA-Z_0-9]+", word_len, bufpos); */
219+    if (!ret)
220+        ret = g_string_sized_new (SHORT_DEF_LEN);
221+
222+    for (i = 0; i < word_len; i++)
223+        g_string_append_c (ret, edit_buffer_get_byte (buf, word_start + i));
224+
225+    return ret;
226+}
227+
228 /* --------------------------------------------------------------------------------------------- */
229 /**
230  * Get current word under cursor
231@@ -3501,62 +3532,122 @@ edit_load_back_cmd (WEdit * edit)
232 /* --------------------------------------------------------------------------------------------- */
233 
234 void
235-edit_get_match_keyword_cmd (WEdit * edit)
236+edit_select_object_from_tags(WEdit * edit, etags_jump_type_t type)
237 {
238-    gsize word_len = 0, max_len = 0;
239-    int num_def = 0;
240-    gsize i;
241-    off_t word_start = 0;
242-    GString *match_expr;
243-    char *path = NULL;
244-    char *ptr = NULL;
245-    char *tagfile = NULL;
246+    int i, num_obj, max_len;
247 
248-    etags_hash_t def_hash[MAX_DEFINITIONS];
249+    /* Group 0-initialized pointers into a struct for easy initialization. */
250+    struct {
251+        const char *fname0; char *fname, *tagfile, *path, *ptr;
252+        GString *match_expr;
253+        etags_hash_t found_func[MAX_TAG_OBJECTS], *selected_object;
254+    } var = {0};
255 
256-    for (i = 0; i < MAX_DEFINITIONS; i++)
257-        def_hash[i].filename = NULL;
258-
259-    /* search start of word to be completed */
260-    if (!edit_find_word_start (&edit->buffer, &word_start, &word_len))
261+    /* No file means no entries in tags file → exit. */
262+    if (edit->filename_vpath == NULL)
263         return;
264 
265-    /* prepare match expression */
266-    match_expr = g_string_sized_new (word_len);
267-    for (i = 0; i < word_len; i++)
268-        g_string_append_c (match_expr, edit_buffer_get_byte (&edit->buffer, word_start + i));
269+    /* Set up current directory variable. */
270+    var.ptr = g_get_current_dir ();
271+    var.path = g_strconcat (var.ptr, PATH_SEP_STR, (char *) NULL);
272+    g_free (var.ptr);
273 
274-    ptr = g_get_current_dir ();
275-    path = g_strconcat (ptr, PATH_SEP_STR, (char *) NULL);
276-    g_free (ptr);
277+    /* Locate the tags file and its directory.  */
278+    etags_locate_tags_file(&var.tagfile, &var.path);
279 
280-    /* Recursive search file 'TAGS' in parent dirs */
281-    do
282-    {
283-        ptr = g_path_get_dirname (path);
284-        g_free (path);
285-        path = ptr;
286-        g_free (tagfile);
287-        tagfile = mc_build_filename (path, TAGS_NAME, (char *) NULL);
288-        if (exist_file (tagfile))
289-            break;
290+    if (!var.tagfile)
291+        goto exit_jump_tag_obj;
292+
293+    if (type >= TAG_JUMP_KIND_FUNCTION_LIST && type <= TAG_JUMP_KIND_ANY_LIST) {
294+        /* Establish the base relative filename of current buffer. */
295+        var.fname0 = vfs_path_as_str (edit->filename_vpath);
296+        if (g_str_has_prefix(var.fname0, var.path)) {
297+            var.fname0 = var.fname0 + strlen(var.path) + 1;
298+            var.fname = g_strdup(var.fname0);
299+        } else
300+            /* A fallback that shouldn't be needed and is unreliable. */
301+            var.fname = g_path_get_basename(var.fname0);
302+        var.match_expr = g_string_new(var.fname);
303+    } else if (type == TAG_JUMP_KIND_MATCH_WORD) {
304+        // The function releases the string on empty word result.
305+        var.match_expr = edit_get_left_whole_word(&edit->buffer, NULL, FALSE);
306+
307+        if(!var.match_expr)
308+            goto exit_jump_tag_obj;
309+
310+    } else
311+        goto exit_jump_tag_obj;
312+
313+    if (type >= TAG_JUMP_KIND_FUNCTION_LIST && type <= TAG_JUMP_KIND_ANY_LIST) {
314+        num_obj = etags_get_objects_for_file(type, var.tagfile, var.path, var.fname, (etags_hash_t *)
315+                        &var.found_func, &max_len, MAX_TAG_OBJECTS);
316+    } else {
317+        max_len = MAX_WIDTH_DEF_DIALOG;
318+        num_obj = etags_set_definition_hash (var.tagfile, var.path, var.match_expr->str, (etags_hash_t *)
319+&var.found_func);
320     }
321-    while (strcmp (path, PATH_SEP_STR) != 0);
322 
323-    if (tagfile != NULL)
324-    {
325-        num_def =
326-            etags_set_definition_hash (tagfile, path, match_expr->str, (etags_hash_t *) & def_hash);
327-        g_free (tagfile);
328+    /* Show the list. */
329+    if (num_obj > 0)
330+        var.selected_object = editcmd_dialog_select_tags_object_show (edit, var.fname, max_len,
331+                                               (etags_hash_t *) & var.found_func, type, num_obj);
332+
333+    if (var.selected_object) {
334+        int line = var.selected_object->line;
335+
336+        /* Move the display to the function line. */
337+        if (type >= TAG_JUMP_KIND_FUNCTION_LIST && type <= TAG_JUMP_KIND_ANY_LIST) {
338+            edit_move_display (edit, line - WIDGET (edit)->lines / 2 - 1);
339+            edit_move_to_line (edit, line - 1);
340+            edit->force |= REDRAW_COMPLETELY;
341+        } else {
342+            char *fullpath = var.selected_object->fullpath;
343+            gboolean do_moveto = FALSE;
344+            if (!edit->modified)
345+                do_moveto = TRUE;
346+            else if (!edit_query_dialog2
347+                     (_("Warning"),
348+                      _("Current text was modified without a file save.\n"
349+                        "Continue discards these changes."), _("C&ontinue"), _("&Cancel")))
350+            {
351+                edit->force |= REDRAW_COMPLETELY;
352+                do_moveto = TRUE;
353+            }
354+
355+            if (do_moveto) {
356+                /* Replace the file in current editor (no new file is opened). */
357+                vfs_path_free (edit_history_moveto[edit_stack_iterator].filename_vpath);
358+
359+                if (edit->dir_vpath != NULL)
360+                    edit_history_moveto[edit_stack_iterator].filename_vpath =
361+                        vfs_path_append_vpath_new (edit->dir_vpath, edit->filename_vpath, NULL);
362+                else
363+                    edit_history_moveto[edit_stack_iterator].filename_vpath =
364+                        vfs_path_clone (edit->filename_vpath);
365+
366+                edit_history_moveto[edit_stack_iterator].line = edit->start_line + edit->curs_row + 1;
367+                edit_stack_iterator++;
368+                vfs_path_free (edit_history_moveto[edit_stack_iterator].filename_vpath);
369+                edit_history_moveto[edit_stack_iterator].filename_vpath =
370+                    vfs_path_from_str ((char *) fullpath);
371+                edit_history_moveto[edit_stack_iterator].line = line;
372+                edit_reload_line (edit, edit_history_moveto[edit_stack_iterator].filename_vpath,
373+                                edit_history_moveto[edit_stack_iterator].line);
374+            }
375+        }
376     }
377-    g_free (path);
378+exit_jump_tag_obj:
379+    /* Clear results hash */
380+    for (i = 0; i < num_obj; i++)
381+        g_free (var.found_func[i].filename);
382 
383-    max_len = MAX_WIDTH_DEF_DIALOG;
384-    word_len = 0;
385-    if (num_def > 0)
386-        editcmd_dialog_select_definition_show (edit, match_expr->str, max_len, word_len,
387-                                               (etags_hash_t *) & def_hash, num_def);
388-    g_string_free (match_expr, TRUE);
389+    /* Release other variables. */
390+    g_free(var.fname);
391+    g_free(var.tagfile);
392+    g_free(var.path);
393+
394+    if (var.match_expr)
395+        g_string_free(var.match_expr, TRUE);
396 }
397 
398 /* --------------------------------------------------------------------------------------------- */
399diff --git a/src/editor/editcmd_dialogs.c b/src/editor/editcmd_dialogs.c
400index 8b3634f23..07f81b156 100644
401--- a/src/editor/editcmd_dialogs.c
402+++ b/src/editor/editcmd_dialogs.c
403@@ -408,105 +408,83 @@ editcmd_dialog_completion_show (const WEdit * edit, int max_len, GString ** comp
404 }
405 
406 /* --------------------------------------------------------------------------------------------- */
407-/* let the user select where function definition */
408+/* function and data structure selection dialog */
409 
410-void
411-editcmd_dialog_select_definition_show (WEdit * edit, char *match_expr, int max_len, int word_len,
412-                                       etags_hash_t * def_hash, int num_lines)
413+etags_hash_t *
414+editcmd_dialog_select_tags_object_show (WEdit * edit, char *match_expr, int max_len,
415+                                       etags_hash_t * all_found, etags_jump_type_t type,
416+                                       int num_lines)
417 {
418-    int start_x, start_y, offset, i;
419+    int start_x, start_y, offset, i, selected_on_start = 0;
420+    gboolean found_current = FALSE;
421     char *curr = NULL;
422-    WDialog *def_dlg;
423-    WListbox *def_list;
424-    int def_dlg_h;              /* dialog height */
425-    int def_dlg_w;              /* dialog width */
426+    WDialog *func_dlg;
427+    WListbox *func_list;
428+    int func_dlg_h;              /* dialog height */
429+    int func_dlg_w;              /* dialog width */
430+    etags_hash_t *selection_data = NULL;
431 
432-    (void) word_len;
433     /* calculate the dialog metrics */
434-    def_dlg_h = num_lines + 2;
435-    def_dlg_w = max_len + 4;
436-    start_x = edit->curs_col + edit->start_col - (def_dlg_w / 2) +
437+    func_dlg_h = num_lines + 2;
438+    func_dlg_w = max_len >= MAX_WIDTH_DEF_DIALOG/2 ? max_len + 4 : MAX_WIDTH_DEF_DIALOG/2 + 4;
439+    start_x = edit->curs_col + edit->start_col - (func_dlg_w / 2) +
440         EDIT_TEXT_HORIZONTAL_OFFSET + (edit->fullscreen ? 0 : 1) + option_line_state_width;
441     start_y = edit->curs_row + EDIT_TEXT_VERTICAL_OFFSET + (edit->fullscreen ? 0 : 1) + 1;
442 
443     if (start_x < 0)
444         start_x = 0;
445-    if (def_dlg_w > COLS)
446-        def_dlg_w = COLS;
447-    if (def_dlg_h > LINES - 2)
448-        def_dlg_h = LINES - 2;
449+    if (func_dlg_w > COLS)
450+        func_dlg_w = COLS;
451+    if (func_dlg_h > LINES - 2)
452+        func_dlg_h = LINES - 2;
453 
454-    offset = start_x + def_dlg_w - COLS;
455+    offset = start_x + func_dlg_w - COLS;
456     if (offset > 0)
457         start_x -= offset;
458-    offset = start_y + def_dlg_h - LINES;
459+    offset = start_y + func_dlg_h - LINES;
460     if (offset > 0)
461         start_y -= (offset + 1);
462 
463-    def_dlg = dlg_create (TRUE, start_y, start_x, def_dlg_h, def_dlg_w, WPOS_KEEP_DEFAULT, TRUE,
464+    func_dlg = dlg_create (TRUE, start_y, start_x, func_dlg_h, func_dlg_w, WPOS_KEEP_DEFAULT, TRUE,
465                           dialog_colors, NULL, NULL, "[Definitions]", match_expr);
466-    def_list = listbox_new (1, 1, def_dlg_h - 2, def_dlg_w - 2, FALSE, NULL);
467-    group_add_widget (GROUP (def_dlg), def_list);
468+    func_list = listbox_new (1, 1, func_dlg_h - 2, func_dlg_w - 2, FALSE, NULL);
469+    group_add_widget (GROUP (func_dlg), func_list);
470 
471     /* fill the listbox with the completions */
472     for (i = 0; i < num_lines; i++)
473     {
474-        char *label_def;
475+        char *label = NULL;
476 
477-        label_def =
478-            g_strdup_printf ("%s -> %s:%ld", def_hash[i].short_define, def_hash[i].filename,
479-                             def_hash[i].line);
480-        listbox_add_item (def_list, LISTBOX_APPEND_AT_END, 0, label_def, &def_hash[i], FALSE);
481-        g_free (label_def);
482+        if (type >= TAG_JUMP_KIND_FUNCTION_LIST && type <= TAG_JUMP_KIND_ANY_LIST)
483+            label = all_found[i].short_define;
484+        else if (type == TAG_JUMP_KIND_MATCH_WORD)
485+            label =
486+                g_strdup_printf ("%s -> %s:%ld", all_found[i].short_define, all_found[i].filename,
487+                             all_found[i].line);
488+        else
489+            label = g_strdup("error");
490+        listbox_add_item (func_list, LISTBOX_APPEND_AT_END, 0, label, &all_found[i], FALSE);
491+        g_free (label);
492+
493+        /* Detect currently active code segment. */
494+        if ((all_found[i].line - 1) <= edit->buffer.curs_line) {
495+            found_current = TRUE;
496+            selected_on_start = i;
497+        }
498     }
499+    if (found_current)
500+        listbox_select_entry(func_list, selected_on_start);
501 
502     /* pop up the dialog and apply the chosen completion */
503-    if (dlg_run (def_dlg) == B_ENTER)
504-    {
505-        etags_hash_t *curr_def = NULL;
506-        gboolean do_moveto = FALSE;
507-
508-        listbox_get_current (def_list, &curr, (void **) &curr_def);
509-
510-        if (!edit->modified)
511-            do_moveto = TRUE;
512-        else if (!edit_query_dialog2
513-                 (_("Warning"),
514-                  _("Current text was modified without a file save.\n"
515-                    "Continue discards these changes."), _("C&ontinue"), _("&Cancel")))
516-        {
517-            edit->force |= REDRAW_COMPLETELY;
518-            do_moveto = TRUE;
519-        }
520-
521-        if (curr != NULL && do_moveto && edit_stack_iterator + 1 < MAX_HISTORY_MOVETO)
522-        {
523-            vfs_path_free (edit_history_moveto[edit_stack_iterator].filename_vpath);
524-
525-            if (edit->dir_vpath != NULL)
526-                edit_history_moveto[edit_stack_iterator].filename_vpath =
527-                    vfs_path_append_vpath_new (edit->dir_vpath, edit->filename_vpath, NULL);
528-            else
529-                edit_history_moveto[edit_stack_iterator].filename_vpath =
530-                    vfs_path_clone (edit->filename_vpath);
531-
532-            edit_history_moveto[edit_stack_iterator].line = edit->start_line + edit->curs_row + 1;
533-            edit_stack_iterator++;
534-            vfs_path_free (edit_history_moveto[edit_stack_iterator].filename_vpath);
535-            edit_history_moveto[edit_stack_iterator].filename_vpath =
536-                vfs_path_from_str ((char *) curr_def->fullpath);
537-            edit_history_moveto[edit_stack_iterator].line = curr_def->line;
538-            edit_reload_line (edit, edit_history_moveto[edit_stack_iterator].filename_vpath,
539-                              edit_history_moveto[edit_stack_iterator].line);
540-        }
541-    }
542-
543-    /* clear definition hash */
544-    for (i = 0; i < MAX_DEFINITIONS; i++)
545-        g_free (def_hash[i].filename);
546+    if (dlg_run (func_dlg) == B_ENTER)
547+        listbox_get_current (func_list, &curr, (void **) &selection_data);
548 
549     /* destroy dialog before return */
550-    dlg_destroy (def_dlg);
551+    dlg_destroy (func_dlg);
552+
553+    return selection_data;
554 }
555 
556 /* --------------------------------------------------------------------------------------------- */
557diff --git a/src/editor/editcmd_dialogs.h b/src/editor/editcmd_dialogs.h
558index f691c857e..8d6a7933a 100644
559--- a/src/editor/editcmd_dialogs.h
560+++ b/src/editor/editcmd_dialogs.h
561@@ -5,7 +5,7 @@
562 
563 /*** typedefs(not structures) and defined constants **********************************************/
564 
565-struct etags_hash_struct;
566+typedef struct etags_hash_struct etags_hash_t;
567 
568 #define B_REPLACE_ALL (B_USER+1)
569 #define B_REPLACE_ONE (B_USER+2)
570@@ -28,8 +28,8 @@ int editcmd_dialog_raw_key_query (const char *heading, const char *query, gboole
571 char *editcmd_dialog_completion_show (const WEdit * edit, int max_len, GString ** compl,
572                                       int num_compl);
573 
574-void editcmd_dialog_select_definition_show (WEdit *, char *, int, int, struct etags_hash_struct *,
575-                                            int);
576+etags_hash_t *editcmd_dialog_select_tags_object_show (WEdit *, char *, int, etags_hash_t *,
577+                                                        etags_jump_type_t, int);
578 
579 int editcmd_dialog_replace_prompt_show (WEdit *, char *, char *, int, int);
580 /*** inline functions ****************************************************************************/
581diff --git a/src/editor/etags.c b/src/editor/etags.c
582index 35c7a2f04..952df9b99 100644
583--- a/src/editor/etags.c
584+++ b/src/editor/etags.c
585@@ -39,6 +39,8 @@
586 
587 #include "lib/global.h"
588 #include "lib/util.h"           /* canonicalize_pathname() */
589+#include "lib/fileloc.h"
590+#include "lib/strutil.h"
591 
592 #include "etags.h"
593 
594@@ -53,6 +55,32 @@
595 /*** file scope functions ************************************************************************/
596 /* --------------------------------------------------------------------------------------------- */
597 
598+int
599+etags_locate_tags_file(char **tagfile_return, char **path_return) {
600+    char *tagfile = *tagfile_return, *path = *path_return, *ptr = NULL;
601+    int search_result = 0;
602+
603+    /* Recursive search file 'TAGS' in parent dirs */
604+    do
605+    {
606+        ptr = g_path_get_dirname (path);
607+        g_free (path);
608+        path = ptr;
609+        g_free (tagfile);
610+        tagfile = mc_build_filename (path, TAGS_NAME, (char *) NULL);
611+        if (exist_file (tagfile)) {
612+            search_result = 1;
613+            break;
614+        }
615+    }
616+    while (strcmp (path, PATH_SEP_STR) != 0);
617+
618+    *tagfile_return = tagfile;
619+    *path_return = path;
620+
621+    return search_result;
622+}
623+
624 static gboolean
625 parse_define (const char *buf, char **long_name, char **short_name, long *line)
626 {
627@@ -169,6 +197,175 @@ parse_define (const char *buf, char **long_name, char **short_name, long *line)
628 /*** public functions ****************************************************************************/
629 /* --------------------------------------------------------------------------------------------- */
630 
631+/* Fills the etags info array with ·all· objects of given ·type· (functions, etc.) */
632+int etags_get_objects_for_file (etags_rank_t type, const char *tagfile,
633+                            const char *start_path, const char *match_filename,
634+                            etags_hash_t * functions_hash,
635+                            int *max_len_return, int size_limit)
636+{
637+    /* *INDENT-OFF* */
638+    enum
639+    {
640+        start,
641+        in_filename,
642+        in_define
643+    } state = start;
644+    /* *INDENT-ON* */
645+
646+    FILE *f;
647+    char buf[BUF_LARGE];
648+
649+    int num = 0;                /* returned value */
650+    char *filename = NULL;
651+
652+    if (!match_filename || !tagfile)
653+        return 0;
654+
655+    *max_len_return = 0;
656+
657+    /* open file with positions */
658+    f = fopen (tagfile, "r");
659+    if (f == NULL)
660+        return 0;
661+
662+    while (fgets (buf, sizeof (buf), f))
663+    {
664+        switch (state)
665+        {
666+        case start:
667+            if (buf[0] == 0x0C)
668+            {
669+                state = in_filename;
670+            }
671+            break;
672+        case in_filename:
673+            {
674+                size_t pos;
675+
676+                pos = strcspn (buf, ",");
677+                g_free (filename);
678+                filename = g_strndup (buf, pos);
679+                state = in_define;
680+                break;
681+            }
682+        case in_define:
683+            if (buf[0] == 0x0C)
684+            {
685+                state = in_filename;
686+                break;
687+            }
688+            /* check if the filename matches the requested one */
689+            if (strcmp (filename, match_filename) == 0)
690+            {
691+                char *longname = NULL;
692+                char *shortname = NULL;
693+                long line = 0;
694+
695+                parse_define (buf, &longname, &shortname, &line);
696+                if (num < size_limit - 1)
697+                {
698+                    gboolean can_be_func, can_be_var, can_be_type, is_other;
699+
700+                    /* Prepare the work variable. */
701+                    char *longname_wr;
702+                    longname_wr = g_strdup(longname);
703+
704+                    /* Function – if there's '(' in the declaration. */
705+                    can_be_func = strstr(longname,"(") != NULL;
706+                    /* Variable – if there's no parens and no # in the declaration. */
707+                    can_be_var = strstr(g_strdelimit(longname_wr,"}{()#",''),"") == NULL;
708+                    /* Type – if there's a 'struct', 'typedef', 'enum' or '}' in the declaration. */
709+                    can_be_type=(strstr(longname,"struct ") ||
710+                                            strstr(longname,"typedef ") ||
711+                                            strstr(longname,"enum ")) ||
712+                                (strstr(longname, "}") &&
713+                                    (g_str_has_suffix(shortname,"_t") ||
714+                                        g_str_has_suffix(shortname,"_type")));
715+                    /* Other kind – nor any of the above. */
716+                    is_other = !can_be_func && !can_be_var && !can_be_type;
717+
718+                    /* Renew the work variable. */
719+                    g_free(longname_wr);
720+                    longname_wr = g_strdup(longname);
721+
722+                    /* A closer examination of type tags. */
723+                    if (type == TAG_RANK_TYPES && can_be_type && !can_be_func) {
724+                        /*
725+                         * Verify if it's not a struct variable or an enum.
726+                         * It filters out occurrences such as:
727+                         * – struct type SHORTNAME … – i.e.: the shortname at 3rd position, because
728+                         *   this means that a struct variable, not a struct type is being defined.
729+                         */
730+                        gchar **words = g_strsplit(str_collapse_whitespace(longname_wr, ' ')," ", -1);
731+                        if (words[2] && strcmp(words[2], shortname) == 0)
732+                            can_be_type = FALSE;
733+                        g_strfreev(words);
734+                    }
735+
736+                    /* A closer examination of variable tags. */
737+                    if (type == TAG_RANK_VARIABLES && can_be_var) {
738+                        /* Verify if it's not a struct typedef or an enum. */
739+                        gchar **words = g_strsplit(str_collapse_whitespace(longname_wr, ' ')," ", -1);
740+                        if (strcmp(words[0], "typedef") == 0 || !words[0] || !words[1])
741+                            can_be_var = FALSE;
742+                        /* Most probably an enum ENUM = 0|1|… assignment. */
743+                        if (!words[0] || strstr(words[0], "=") || (words[1] && words[1][0] == '='))
744+                            can_be_var = FALSE;
745+                        g_strfreev(words);
746+                    }
747+
748+                    /* Free the work variable. */
749+                    g_free(longname_wr);
750+
751+                    /* Is the object of the requested type? */
752+                    if (type == TAG_RANK_ANY ||
753+                        ((type == TAG_RANK_FUNCTIONS && can_be_func) ||
754+                            (type == TAG_RANK_VARIABLES && can_be_var) ||
755+                            (type == TAG_RANK_TYPES && can_be_type) ||
756+                            (type == TAG_RANK_OTHER && is_other)))
757+                    {
758+                        /* Update the max. length return variable */
759+                        int max_len_candidate;
760+                        max_len_candidate = strlen(shortname);
761+                        if (*max_len_return < max_len_candidate)
762+                            *max_len_return = max_len_candidate;
763+
764+                        /* Save the filename. */
765+                        functions_hash[num].filename = g_strdup (filename);
766+                        functions_hash[num].filename_len = strlen (filename);
767+
768+                        /* Save and canonicalize the path to the file. */
769+                        functions_hash[num].fullpath =
770+                            mc_build_filename (start_path, filename, (char *) NULL);
771+                        canonicalize_pathname (functions_hash[num].fullpath);
772+
773+                        /* Save the short define. */
774+                        if (shortname)
775+                            functions_hash[num].short_define = g_strdup (shortname);
776+                        else
777+                            functions_hash[num].short_define = g_strdup (longname);
778+
779+                        /* Save the line number. */
780+                        functions_hash[num].line = line;
781+
782+                        /* Increase the count of the matched objects. */
783+                        num++;
784+                    }
785+                }
786+            }
787+            break;
788+        default:
789+            break;
790+        }
791+    }
792+
793+    g_free (filename);
794+    fclose (f);
795+    return num;
796+}
797+
798+/* --------------------------------------------------------------------------------------------- */
799+
800 int
801 etags_set_definition_hash (const char *tagfile, const char *start_path,
802                            const char *match_func, etags_hash_t * def_hash)
803diff --git a/src/editor/etags.h b/src/editor/etags.h
804index be71b3a27..a0eb77710 100644
805--- a/src/editor/etags.h
806+++ b/src/editor/etags.h
807@@ -6,10 +6,10 @@
808 
809 /*** typedefs(not structures) and defined constants **********************************************/
810 
811-#define MAX_WIDTH_DEF_DIALOG 60 /* max width def dialog */
812-#define MAX_DEFINITIONS 60      /* count found entries show */
813-#define SHORT_DEF_LEN   30
814-#define LONG_DEF_LEN    40
815+#define MAX_WIDTH_DEF_DIALOG 60 /* max width of the dialog */
816+#define MAX_TAG_OBJECTS   350
817+#define SHORT_DEF_LEN   70
818+#define LONG_DEF_LEN    70
819 #define LINE_DEF_LEN    16
820 
821 /*** enums ***************************************************************************************/
822@@ -25,6 +25,27 @@ typedef struct etags_hash_struct
823     long line;
824 } etags_hash_t;
825 
826+
827+typedef enum
828+{
829+    TAG_JUMP_KIND_FUNCTION_LIST,   /* List of functions in current file. */
830+    TAG_JUMP_KIND_TYPE_LIST,       /* List of type definitions in current file. */
831+    TAG_JUMP_KIND_VAR_LIST,        /* List of variables in current file. */
832+    TAG_JUMP_KIND_OTHER_LIST,      /* List of other tag object types for the current file. */
833+    TAG_JUMP_KIND_ANY_LIST,        /* List of all tags for current file. */
834+    TAG_JUMP_KIND_MATCH_WORD,      /* A list of tag objects matching left word. */
835+    TAG_JUMP_KIND_QUICK_WHOLE_WORD /* Future – instantly jump to the id under cursor, same file */
836+} etags_jump_type_t;
837+
838+typedef enum
839+{
840+    TAG_RANK_FUNCTIONS,     /* Function definitions */
841+    TAG_RANK_TYPES,         /* Types (structs, typedefs, etc.)  */
842+    TAG_RANK_VARIABLES,     /* Variables */
843+    TAG_RANK_OTHER,         /* Other kind (not of the above) */
844+    TAG_RANK_ANY            /* All kinds */
845+} etags_rank_t;
846+
847 /*** global variables defined in .c file *********************************************************/
848 
849 /*** declarations of public functions ************************************************************/
850@@ -33,5 +54,12 @@ typedef struct etags_hash_struct
851 int etags_set_definition_hash (const char *tagfile, const char *start_path,
852                                const char *match_func, etags_hash_t * def_hash);
853 
854+int etags_get_objects_for_file (etags_rank_t type, const char *tagfile,
855+                            const char *start_path, const char *match_filename,
856+                            etags_hash_t * functions_hash,
857+                            int *max_len_return, int size_limit);
858+
859+int etags_locate_tags_file(char **tagfile_return, char **path_return);
860+
861 /*** inline functions ****************************************************************************/
862 #endif /* MC__EDIT_ETAGS_H */
863diff --git a/src/keybind-defaults.c b/src/keybind-defaults.c
864index c423e6be4..e16df0ea5 100644
865--- a/src/keybind-defaults.c
866+++ b/src/keybind-defaults.c
867@@ -465,6 +465,11 @@ static const global_keymap_ini_t default_editor_keymap[] = {
868     {"ShowNumbers", "alt-n"},
869     {"ShowTabTws", "alt-underline"},
870     {"SyntaxOnOff", "ctrl-s"},
871+    {"SelectFunction","alt-shift-f"},
872+    {"SelectVariable","alt-shift-v"},
873+    {"SelectType","alt-shift-t"},
874+    {"SelectOther","alt-shift-o"},
875+    {"SelectAllKinds","alt-shift-a"},
876     {"Find", "alt-enter"},
877     {"FilePrev", "alt-minus"},
878     {"FileNext", "alt-plus"},
879--
8802.28.0
881