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

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