root/src/editor/etags.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. etags_hash_free
  2. parse_define
  3. etags_set_definition_hash
  4. editcmd_dialog_select_definition_add
  5. editcmd_dialog_select_definition_show
  6. edit_get_match_keyword_cmd

   1 /*
   2    Editor C-code navigation via tags.
   3    make TAGS file via command:
   4    $ find . -type f -name "*.[ch]" | etags -l c --declarations -
   5 
   6    or, if etags utility not installed:
   7    $ find . -type f -name "*.[ch]" | ctags --c-kinds=+p --fields=+iaS --extra=+q -e -L-
   8 
   9    Copyright (C) 2009-2024
  10    Free Software Foundation, Inc.
  11 
  12    Written by:
  13    Ilia Maslakov <il.smind@gmail.com>, 2009
  14    Slava Zanko <slavazanko@gmail.com>, 2009
  15    Andrew Borodin <aborodin@vmail.ru>, 2010-2022
  16 
  17    This file is part of the Midnight Commander.
  18 
  19    The Midnight Commander is free software: you can redistribute it
  20    and/or modify it under the terms of the GNU General Public License as
  21    published by the Free Software Foundation, either version 3 of the License,
  22    or (at your option) any later version.
  23 
  24    The Midnight Commander is distributed in the hope that it will be useful,
  25    but WITHOUT ANY WARRANTY; without even the implied warranty of
  26    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  27    GNU General Public License for more details.
  28 
  29    You should have received a copy of the GNU General Public License
  30    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  31  */
  32 
  33 #include <config.h>
  34 
  35 #include <ctype.h>
  36 #include <stdio.h>
  37 #include <stdlib.h>
  38 #include <string.h>
  39 
  40 #include "lib/global.h"
  41 #include "lib/fileloc.h"        /* TAGS_NAME */
  42 #include "lib/tty/tty.h"        /* LINES, COLS */
  43 #include "lib/strutil.h"
  44 #include "lib/util.h"
  45 
  46 #include "editwidget.h"
  47 
  48 #include "etags.h"
  49 
  50 /*** global variables ****************************************************************************/
  51 
  52 /*** file scope macro definitions ****************************************************************/
  53 
  54 /*** file scope type declarations ****************************************************************/
  55 
  56 /*** forward declarations (file scope functions) *************************************************/
  57 
  58 /*** file scope variables ************************************************************************/
  59 
  60 static int def_max_width;
  61 
  62 /* --------------------------------------------------------------------------------------------- */
  63 /*** file scope functions ************************************************************************/
  64 /* --------------------------------------------------------------------------------------------- */
  65 
  66 static void
  67 etags_hash_free (gpointer data)
     /* [previous][next][first][last][top][bottom][index][help]  */
  68 {
  69     etags_hash_t *hash = (etags_hash_t *) data;
  70 
  71     g_free (hash->filename);
  72     g_free (hash->fullpath);
  73     g_free (hash->short_define);
  74     g_free (hash);
  75 }
  76 
  77 /* --------------------------------------------------------------------------------------------- */
  78 
  79 static gboolean
  80 parse_define (const char *buf, char **long_name, char **short_name, long *line)
     /* [previous][next][first][last][top][bottom][index][help]  */
  81 {
  82     /* *INDENT-OFF* */
  83     enum
  84     {
  85         in_longname,
  86         in_shortname,
  87         in_shortname_first_char,
  88         in_line,
  89         finish
  90     } def_state = in_longname;
  91     /* *INDENT-ON* */
  92 
  93     GString *longdef = NULL;
  94     GString *shortdef = NULL;
  95     GString *linedef = NULL;
  96 
  97     char c = *buf;
  98 
  99     while (!(c == '\0' || c == '\n'))
 100     {
 101         switch (def_state)
 102         {
 103         case in_longname:
 104             if (c == 0x01)
 105                 def_state = in_line;
 106             else if (c == 0x7F)
 107                 def_state = in_shortname;
 108             else
 109             {
 110                 if (longdef == NULL)
 111                     longdef = g_string_sized_new (32);
 112 
 113                 g_string_append_c (longdef, c);
 114             }
 115             break;
 116 
 117         case in_shortname_first_char:
 118             if (isdigit (c))
 119             {
 120                 if (shortdef == NULL)
 121                     shortdef = g_string_sized_new (32);
 122                 else
 123                     g_string_set_size (shortdef, 0);
 124 
 125                 buf--;
 126                 def_state = in_line;
 127             }
 128             else if (c == 0x01)
 129                 def_state = in_line;
 130             else
 131             {
 132                 if (shortdef == NULL)
 133                     shortdef = g_string_sized_new (32);
 134 
 135                 g_string_append_c (shortdef, c);
 136                 def_state = in_shortname;
 137             }
 138             break;
 139 
 140         case in_shortname:
 141             if (c == 0x01)
 142                 def_state = in_line;
 143             else if (c == '\n')
 144                 def_state = finish;
 145             else
 146             {
 147                 if (shortdef == NULL)
 148                     shortdef = g_string_sized_new (32);
 149 
 150                 g_string_append_c (shortdef, c);
 151             }
 152             break;
 153 
 154         case in_line:
 155             if (c == ',' || c == '\n')
 156                 def_state = finish;
 157             else if (isdigit (c))
 158             {
 159                 if (linedef == NULL)
 160                     linedef = g_string_sized_new (32);
 161 
 162                 g_string_append_c (linedef, c);
 163             }
 164             break;
 165 
 166         case finish:
 167             *long_name = longdef == NULL ? NULL : g_string_free (longdef, FALSE);
 168             *short_name = shortdef == NULL ? NULL : g_string_free (shortdef, FALSE);
 169 
 170             if (linedef == NULL)
 171                 *line = 0;
 172             else
 173             {
 174                 *line = atol (linedef->str);
 175                 g_string_free (linedef, TRUE);
 176             }
 177             return TRUE;
 178 
 179         default:
 180             break;
 181         }
 182 
 183         buf++;
 184         c = *buf;
 185     }
 186 
 187     *long_name = NULL;
 188     *short_name = NULL;
 189     *line = 0;
 190 
 191     return FALSE;
 192 }
 193 
 194 /* --------------------------------------------------------------------------------------------- */
 195 
 196 static GPtrArray *
 197 etags_set_definition_hash (const char *tagfile, const char *start_path, const char *match_func)
     /* [previous][next][first][last][top][bottom][index][help]  */
 198 {
 199     /* *INDENT-OFF* */
 200     enum
 201     {
 202         start,
 203         in_filename,
 204         in_define
 205     } state = start;
 206     /* *INDENT-ON* */
 207 
 208     FILE *f;
 209     char buf[BUF_LARGE];
 210     char *filename = NULL;
 211     GPtrArray *ret = NULL;
 212 
 213     if (match_func == NULL || tagfile == NULL)
 214         return NULL;
 215 
 216     /* open file with positions */
 217     f = fopen (tagfile, "r");
 218     if (f == NULL)
 219         return NULL;
 220 
 221     while (fgets (buf, sizeof (buf), f) != NULL)
 222         switch (state)
 223         {
 224         case start:
 225             if (buf[0] == 0x0C)
 226                 state = in_filename;
 227             break;
 228 
 229         case in_filename:
 230             {
 231                 size_t pos;
 232 
 233                 pos = strcspn (buf, ",");
 234                 g_free (filename);
 235                 filename = g_strndup (buf, pos);
 236                 state = in_define;
 237                 break;
 238             }
 239 
 240         case in_define:
 241             if (buf[0] == 0x0C)
 242                 state = in_filename;
 243             else
 244             {
 245                 char *chekedstr;
 246 
 247                 /* check if the filename matches the define pos */
 248                 chekedstr = strstr (buf, match_func);
 249                 if (chekedstr != NULL)
 250                 {
 251                     char *longname = NULL;
 252                     char *shortname = NULL;
 253                     etags_hash_t *def_hash;
 254 
 255                     def_hash = g_new (etags_hash_t, 1);
 256 
 257                     def_hash->fullpath = mc_build_filename (start_path, filename, (char *) NULL);
 258                     def_hash->filename = g_strdup (filename);
 259 
 260                     def_hash->line = 0;
 261 
 262                     parse_define (chekedstr, &longname, &shortname, &def_hash->line);
 263 
 264                     if (shortname != NULL && *shortname != '\0')
 265                     {
 266                         def_hash->short_define = shortname;
 267                         g_free (longname);
 268                     }
 269                     else
 270                     {
 271                         def_hash->short_define = longname;
 272                         g_free (shortname);
 273                     }
 274 
 275                     if (ret == NULL)
 276                         ret = g_ptr_array_new_with_free_func (etags_hash_free);
 277 
 278                     g_ptr_array_add (ret, def_hash);
 279                 }
 280             }
 281             break;
 282 
 283         default:
 284             break;
 285         }
 286 
 287     g_free (filename);
 288     fclose (f);
 289 
 290     return ret;
 291 }
 292 
 293 /* --------------------------------------------------------------------------------------------- */
 294 
 295 static void
 296 editcmd_dialog_select_definition_add (gpointer data, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 297 {
 298     etags_hash_t *def_hash = (etags_hash_t *) data;
 299     WListbox *def_list = (WListbox *) user_data;
 300     char *label_def;
 301     int def_width;
 302 
 303     label_def =
 304         g_strdup_printf ("%s -> %s:%ld", def_hash->short_define, def_hash->filename,
 305                          def_hash->line);
 306     listbox_add_item (def_list, LISTBOX_APPEND_AT_END, 0, label_def, def_hash, FALSE);
 307     def_width = str_term_width1 (label_def);
 308     g_free (label_def);
 309     def_max_width = MAX (def_max_width, def_width);
 310 }
 311 
 312 /* --------------------------------------------------------------------------------------------- */
 313 /* let the user select where function definition */
 314 
 315 static void
 316 editcmd_dialog_select_definition_show (WEdit * edit, char *match_expr, GPtrArray * def_hash)
     /* [previous][next][first][last][top][bottom][index][help]  */
 317 {
 318     const WRect *w = &CONST_WIDGET (edit)->rect;
 319     int start_x, start_y, offset;
 320     char *curr = NULL;
 321     WDialog *def_dlg;
 322     WListbox *def_list;
 323     int def_dlg_h;              /* dialog height */
 324     int def_dlg_w;              /* dialog width */
 325 
 326     /* calculate the dialog metrics */
 327     def_dlg_h = def_hash->len + 2;
 328     def_dlg_w = COLS - 2;       /* will be clarified later */
 329     start_x = w->x + edit->curs_col + edit->start_col + EDIT_TEXT_HORIZONTAL_OFFSET +
 330         (edit->fullscreen ? 0 : 1) + edit_options.line_state_width;
 331     start_y = w->y + edit->curs_row + EDIT_TEXT_VERTICAL_OFFSET + (edit->fullscreen ? 0 : 1) + 1;
 332 
 333     if (start_x < 0)
 334         start_x = 0;
 335     if (start_x < w->x + 1)
 336         start_x = w->x + 1 + edit_options.line_state_width;
 337 
 338     if (def_dlg_h > LINES - 2)
 339         def_dlg_h = LINES - 2;
 340 
 341     offset = start_y + def_dlg_h - LINES;
 342     if (offset > 0)
 343         start_y -= (offset + 1);
 344 
 345     def_dlg = dlg_create (TRUE, start_y, start_x, def_dlg_h, def_dlg_w, WPOS_KEEP_DEFAULT, TRUE,
 346                           dialog_colors, NULL, NULL, "[Definitions]", match_expr);
 347     def_list = listbox_new (1, 1, def_dlg_h - 2, def_dlg_w - 2, FALSE, NULL);
 348     group_add_widget_autopos (GROUP (def_dlg), def_list, WPOS_KEEP_ALL, NULL);
 349 
 350     /* fill the listbox with the completions and get the maximum width */
 351     def_max_width = 0;
 352     g_ptr_array_foreach (def_hash, editcmd_dialog_select_definition_add, def_list);
 353 
 354     /* adjust dialog width */
 355     def_dlg_w = def_max_width + 4;
 356     offset = start_x + def_dlg_w - COLS;
 357     if (offset > 0)
 358         start_x -= offset;
 359 
 360     widget_set_size (WIDGET (def_dlg), start_y, start_x, def_dlg_h, def_dlg_w);
 361 
 362     /* pop up the dialog and apply the chosen completion */
 363     if (dlg_run (def_dlg) == B_ENTER)
 364     {
 365         etags_hash_t *curr_def = NULL;
 366         gboolean do_moveto = FALSE;
 367 
 368         listbox_get_current (def_list, &curr, (void **) &curr_def);
 369 
 370         if (!edit->modified)
 371             do_moveto = TRUE;
 372         else if (!edit_query_dialog2
 373                  (_("Warning"),
 374                   _("Current text was modified without a file save.\n"
 375                     "Continue discards these changes."), _("C&ontinue"), _("&Cancel")))
 376         {
 377             edit->force |= REDRAW_COMPLETELY;
 378             do_moveto = TRUE;
 379         }
 380 
 381         if (curr != NULL && do_moveto && edit_stack_iterator + 1 < MAX_HISTORY_MOVETO)
 382         {
 383             vfs_path_free (edit_history_moveto[edit_stack_iterator].filename_vpath, TRUE);
 384 
 385             /* Is file path absolute? Prepend with dir_vpath if necessary */
 386             if (edit->filename_vpath != NULL && edit->filename_vpath->relative
 387                 && edit->dir_vpath != NULL)
 388                 edit_history_moveto[edit_stack_iterator].filename_vpath =
 389                     vfs_path_append_vpath_new (edit->dir_vpath, edit->filename_vpath, NULL);
 390             else
 391                 edit_history_moveto[edit_stack_iterator].filename_vpath =
 392                     vfs_path_clone (edit->filename_vpath);
 393 
 394             edit_history_moveto[edit_stack_iterator].line = edit->start_line + edit->curs_row + 1;
 395             edit_stack_iterator++;
 396             vfs_path_free (edit_history_moveto[edit_stack_iterator].filename_vpath, TRUE);
 397             edit_history_moveto[edit_stack_iterator].filename_vpath =
 398                 vfs_path_from_str ((char *) curr_def->fullpath);
 399             edit_history_moveto[edit_stack_iterator].line = curr_def->line;
 400             edit_reload_line (edit, edit_history_moveto[edit_stack_iterator].filename_vpath,
 401                               edit_history_moveto[edit_stack_iterator].line);
 402         }
 403     }
 404 
 405     /* destroy dialog before return */
 406     widget_destroy (WIDGET (def_dlg));
 407 }
 408 
 409 /* --------------------------------------------------------------------------------------------- */
 410 /*** public functions ****************************************************************************/
 411 /* --------------------------------------------------------------------------------------------- */
 412 
 413 void
 414 edit_get_match_keyword_cmd (WEdit * edit)
     /* [previous][next][first][last][top][bottom][index][help]  */
 415 {
 416     gsize word_len = 0;
 417     gsize i;
 418     off_t word_start = 0;
 419     GString *match_expr;
 420     char *path = NULL;
 421     char *ptr = NULL;
 422     char *tagfile = NULL;
 423     GPtrArray *def_hash = NULL;
 424 
 425     /* search start of word to be completed */
 426     if (!edit_buffer_find_word_start (&edit->buffer, &word_start, &word_len))
 427         return;
 428 
 429     /* prepare match expression */
 430     match_expr = g_string_sized_new (word_len);
 431     for (i = 0; i < word_len; i++)
 432         g_string_append_c (match_expr, edit_buffer_get_byte (&edit->buffer, word_start + i));
 433 
 434     ptr = g_get_current_dir ();
 435     path = g_strconcat (ptr, PATH_SEP_STR, (char *) NULL);
 436     g_free (ptr);
 437 
 438     /* Recursive search file 'TAGS' in parent dirs */
 439     do
 440     {
 441         ptr = g_path_get_dirname (path);
 442         g_free (path);
 443         path = ptr;
 444         g_free (tagfile);
 445         tagfile = mc_build_filename (path, TAGS_NAME, (char *) NULL);
 446         if (tagfile != NULL && exist_file (tagfile))
 447             break;
 448     }
 449     while (strcmp (path, PATH_SEP_STR) != 0);
 450 
 451     if (tagfile != NULL)
 452     {
 453         def_hash = etags_set_definition_hash (tagfile, path, match_expr->str);
 454         g_free (tagfile);
 455     }
 456     g_free (path);
 457 
 458     if (def_hash != NULL)
 459     {
 460         editcmd_dialog_select_definition_show (edit, match_expr->str, def_hash);
 461 
 462         g_ptr_array_free (def_hash, TRUE);
 463     }
 464 
 465     g_string_free (match_expr, TRUE);
 466 }
 467 
 468 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */