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_take (def_list, LISTBOX_APPEND_AT_END, 0, label_def, def_hash, FALSE);
 307     def_width = str_term_width1 (label_def);
 308     def_max_width = MAX (def_max_width, def_width);
 309 }
 310 
 311 /* --------------------------------------------------------------------------------------------- */
 312 /* let the user select where function definition */
 313 
 314 static void
 315 editcmd_dialog_select_definition_show (WEdit *edit, char *match_expr, GPtrArray *def_hash)
     /* [previous][next][first][last][top][bottom][index][help]  */
 316 {
 317     const WRect *w = &CONST_WIDGET (edit)->rect;
 318     int start_x, start_y, offset;
 319     char *curr = NULL;
 320     WDialog *def_dlg;
 321     WListbox *def_list;
 322     int def_dlg_h;              /* dialog height */
 323     int def_dlg_w;              /* dialog width */
 324 
 325     /* calculate the dialog metrics */
 326     def_dlg_h = def_hash->len + 2;
 327     def_dlg_w = COLS - 2;       /* will be clarified later */
 328     start_x = w->x + edit->curs_col + edit->start_col + EDIT_TEXT_HORIZONTAL_OFFSET +
 329         (edit->fullscreen ? 0 : 1) + edit_options.line_state_width;
 330     start_y = w->y + edit->curs_row + EDIT_TEXT_VERTICAL_OFFSET + (edit->fullscreen ? 0 : 1) + 1;
 331 
 332     if (start_x < 0)
 333         start_x = 0;
 334     if (start_x < w->x + 1)
 335         start_x = w->x + 1 + edit_options.line_state_width;
 336 
 337     if (def_dlg_h > LINES - 2)
 338         def_dlg_h = LINES - 2;
 339 
 340     offset = start_y + def_dlg_h - LINES;
 341     if (offset > 0)
 342         start_y -= (offset + 1);
 343 
 344     def_dlg = dlg_create (TRUE, start_y, start_x, def_dlg_h, def_dlg_w, WPOS_KEEP_DEFAULT, TRUE,
 345                           dialog_colors, NULL, NULL, "[Definitions]", match_expr);
 346     def_list = listbox_new (1, 1, def_dlg_h - 2, def_dlg_w - 2, FALSE, NULL);
 347     group_add_widget_autopos (GROUP (def_dlg), def_list, WPOS_KEEP_ALL, NULL);
 348 
 349     /* fill the listbox with the completions and get the maximum width */
 350     def_max_width = 0;
 351     g_ptr_array_foreach (def_hash, editcmd_dialog_select_definition_add, def_list);
 352 
 353     /* adjust dialog width */
 354     def_dlg_w = def_max_width + 4;
 355     offset = start_x + def_dlg_w - COLS;
 356     if (offset > 0)
 357         start_x -= offset;
 358 
 359     widget_set_size (WIDGET (def_dlg), start_y, start_x, def_dlg_h, def_dlg_w);
 360 
 361     /* pop up the dialog and apply the chosen completion */
 362     if (dlg_run (def_dlg) == B_ENTER)
 363     {
 364         etags_hash_t *curr_def = NULL;
 365         gboolean do_moveto = FALSE;
 366 
 367         listbox_get_current (def_list, &curr, (void **) &curr_def);
 368 
 369         if (!edit->modified)
 370             do_moveto = TRUE;
 371         else if (!edit_query_dialog2
 372                  (_("Warning"),
 373                   _("Current text was modified without a file save.\n"
 374                     "Continue discards these changes."), _("C&ontinue"), _("&Cancel")))
 375         {
 376             edit->force |= REDRAW_COMPLETELY;
 377             do_moveto = TRUE;
 378         }
 379 
 380         if (curr != NULL && do_moveto && edit_stack_iterator + 1 < MAX_HISTORY_MOVETO)
 381         {
 382             vfs_path_t *vpath;
 383 
 384             /* Is file path absolute? Prepend with dir_vpath if necessary */
 385             if (edit->filename_vpath != NULL && edit->filename_vpath->relative
 386                 && edit->dir_vpath != NULL)
 387                 vpath = vfs_path_append_vpath_new (edit->dir_vpath, edit->filename_vpath, NULL);
 388             else
 389                 vpath = vfs_path_clone (edit->filename_vpath);
 390 
 391             edit_arg_assign (&edit_history_moveto[edit_stack_iterator], vpath,
 392                              edit->start_line + edit->curs_row + 1);
 393             edit_stack_iterator++;
 394             edit_arg_assign (&edit_history_moveto[edit_stack_iterator],
 395                              vfs_path_from_str ((char *) curr_def->fullpath), curr_def->line);
 396             edit_reload_line (edit, &edit_history_moveto[edit_stack_iterator]);
 397         }
 398     }
 399 
 400     /* destroy dialog before return */
 401     widget_destroy (WIDGET (def_dlg));
 402 }
 403 
 404 /* --------------------------------------------------------------------------------------------- */
 405 /*** public functions ****************************************************************************/
 406 /* --------------------------------------------------------------------------------------------- */
 407 
 408 void
 409 edit_get_match_keyword_cmd (WEdit *edit)
     /* [previous][next][first][last][top][bottom][index][help]  */
 410 {
 411     gsize word_len = 0;
 412     gsize i;
 413     off_t word_start = 0;
 414     GString *match_expr;
 415     char *path = NULL;
 416     char *ptr = NULL;
 417     char *tagfile = NULL;
 418     GPtrArray *def_hash = NULL;
 419 
 420     /* search start of word to be completed */
 421     if (!edit_buffer_find_word_start (&edit->buffer, &word_start, &word_len))
 422         return;
 423 
 424     /* prepare match expression */
 425     match_expr = g_string_sized_new (word_len);
 426     for (i = 0; i < word_len; i++)
 427         g_string_append_c (match_expr, edit_buffer_get_byte (&edit->buffer, word_start + i));
 428 
 429     ptr = my_get_current_dir ();
 430     path = g_strconcat (ptr, PATH_SEP_STR, (char *) NULL);
 431     g_free (ptr);
 432 
 433     /* Recursive search file 'TAGS' in parent dirs */
 434     do
 435     {
 436         ptr = g_path_get_dirname (path);
 437         g_free (path);
 438         path = ptr;
 439         g_free (tagfile);
 440         tagfile = mc_build_filename (path, TAGS_NAME, (char *) NULL);
 441         if (tagfile != NULL && exist_file (tagfile))
 442             break;
 443     }
 444     while (strcmp (path, PATH_SEP_STR) != 0);
 445 
 446     if (tagfile != NULL)
 447     {
 448         def_hash = etags_set_definition_hash (tagfile, path, match_expr->str);
 449         g_free (tagfile);
 450     }
 451     g_free (path);
 452 
 453     if (def_hash != NULL)
 454     {
 455         editcmd_dialog_select_definition_show (edit, match_expr->str, def_hash);
 456 
 457         g_ptr_array_free (def_hash, TRUE);
 458     }
 459 
 460     g_string_free (match_expr, TRUE);
 461 }
 462 
 463 /* --------------------------------------------------------------------------------------------- */

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