summaryrefslogtreecommitdiffstats
path: root/src/editor/etags.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/editor/etags.c')
-rw-r--r--src/editor/etags.c468
1 files changed, 468 insertions, 0 deletions
diff --git a/src/editor/etags.c b/src/editor/etags.c
new file mode 100644
index 0000000..7b570d6
--- /dev/null
+++ b/src/editor/etags.c
@@ -0,0 +1,468 @@
+/*
+ Editor C-code navigation via tags.
+ make TAGS file via command:
+ $ find . -type f -name "*.[ch]" | etags -l c --declarations -
+
+ or, if etags utility not installed:
+ $ find . -type f -name "*.[ch]" | ctags --c-kinds=+p --fields=+iaS --extra=+q -e -L-
+
+ Copyright (C) 2009-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Ilia Maslakov <il.smind@gmail.com>, 2009
+ Slava Zanko <slavazanko@gmail.com>, 2009
+ Andrew Borodin <aborodin@vmail.ru>, 2010-2022
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander is free software: you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/fileloc.h" /* TAGS_NAME */
+#include "lib/tty/tty.h" /* LINES, COLS */
+#include "lib/strutil.h"
+#include "lib/util.h"
+
+#include "editwidget.h"
+
+#include "etags.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static int def_max_width;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+etags_hash_free (gpointer data)
+{
+ etags_hash_t *hash = (etags_hash_t *) data;
+
+ g_free (hash->filename);
+ g_free (hash->fullpath);
+ g_free (hash->short_define);
+ g_free (hash);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+parse_define (const char *buf, char **long_name, char **short_name, long *line)
+{
+ /* *INDENT-OFF* */
+ enum
+ {
+ in_longname,
+ in_shortname,
+ in_shortname_first_char,
+ in_line,
+ finish
+ } def_state = in_longname;
+ /* *INDENT-ON* */
+
+ GString *longdef = NULL;
+ GString *shortdef = NULL;
+ GString *linedef = NULL;
+
+ char c = *buf;
+
+ while (!(c == '\0' || c == '\n'))
+ {
+ switch (def_state)
+ {
+ case in_longname:
+ if (c == 0x01)
+ def_state = in_line;
+ else if (c == 0x7F)
+ def_state = in_shortname;
+ else
+ {
+ if (longdef == NULL)
+ longdef = g_string_sized_new (32);
+
+ g_string_append_c (longdef, c);
+ }
+ break;
+
+ case in_shortname_first_char:
+ if (isdigit (c))
+ {
+ if (shortdef == NULL)
+ shortdef = g_string_sized_new (32);
+ else
+ g_string_set_size (shortdef, 0);
+
+ buf--;
+ def_state = in_line;
+ }
+ else if (c == 0x01)
+ def_state = in_line;
+ else
+ {
+ if (shortdef == NULL)
+ shortdef = g_string_sized_new (32);
+
+ g_string_append_c (shortdef, c);
+ def_state = in_shortname;
+ }
+ break;
+
+ case in_shortname:
+ if (c == 0x01)
+ def_state = in_line;
+ else if (c == '\n')
+ def_state = finish;
+ else
+ {
+ if (shortdef == NULL)
+ shortdef = g_string_sized_new (32);
+
+ g_string_append_c (shortdef, c);
+ }
+ break;
+
+ case in_line:
+ if (c == ',' || c == '\n')
+ def_state = finish;
+ else if (isdigit (c))
+ {
+ if (linedef == NULL)
+ linedef = g_string_sized_new (32);
+
+ g_string_append_c (linedef, c);
+ }
+ break;
+
+ case finish:
+ *long_name = longdef == NULL ? NULL : g_string_free (longdef, FALSE);
+ *short_name = shortdef == NULL ? NULL : g_string_free (shortdef, FALSE);
+
+ if (linedef == NULL)
+ *line = 0;
+ else
+ {
+ *line = atol (linedef->str);
+ g_string_free (linedef, TRUE);
+ }
+ return TRUE;
+
+ default:
+ break;
+ }
+
+ buf++;
+ c = *buf;
+ }
+
+ *long_name = NULL;
+ *short_name = NULL;
+ *line = 0;
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GPtrArray *
+etags_set_definition_hash (const char *tagfile, const char *start_path, const char *match_func)
+{
+ /* *INDENT-OFF* */
+ enum
+ {
+ start,
+ in_filename,
+ in_define
+ } state = start;
+ /* *INDENT-ON* */
+
+ FILE *f;
+ char buf[BUF_LARGE];
+ char *filename = NULL;
+ GPtrArray *ret = NULL;
+
+ if (match_func == NULL || tagfile == NULL)
+ return NULL;
+
+ /* open file with positions */
+ f = fopen (tagfile, "r");
+ if (f == NULL)
+ return NULL;
+
+ while (fgets (buf, sizeof (buf), f) != NULL)
+ switch (state)
+ {
+ case start:
+ if (buf[0] == 0x0C)
+ state = in_filename;
+ break;
+
+ case in_filename:
+ {
+ size_t pos;
+
+ pos = strcspn (buf, ",");
+ g_free (filename);
+ filename = g_strndup (buf, pos);
+ state = in_define;
+ break;
+ }
+
+ case in_define:
+ if (buf[0] == 0x0C)
+ state = in_filename;
+ else
+ {
+ char *chekedstr;
+
+ /* check if the filename matches the define pos */
+ chekedstr = strstr (buf, match_func);
+ if (chekedstr != NULL)
+ {
+ char *longname = NULL;
+ char *shortname = NULL;
+ etags_hash_t *def_hash;
+
+ def_hash = g_new (etags_hash_t, 1);
+
+ def_hash->fullpath = mc_build_filename (start_path, filename, (char *) NULL);
+ def_hash->filename = g_strdup (filename);
+
+ def_hash->line = 0;
+
+ parse_define (chekedstr, &longname, &shortname, &def_hash->line);
+
+ if (shortname != NULL && *shortname != '\0')
+ {
+ def_hash->short_define = shortname;
+ g_free (longname);
+ }
+ else
+ {
+ def_hash->short_define = longname;
+ g_free (shortname);
+ }
+
+ if (ret == NULL)
+ ret = g_ptr_array_new_with_free_func (etags_hash_free);
+
+ g_ptr_array_add (ret, def_hash);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ g_free (filename);
+ fclose (f);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+editcmd_dialog_select_definition_add (gpointer data, gpointer user_data)
+{
+ etags_hash_t *def_hash = (etags_hash_t *) data;
+ WListbox *def_list = (WListbox *) user_data;
+ char *label_def;
+ int def_width;
+
+ label_def =
+ g_strdup_printf ("%s -> %s:%ld", def_hash->short_define, def_hash->filename,
+ def_hash->line);
+ listbox_add_item (def_list, LISTBOX_APPEND_AT_END, 0, label_def, def_hash, FALSE);
+ def_width = str_term_width1 (label_def);
+ g_free (label_def);
+ def_max_width = MAX (def_max_width, def_width);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* let the user select where function definition */
+
+static void
+editcmd_dialog_select_definition_show (WEdit * edit, char *match_expr, GPtrArray * def_hash)
+{
+ const WRect *w = &CONST_WIDGET (edit)->rect;
+ int start_x, start_y, offset;
+ char *curr = NULL;
+ WDialog *def_dlg;
+ WListbox *def_list;
+ int def_dlg_h; /* dialog height */
+ int def_dlg_w; /* dialog width */
+
+ /* calculate the dialog metrics */
+ def_dlg_h = def_hash->len + 2;
+ def_dlg_w = COLS - 2; /* will be clarified later */
+ start_x = w->x + edit->curs_col + edit->start_col + EDIT_TEXT_HORIZONTAL_OFFSET +
+ (edit->fullscreen ? 0 : 1) + edit_options.line_state_width;
+ start_y = w->y + edit->curs_row + EDIT_TEXT_VERTICAL_OFFSET + (edit->fullscreen ? 0 : 1) + 1;
+
+ if (start_x < 0)
+ start_x = 0;
+ if (start_x < w->x + 1)
+ start_x = w->x + 1 + edit_options.line_state_width;
+
+ if (def_dlg_h > LINES - 2)
+ def_dlg_h = LINES - 2;
+
+ offset = start_y + def_dlg_h - LINES;
+ if (offset > 0)
+ start_y -= (offset + 1);
+
+ def_dlg = dlg_create (TRUE, start_y, start_x, def_dlg_h, def_dlg_w, WPOS_KEEP_DEFAULT, TRUE,
+ dialog_colors, NULL, NULL, "[Definitions]", match_expr);
+ def_list = listbox_new (1, 1, def_dlg_h - 2, def_dlg_w - 2, FALSE, NULL);
+ group_add_widget_autopos (GROUP (def_dlg), def_list, WPOS_KEEP_ALL, NULL);
+
+ /* fill the listbox with the completions and get the maximum width */
+ def_max_width = 0;
+ g_ptr_array_foreach (def_hash, editcmd_dialog_select_definition_add, def_list);
+
+ /* adjust dialog width */
+ def_dlg_w = def_max_width + 4;
+ offset = start_x + def_dlg_w - COLS;
+ if (offset > 0)
+ start_x -= offset;
+
+ widget_set_size (WIDGET (def_dlg), start_y, start_x, def_dlg_h, def_dlg_w);
+
+ /* pop up the dialog and apply the chosen completion */
+ if (dlg_run (def_dlg) == B_ENTER)
+ {
+ etags_hash_t *curr_def = NULL;
+ gboolean do_moveto = FALSE;
+
+ listbox_get_current (def_list, &curr, (void **) &curr_def);
+
+ if (!edit->modified)
+ do_moveto = TRUE;
+ else if (!edit_query_dialog2
+ (_("Warning"),
+ _("Current text was modified without a file save.\n"
+ "Continue discards these changes."), _("C&ontinue"), _("&Cancel")))
+ {
+ edit->force |= REDRAW_COMPLETELY;
+ do_moveto = TRUE;
+ }
+
+ if (curr != NULL && do_moveto && edit_stack_iterator + 1 < MAX_HISTORY_MOVETO)
+ {
+ vfs_path_free (edit_history_moveto[edit_stack_iterator].filename_vpath, TRUE);
+
+ /* Is file path absolute? Prepend with dir_vpath if necessary */
+ if (edit->filename_vpath != NULL && edit->filename_vpath->relative
+ && edit->dir_vpath != NULL)
+ edit_history_moveto[edit_stack_iterator].filename_vpath =
+ vfs_path_append_vpath_new (edit->dir_vpath, edit->filename_vpath, NULL);
+ else
+ edit_history_moveto[edit_stack_iterator].filename_vpath =
+ vfs_path_clone (edit->filename_vpath);
+
+ edit_history_moveto[edit_stack_iterator].line = edit->start_line + edit->curs_row + 1;
+ edit_stack_iterator++;
+ vfs_path_free (edit_history_moveto[edit_stack_iterator].filename_vpath, TRUE);
+ edit_history_moveto[edit_stack_iterator].filename_vpath =
+ vfs_path_from_str ((char *) curr_def->fullpath);
+ edit_history_moveto[edit_stack_iterator].line = curr_def->line;
+ edit_reload_line (edit, edit_history_moveto[edit_stack_iterator].filename_vpath,
+ edit_history_moveto[edit_stack_iterator].line);
+ }
+ }
+
+ /* destroy dialog before return */
+ widget_destroy (WIDGET (def_dlg));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_get_match_keyword_cmd (WEdit * edit)
+{
+ gsize word_len = 0;
+ gsize i;
+ off_t word_start = 0;
+ GString *match_expr;
+ char *path = NULL;
+ char *ptr = NULL;
+ char *tagfile = NULL;
+ GPtrArray *def_hash = NULL;
+
+ /* search start of word to be completed */
+ if (!edit_buffer_find_word_start (&edit->buffer, &word_start, &word_len))
+ return;
+
+ /* prepare match expression */
+ match_expr = g_string_sized_new (word_len);
+ for (i = 0; i < word_len; i++)
+ g_string_append_c (match_expr, edit_buffer_get_byte (&edit->buffer, word_start + i));
+
+ ptr = g_get_current_dir ();
+ path = g_strconcat (ptr, PATH_SEP_STR, (char *) NULL);
+ g_free (ptr);
+
+ /* Recursive search file 'TAGS' in parent dirs */
+ do
+ {
+ ptr = g_path_get_dirname (path);
+ g_free (path);
+ path = ptr;
+ g_free (tagfile);
+ tagfile = mc_build_filename (path, TAGS_NAME, (char *) NULL);
+ if (tagfile != NULL && exist_file (tagfile))
+ break;
+ }
+ while (strcmp (path, PATH_SEP_STR) != 0);
+
+ if (tagfile != NULL)
+ {
+ def_hash = etags_set_definition_hash (tagfile, path, match_expr->str);
+ g_free (tagfile);
+ }
+ g_free (path);
+
+ if (def_hash != NULL)
+ {
+ editcmd_dialog_select_definition_show (edit, match_expr->str, def_hash);
+
+ g_ptr_array_free (def_hash, TRUE);
+ }
+
+ g_string_free (match_expr, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */