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-2022
  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"           /* canonicalize_pathname() */
  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 /*** file scope variables ************************************************************************/
  57 
  58 static int def_max_width;
  59 
  60 /* --------------------------------------------------------------------------------------------- */
  61 /*** file scope functions ************************************************************************/
  62 /* --------------------------------------------------------------------------------------------- */
  63 
  64 static void
  65 etags_hash_free (gpointer data)
     /* [previous][next][first][last][top][bottom][index][help]  */
  66 {
  67     etags_hash_t *hash = (etags_hash_t *) data;
  68 
  69     g_free (hash->filename);
  70     g_free (hash->fullpath);
  71     g_free (hash->short_define);
  72     g_free (hash);
  73 }
  74 
  75 /* --------------------------------------------------------------------------------------------- */
  76 
  77 static gboolean
  78 parse_define (const char *buf, char **long_name, char **short_name, long *line)
     /* [previous][next][first][last][top][bottom][index][help]  */
  79 {
  80     /* *INDENT-OFF* */
  81     enum
  82     {
  83         in_longname,
  84         in_shortname,
  85         in_shortname_first_char,
  86         in_line,
  87         finish
  88     } def_state = in_longname;
  89     /* *INDENT-ON* */
  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     /* *INDENT-OFF* */
 198     enum
 199     {
 200         start,
 201         in_filename,
 202         in_define
 203     } state = start;
 204     /* *INDENT-ON* */
 205 
 206     FILE *f;
 207     char buf[BUF_LARGE];
 208     char *filename = NULL;
 209     GPtrArray *ret = NULL;
 210 
 211     if (match_func == NULL || tagfile == NULL)
 212         return NULL;
 213 
 214     /* open file with positions */
 215     f = fopen (tagfile, "r");
 216     if (f == NULL)
 217         return NULL;
 218 
 219     while (fgets (buf, sizeof (buf), f) != NULL)
 220         switch (state)
 221         {
 222         case start:
 223             if (buf[0] == 0x0C)
 224                 state = in_filename;
 225             break;
 226 
 227         case in_filename:
 228             {
 229                 size_t pos;
 230 
 231                 pos = strcspn (buf, ",");
 232                 g_free (filename);
 233                 filename = g_strndup (buf, pos);
 234                 state = in_define;
 235                 break;
 236             }
 237 
 238         case in_define:
 239             if (buf[0] == 0x0C)
 240                 state = in_filename;
 241             else
 242             {
 243                 char *chekedstr;
 244 
 245                 /* check if the filename matches the define pos */
 246                 chekedstr = strstr (buf, match_func);
 247                 if (chekedstr != NULL)
 248                 {
 249                     char *longname = NULL;
 250                     char *shortname = NULL;
 251                     etags_hash_t *def_hash;
 252 
 253                     def_hash = g_new (etags_hash_t, 1);
 254 
 255                     def_hash->fullpath = mc_build_filename (start_path, filename, (char *) NULL);
 256                     canonicalize_pathname (def_hash->fullpath);
 257                     def_hash->filename = g_strdup (filename);
 258 
 259                     def_hash->line = 0;
 260 
 261                     parse_define (chekedstr, &longname, &shortname, &def_hash->line);
 262 
 263                     if (shortname != NULL && *shortname != '\0')
 264                     {
 265                         def_hash->short_define = shortname;
 266                         g_free (longname);
 267                     }
 268                     else
 269                     {
 270                         def_hash->short_define = longname;
 271                         g_free (shortname);
 272                     }
 273 
 274                     if (ret == NULL)
 275                         ret = g_ptr_array_new_with_free_func (etags_hash_free);
 276 
 277                     g_ptr_array_add (ret, def_hash);
 278                 }
 279             }
 280             break;
 281 
 282         default:
 283             break;
 284         }
 285 
 286     g_free (filename);
 287     fclose (f);
 288 
 289     return ret;
 290 }
 291 
 292 /* --------------------------------------------------------------------------------------------- */
 293 
 294 static void
 295 editcmd_dialog_select_definition_add (gpointer data, gpointer user_data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 296 {
 297     etags_hash_t *def_hash = (etags_hash_t *) data;
 298     WListbox *def_list = (WListbox *) user_data;
 299     char *label_def;
 300     int def_width;
 301 
 302     label_def =
 303         g_strdup_printf ("%s -> %s:%ld", def_hash->short_define, def_hash->filename,
 304                          def_hash->line);
 305     listbox_add_item (def_list, LISTBOX_APPEND_AT_END, 0, label_def, def_hash, FALSE);
 306     def_width = str_term_width1 (label_def);
 307     g_free (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) + option_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 + option_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 maximim 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_free (edit_history_moveto[edit_stack_iterator].filename_vpath, TRUE);
 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                 edit_history_moveto[edit_stack_iterator].filename_vpath =
 388                     vfs_path_append_vpath_new (edit->dir_vpath, edit->filename_vpath, NULL);
 389             else
 390                 edit_history_moveto[edit_stack_iterator].filename_vpath =
 391                     vfs_path_clone (edit->filename_vpath);
 392 
 393             edit_history_moveto[edit_stack_iterator].line = edit->start_line + edit->curs_row + 1;
 394             edit_stack_iterator++;
 395             vfs_path_free (edit_history_moveto[edit_stack_iterator].filename_vpath, TRUE);
 396             edit_history_moveto[edit_stack_iterator].filename_vpath =
 397                 vfs_path_from_str ((char *) curr_def->fullpath);
 398             edit_history_moveto[edit_stack_iterator].line = curr_def->line;
 399             edit_reload_line (edit, edit_history_moveto[edit_stack_iterator].filename_vpath,
 400                               edit_history_moveto[edit_stack_iterator].line);
 401         }
 402     }
 403 
 404     /* destroy dialog before return */
 405     widget_destroy (WIDGET (def_dlg));
 406 }
 407 
 408 /* --------------------------------------------------------------------------------------------- */
 409 /*** public functions ****************************************************************************/
 410 /* --------------------------------------------------------------------------------------------- */
 411 
 412 void
 413 edit_get_match_keyword_cmd (WEdit * edit)
     /* [previous][next][first][last][top][bottom][index][help]  */
 414 {
 415     gsize word_len = 0;
 416     gsize i;
 417     off_t word_start = 0;
 418     GString *match_expr;
 419     char *path = NULL;
 420     char *ptr = NULL;
 421     char *tagfile = NULL;
 422     GPtrArray *def_hash = NULL;
 423 
 424     /* search start of word to be completed */
 425     if (!edit_buffer_find_word_start (&edit->buffer, &word_start, &word_len))
 426         return;
 427 
 428     /* prepare match expression */
 429     match_expr = g_string_sized_new (word_len);
 430     for (i = 0; i < word_len; i++)
 431         g_string_append_c (match_expr, edit_buffer_get_byte (&edit->buffer, word_start + i));
 432 
 433     ptr = g_get_current_dir ();
 434     path = g_strconcat (ptr, PATH_SEP_STR, (char *) NULL);
 435     g_free (ptr);
 436 
 437     /* Recursive search file 'TAGS' in parent dirs */
 438     do
 439     {
 440         ptr = g_path_get_dirname (path);
 441         g_free (path);
 442         path = ptr;
 443         g_free (tagfile);
 444         tagfile = mc_build_filename (path, TAGS_NAME, (char *) NULL);
 445         if (tagfile != NULL && exist_file (tagfile))
 446             break;
 447     }
 448     while (strcmp (path, PATH_SEP_STR) != 0);
 449 
 450     if (tagfile != NULL)
 451     {
 452         def_hash = etags_set_definition_hash (tagfile, path, match_expr->str);
 453         g_free (tagfile);
 454     }
 455     g_free (path);
 456 
 457     if (def_hash != NULL)
 458     {
 459         editcmd_dialog_select_definition_show (edit, match_expr->str, def_hash);
 460 
 461         g_ptr_array_free (def_hash, TRUE);
 462     }
 463 
 464     g_string_free (match_expr, TRUE);
 465 }
 466 
 467 /* --------------------------------------------------------------------------------------------- */

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