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-2025
  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 <https://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     enum
  83     {
  84         in_longname,
  85         in_shortname,
  86         in_shortname_first_char,
  87         in_line,
  88         finish
  89     } def_state = in_longname;
  90 
  91     GString *longdef = NULL;
  92     GString *shortdef = NULL;
  93     GString *linedef = NULL;
  94 
  95     char c = *buf;
  96 
  97     while (!(c == '\0' || c == '\n'))
  98     {
  99         switch (def_state)
 100         {
 101         case in_longname:
 102             if (c == 0x01)
 103                 def_state = in_line;
 104             else if (c == 0x7F)
 105                 def_state = in_shortname;
 106             else
 107             {
 108                 if (longdef == NULL)
 109                     longdef = g_string_sized_new (32);
 110 
 111                 g_string_append_c (longdef, c);
 112             }
 113             break;
 114 
 115         case in_shortname_first_char:
 116             if (isdigit (c))
 117             {
 118                 if (shortdef == NULL)
 119                     shortdef = g_string_sized_new (32);
 120                 else
 121                     g_string_set_size (shortdef, 0);
 122 
 123                 buf--;
 124                 def_state = in_line;
 125             }
 126             else if (c == 0x01)
 127                 def_state = in_line;
 128             else
 129             {
 130                 if (shortdef == NULL)
 131                     shortdef = g_string_sized_new (32);
 132 
 133                 g_string_append_c (shortdef, c);
 134                 def_state = in_shortname;
 135             }
 136             break;
 137 
 138         case in_shortname:
 139             if (c == 0x01)
 140                 def_state = in_line;
 141             else if (c == '\n')
 142                 def_state = finish;
 143             else
 144             {
 145                 if (shortdef == NULL)
 146                     shortdef = g_string_sized_new (32);
 147 
 148                 g_string_append_c (shortdef, c);
 149             }
 150             break;
 151 
 152         case in_line:
 153             if (c == ',' || c == '\n')
 154                 def_state = finish;
 155             else if (isdigit (c))
 156             {
 157                 if (linedef == NULL)
 158                     linedef = g_string_sized_new (32);
 159 
 160                 g_string_append_c (linedef, c);
 161             }
 162             break;
 163 
 164         case finish:
 165             *long_name = longdef == NULL ? NULL : g_string_free (longdef, FALSE);
 166             *short_name = shortdef == NULL ? NULL : g_string_free (shortdef, FALSE);
 167 
 168             if (linedef == NULL)
 169                 *line = 0;
 170             else
 171             {
 172                 *line = atol (linedef->str);
 173                 g_string_free (linedef, TRUE);
 174             }
 175             return TRUE;
 176 
 177         default:
 178             break;
 179         }
 180 
 181         buf++;
 182         c = *buf;
 183     }
 184 
 185     *long_name = NULL;
 186     *short_name = NULL;
 187     *line = 0;
 188 
 189     return FALSE;
 190 }
 191 
 192 /* --------------------------------------------------------------------------------------------- */
 193 
 194 static GPtrArray *
 195 etags_set_definition_hash (const char *tagfile, const char *start_path, const char *match_func)
     /* [previous][next][first][last][top][bottom][index][help]  */
 196 {
 197     enum
 198     {
 199         start,
 200         in_filename,
 201         in_define
 202     } state = start;
 203 
 204     FILE *f;
 205     char buf[BUF_LARGE];
 206     char *filename = NULL;
 207     GPtrArray *ret = NULL;
 208 
 209     if (match_func == NULL || tagfile == NULL)
 210         return NULL;
 211 
 212     // open file with positions
 213     f = fopen (tagfile, "r");
 214     if (f == NULL)
 215         return NULL;
 216 
 217     while (fgets (buf, sizeof (buf), f) != NULL)
 218         switch (state)
 219         {
 220         case start:
 221             if (buf[0] == 0x0C)
 222                 state = in_filename;
 223             break;
 224 
 225         case in_filename:
 226         {
 227             size_t pos;
 228 
 229             pos = strcspn (buf, ",");
 230             g_free (filename);
 231             filename = g_strndup (buf, pos);
 232             state = in_define;
 233             break;
 234         }
 235 
 236         case in_define:
 237             if (buf[0] == 0x0C)
 238                 state = in_filename;
 239             else
 240             {
 241                 char *chekedstr;
 242 
 243                 // check if the filename matches the define pos
 244                 chekedstr = strstr (buf, match_func);
 245                 if (chekedstr != NULL)
 246                 {
 247                     char *longname = NULL;
 248                     char *shortname = NULL;
 249                     etags_hash_t *def_hash;
 250 
 251                     def_hash = g_new (etags_hash_t, 1);
 252 
 253                     def_hash->fullpath = mc_build_filename (start_path, filename, (char *) NULL);
 254                     def_hash->filename = g_strdup (filename);
 255 
 256                     def_hash->line = 0;
 257 
 258                     parse_define (chekedstr, &longname, &shortname, &def_hash->line);
 259 
 260                     if (shortname != NULL && *shortname != '\0')
 261                     {
 262                         def_hash->short_define = shortname;
 263                         g_free (longname);
 264                     }
 265                     else
 266                     {
 267                         def_hash->short_define = longname;
 268                         g_free (shortname);
 269                     }
 270 
 271                     if (ret == NULL)
 272                         ret = g_ptr_array_new_with_free_func (etags_hash_free);
 273 
 274                     g_ptr_array_add (ret, def_hash);
 275                 }
 276             }
 277             break;
 278 
 279         default:
 280             break;
 281         }
 282 
 283     g_free (filename);
 284     fclose (f);
 285 
 286     return ret;
 287 }
 288 
 289 /* --------------------------------------------------------------------------------------------- */
 290 
 291 static void
 292 editcmd_dialog_select_definition_add (gpointer data, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 293 {
 294     etags_hash_t *def_hash = (etags_hash_t *) data;
 295     WListbox *def_list = (WListbox *) user_data;
 296     char *label_def;
 297     int def_width;
 298 
 299     label_def = g_strdup_printf ("%s -> %s:%ld", def_hash->short_define, def_hash->filename,
 300                                  def_hash->line);
 301     listbox_add_item_take (def_list, LISTBOX_APPEND_AT_END, 0, label_def, def_hash, FALSE);
 302     def_width = str_term_width1 (label_def);
 303     def_max_width = MAX (def_max_width, def_width);
 304 }
 305 
 306 /* --------------------------------------------------------------------------------------------- */
 307 /* let the user select where function definition */
 308 
 309 static void
 310 editcmd_dialog_select_definition_show (WEdit *edit, char *match_expr, GPtrArray *def_hash)
     /* [previous][next][first][last][top][bottom][index][help]  */
 311 {
 312     const WRect *w = &CONST_WIDGET (edit)->rect;
 313     int start_x, start_y, offset;
 314     char *curr = NULL;
 315     WDialog *def_dlg;
 316     WListbox *def_list;
 317     int def_dlg_h;  // dialog height
 318     int def_dlg_w;  // dialog width
 319 
 320     // calculate the dialog metrics
 321     def_dlg_h = def_hash->len + 2;
 322     def_dlg_w = COLS - 2;  // will be clarified later
 323     start_x = w->x + edit->curs_col + edit->start_col + EDIT_TEXT_HORIZONTAL_OFFSET
 324         + (edit->fullscreen != 0 ? 0 : 1) + edit_options.line_state_width;
 325     start_y =
 326         w->y + edit->curs_row + EDIT_TEXT_VERTICAL_OFFSET + (edit->fullscreen != 0 ? 0 : 1) + 1;
 327 
 328     if (start_x < 0)
 329         start_x = 0;
 330     if (start_x < w->x + 1)
 331         start_x = w->x + 1 + edit_options.line_state_width;
 332 
 333     if (def_dlg_h > LINES - 2)
 334         def_dlg_h = LINES - 2;
 335 
 336     offset = start_y + def_dlg_h - LINES;
 337     if (offset > 0)
 338         start_y -= (offset + 1);
 339 
 340     def_dlg = dlg_create (TRUE, start_y, start_x, def_dlg_h, def_dlg_w, WPOS_KEEP_DEFAULT, TRUE,
 341                           dialog_colors, NULL, NULL, "[Definitions]", match_expr);
 342     def_list = listbox_new (1, 1, def_dlg_h - 2, def_dlg_w - 2, FALSE, NULL);
 343     group_add_widget_autopos (GROUP (def_dlg), def_list, WPOS_KEEP_ALL, NULL);
 344 
 345     // fill the listbox with the completions and get the maximum width
 346     def_max_width = 0;
 347     g_ptr_array_foreach (def_hash, editcmd_dialog_select_definition_add, def_list);
 348 
 349     // adjust dialog width
 350     def_dlg_w = def_max_width + 4;
 351     offset = start_x + def_dlg_w - COLS;
 352     if (offset > 0)
 353         start_x -= offset;
 354 
 355     widget_set_size (WIDGET (def_dlg), start_y, start_x, def_dlg_h, def_dlg_w);
 356 
 357     // pop up the dialog and apply the chosen completion
 358     if (dlg_run (def_dlg) == B_ENTER)
 359     {
 360         etags_hash_t *curr_def = NULL;
 361         gboolean do_moveto = FALSE;
 362 
 363         listbox_get_current (def_list, &curr, (void **) &curr_def);
 364 
 365         if (edit->modified == 0)
 366             do_moveto = TRUE;
 367         else if (!edit_query_dialog2 (_ ("Warning"),
 368                                       _ ("Current text was modified without a file save.\n"
 369                                          "Continue discards these changes."),
 370                                       _ ("C&ontinue"), _ ("&Cancel")))
 371         {
 372             edit->force |= REDRAW_COMPLETELY;
 373             do_moveto = TRUE;
 374         }
 375 
 376         if (curr != NULL && do_moveto && edit_stack_iterator + 1 < MAX_HISTORY_MOVETO)
 377         {
 378             vfs_path_t *vpath;
 379 
 380             // Is file path absolute? Prepend with dir_vpath if necessary
 381             if (edit->filename_vpath != NULL && edit->filename_vpath->relative
 382                 && edit->dir_vpath != NULL)
 383                 vpath = vfs_path_append_vpath_new (edit->dir_vpath, edit->filename_vpath, NULL);
 384             else
 385                 vpath = vfs_path_clone (edit->filename_vpath);
 386 
 387             edit_arg_assign (&edit_history_moveto[edit_stack_iterator], vpath,
 388                              edit->start_line + edit->curs_row + 1);
 389             edit_stack_iterator++;
 390             edit_arg_assign (&edit_history_moveto[edit_stack_iterator],
 391                              vfs_path_from_str ((char *) curr_def->fullpath), curr_def->line);
 392             edit_reload_line (edit, &edit_history_moveto[edit_stack_iterator]);
 393         }
 394     }
 395 
 396     // destroy dialog before return
 397     widget_destroy (WIDGET (def_dlg));
 398 }
 399 
 400 /* --------------------------------------------------------------------------------------------- */
 401 /*** public functions ****************************************************************************/
 402 /* --------------------------------------------------------------------------------------------- */
 403 
 404 void
 405 edit_get_match_keyword_cmd (WEdit *edit)
     /* [previous][next][first][last][top][bottom][index][help]  */
 406 {
 407     gsize word_len = 0;
 408     gsize i;
 409     off_t word_start = 0;
 410     GString *match_expr;
 411     char *path = NULL;
 412     char *ptr = NULL;
 413     char *tagfile = NULL;
 414     GPtrArray *def_hash = NULL;
 415 
 416     // search start of word to be completed
 417     if (!edit_buffer_find_word_start (&edit->buffer, &word_start, &word_len))
 418         return;
 419 
 420     // prepare match expression
 421     match_expr = g_string_sized_new (word_len);
 422     for (i = 0; i < word_len; i++)
 423         g_string_append_c (match_expr, edit_buffer_get_byte (&edit->buffer, word_start + i));
 424 
 425     ptr = my_get_current_dir ();
 426     path = g_strconcat (ptr, PATH_SEP_STR, (char *) NULL);
 427     g_free (ptr);
 428 
 429     // Recursive search file 'TAGS' in parent dirs
 430     do
 431     {
 432         ptr = g_path_get_dirname (path);
 433         g_free (path);
 434         path = ptr;
 435         g_free (tagfile);
 436         tagfile = mc_build_filename (path, TAGS_NAME, (char *) NULL);
 437         if (tagfile != NULL && exist_file (tagfile))
 438             break;
 439     }
 440     while (strcmp (path, PATH_SEP_STR) != 0);
 441 
 442     if (tagfile != NULL)
 443     {
 444         def_hash = etags_set_definition_hash (tagfile, path, match_expr->str);
 445         g_free (tagfile);
 446     }
 447     g_free (path);
 448 
 449     if (def_hash != NULL)
 450     {
 451         editcmd_dialog_select_definition_show (edit, match_expr->str, def_hash);
 452 
 453         g_ptr_array_free (def_hash, TRUE);
 454     }
 455 
 456     g_string_free (match_expr, TRUE);
 457 }
 458 
 459 /* --------------------------------------------------------------------------------------------- */

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