bug-gnu-emacs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Patch: auto-update etags


From: Tom Tromey
Subject: Patch: auto-update etags
Date: 07 Feb 2002 11:03:12 -0700

This patch changes etags.c and etags.el to support an "auto update"
mode.  The idea here is that we record in the TAGS file the options
that were used to create each set of entries.  Then when saving a file
which is mentioned in a TAGS file, we re-run etags to automatically
regenerate that file's entries.

This is similar to what some IDEs provide.  For instance, Source
Navigator does something like this.

In order to do this I had to change the TAGS file format a little
bit.  I also made `etags -u' work with Emacs-style tags files.  While
doing this I noticed a couple little bugs in etags which I also fixed:

* Entries in the long options table were not conditional.  This would
  lead to strange error messages like this:

    creche. etags -u
            Try `etags --help' for a complete list of options.

* The copyright date in `--version' was not recent.

This new feature only works with Emacs-style tags files.

Tom

Index: lib-src/ChangeLog
===================================================================
RCS file: /cvs/emacs/lib-src/ChangeLog,v
retrieving revision 2.140
diff -u -r2.140 ChangeLog
--- lib-src/ChangeLog   3 Feb 2002 17:33:19 -0000       2.140
+++ lib-src/ChangeLog   7 Feb 2002 17:28:32 -0000
@@ -1,3 +1,19 @@
+2002-02-06  Tom Tromey  <address@hidden>
+
+       * etags.c (main): Recognize `-u' in etags mode.
+       (longopts): Don't mention options which aren't enabled in the
+       current mode.
+       (print_help): Print `-u' in etags mode.
+       (update_file): New function.
+       (get_state_string): New function.
+       (add_pattern): Likewise.
+       (add_string): Likewise.
+       (process_file): Use get_state_string.
+       (add_argument): New function.
+       (pattern): Added ignore_case field.
+       (add_regex): Initialize it.
+       (print_version): Updated copyright date.
+
 2002-02-03  Paul Eggert  <address@hidden>
 
        * rcs2log(Copyright): Update to 2002.
Index: lib-src/etags.c
===================================================================
RCS file: /cvs/emacs/lib-src/etags.c,v
retrieving revision 3.7
diff -u -r3.7 etags.c
--- lib-src/etags.c     26 Dec 2001 22:11:21 -0000      3.7
+++ lib-src/etags.c     7 Feb 2002 17:28:36 -0000
@@ -1,5 +1,5 @@
 /* Tags file maker to go with GNU Emacs           -*- coding: latin-1 -*-
-   Copyright (C) 1984, 1987-1989, 1993-1995, 1998-2001
+   Copyright (C) 1984, 1987-1989, 1993-1995, 1998-2001, 2002
    Free Software Foundation, Inc. and Ken Arnold
 
 This file is not considered part of GNU Emacs.
@@ -366,7 +366,6 @@
 char *dbp;                     /* pointer to start of current tag */
 
 node *head;                    /* the head of the binary tree of tags */
-
 linebuffer lb;                 /* the current line */
 
 /* boolean "functions" (see init)      */
@@ -408,22 +407,21 @@
 {
   { "packages-only",      no_argument,      &packages_only, TRUE  },
   { "append",            no_argument,       NULL,           'a'   },
-  { "backward-search",   no_argument,       NULL,           'B'   },
   { "c++",               no_argument,       NULL,           'C'   },
-  { "cxref",             no_argument,       NULL,           'x'   },
   { "defines",           no_argument,       NULL,           'd'   },
   { "declarations",      no_argument,       &declarations,  TRUE  },
-  { "no-defines",        no_argument,       NULL,           'D'   },
   { "globals",           no_argument,       &globals,       TRUE  },
+  { "no-defines",        no_argument,       NULL,           'D'   },
   { "no-globals",        no_argument,       &globals,       FALSE },
   { "help",              no_argument,       NULL,           'h'   },
   { "help",              no_argument,       NULL,           'H'   },
   { "ignore-indentation", no_argument,      NULL,           'I'   },
+#if (!CTAGS)
   { "include",           required_argument, NULL,           'i'   },
+#endif /* ! CTAGS */
   { "language",           required_argument, NULL,                  'l'   },
   { "members",           no_argument,       &members,       TRUE  },
   { "no-members",        no_argument,       &members,       FALSE },
-  { "no-warn",           no_argument,       NULL,           'w'   },
   { "output",            required_argument, NULL,           'o'   },
 #ifdef ETAGS_REGEXPS
   { "regex",             required_argument, NULL,           'r'   },
@@ -434,7 +432,12 @@
   { "typedefs-and-c++",          no_argument,       NULL,           'T'   },
   { "update",            no_argument,       NULL,           'u'   },
   { "version",           no_argument,       NULL,           'V'   },
+#if CTAGS
+  { "backward-search",   no_argument,       NULL,           'B'   },
+  { "cxref",             no_argument,       NULL,           'x'   },
+  { "no-warn",           no_argument,       NULL,           'w'   },
   { "vgrind",            no_argument,       NULL,           'v'   },
+#endif /* CTAGS */
   { NULL }
 };
 #endif /* LONG_OPTIONS */
@@ -451,6 +454,7 @@
   struct re_registers regs;
   char *name_pattern;
   bool error_signaled;
+  bool ignore_case;
 } pattern;
 
 /* List of all regexps. */
@@ -643,7 +647,7 @@
 print_version ()
 {
   printf ("%s (%s %s)\n", (CTAGS) ? "ctags" : "etags", EMACS_NAME, VERSION);
-  puts ("Copyright (C) 1999 Free Software Foundation, Inc. and Ken Arnold");
+  puts ("Copyright (C) 2002 Free Software Foundation, Inc. and Ken Arnold");
   puts ("This program is distributed under the same terms as Emacs");
 
   exit (GOOD);
@@ -752,13 +756,18 @@
       puts ("-T, --typedefs-and-c++\n\
         Generate tag entries for C typedefs, C struct/enum/union tags,\n\
         and C++ member functions.");
-      puts ("-u, --update\n\
+    }
+
+  puts ("-u, --update\n\
         Update the tag entries for the given files, leaving tag\n\
         entries for other files in place.  Currently, this is\n\
         implemented by deleting the existing entries for the given\n\
         files and then rewriting the new entries at the end of the\n\
         tags file.  It is often faster to simply rebuild the entire\n\
         tag file than to use this.");
+
+  if (CTAGS)
+    {
       puts ("-v, --vgrind\n\
         Generates an index of items intended for human consumption,\n\
         similar to the output of vgrind.  The index is sorted, and\n\
@@ -795,7 +804,9 @@
   at_icregexp
 };
 
-/* This structure helps us allow mixing of --lang and file names. */
+/* This structure helps us allow mixing of --lang and file names.  It
+   is also used to let us get updated command-line information from
+   the TAGS file when updating.  */
 typedef struct
 {
   enum argument_type arg_type;
@@ -803,6 +814,8 @@
   language *lang;              /* language of the regexp */
 } argument;
 
+static void update_file __P((int, argument *));
+
 #ifdef VMS                     /* VMS specific functions */
 
 #define        EOS     '\0'
@@ -1072,6 +1085,7 @@
        case 'T':
          typedefs = typedefs_or_cplusplus = TRUE;
          break;
+       case 'u': update = TRUE;        break;
 #if (!CTAGS)
          /* Etags options */
        case 'i':
@@ -1080,7 +1094,6 @@
 #else /* CTAGS */
          /* Ctags options. */
        case 'B': searchar = '?';       break;
-       case 'u': update = TRUE;        break;
        case 'v': vgrind_style = TRUE;  /*FALLTHRU*/
        case 'x': cxref_style = TRUE;   break;
        case 'w': no_warnings = TRUE;   break;
@@ -1125,6 +1138,9 @@
 
   if (!CTAGS)
     {
+      if (update)
+       update_file (current_arg, argbuffer);
+
       if (streq (tagfile, "-"))
        {
          tagf = stdout;
@@ -1212,7 +1228,7 @@
       exit (GOOD);
     }
 
-  if (update)
+  if (update && CTAGS)
     {
       char cmd[BUFSIZ];
       for (i = 0; i < current_arg; ++i)
@@ -1236,7 +1252,7 @@
   head = NULL;
   fclose (tagf);
 
-  if (update)
+  if (update && CTAGS)
     {
       char cmd[BUFSIZ];
       sprintf (cmd, "sort %s -o %s", tagfile, tagfile);
@@ -1246,6 +1262,119 @@
 }
 
 
+/* Add a string to a buffer.  */
+static void
+add_string (bufferp, bsizep, blenp, str)
+     char **bufferp;
+     int *bsizep, *blenp;
+     char *str;
+{
+  int len = strlen (str);
+  if (*blenp + len > *bsizep)
+    {
+      *bsizep *= 2;
+      *bufferp = xrealloc (*bufferp, *bsizep);
+    }
+  strcat (*bufferp, str);
+  *blenp += len;
+}
+
+/*
+ * Add a string to a buffer.  The string is assumed to be the start of
+ * a new argument; if the buffer is not empty we add a separator.
+ */
+static void
+add_argument (bufferp, bsizep, blenp, str)
+     char **bufferp;
+     int *bsizep, *blenp;
+     char *str;
+{
+  /* Add argument separator.  */
+  if ((*bufferp)[0] != '\0')
+    add_string (bufferp, bsizep, blenp, "\002");
+
+  add_string (bufferp, bsizep, blenp, str);
+}
+
+/* Add a regexp to a buffer.  */
+static void
+add_pattern (bufferp, bsizep, blenp, pat)
+     char **bufferp;
+     int *bsizep, *blenp;
+     pattern *pat;
+{
+  /* We're adding them in reverse order, and it is simplest to
+     recurse.  */
+  if (pat == NULL)
+    return;
+  add_pattern (pat->p_next);
+
+  add_argument (bufferp, bsizep, blenp,
+               (pat->ignore_case ? "-ignore-case-regex=" : "-regex="));
+
+  if (pat->lang != NULL)
+    {
+      add_string (bufferp, bsizep, blenp, "{");
+      add_string (bufferp, bsizep, blenp, pat->lang->name);
+      add_string (bufferp, bsizep, blenp, "}");
+    }
+  add_string (bufferp, bsizep, blenp, "/");
+  add_string (bufferp, bsizep, blenp, pat->regex);
+  add_string (bufferp, bsizep, blenp, "/");
+  if (pat->name_pattern[0] != '\0')
+    {
+      add_string (bufferp, bsizep, blenp, pat->name_pattern);
+      add_string (bufferp, bsizep, blenp, "/");
+    }
+}
+
+/*
+ * Return a string which can later be used to recreate the tags for
+ * this particular file.  This is recorded in the TAGS file for use
+ * when incrementally updating.
+ */
+static char *
+get_state_string ()
+{
+  int size = 50, len = 0;
+  char *buffer = xmalloc (size);
+
+  buffer[0] = '\0';
+
+  if (curlang != NULL)
+    {
+      add_argument (&buffer, &size, &len, "--lang=");
+      add_string (&buffer, &size, &len, curlang->name);
+    }
+
+#ifdef ETAGS_REGEXPS
+  add_pattern (&buffer, &size, &len, p_head);
+#endif
+
+  if (typedefs)
+    add_argument (&buffer, &size, &len, "-t");
+  if (typedefs_or_cplusplus)
+    add_argument (&buffer, &size, &len, "-T");
+  if (constantypedefs)
+    add_argument (&buffer, &size, &len, "-d");
+  if (declarations)
+    add_argument (&buffer, &size, &len, "--declarations");
+  if (globals)
+    add_argument (&buffer, &size, &len, "--globals");
+  if (members)
+    add_argument (&buffer, &size, &len, "--members");
+  if (no_warnings)
+    add_argument (&buffer, &size, &len, "-w");
+  if (cplusplus)
+    add_argument (&buffer, &size, &len, "-C");
+  if (noindentypedefs)
+    add_argument (&buffer, &size, &len, "-I");
+  if (packages_only)
+    add_argument (&buffer, &size, &len, "--packages-only");
+
+  return buffer;
+}
+
 
 /*
  * Return a compressor given the file name.  If EXTPTR is non-zero,
@@ -1495,7 +1624,7 @@
 
   if (!CTAGS)
     {
-      char *filename;
+      char *filename, *state;
 
       if (filename_is_absolute (uncompressed_name))
        {
@@ -1508,8 +1637,11 @@
             to the directory of the tags file. */
          filename = relative_filename (uncompressed_name, tagfiledir);
        }
-      fprintf (tagf, "\f\n%s,%d\n", filename, total_size_of_entries (head));
+      state = get_state_string ();
+      fprintf (tagf, "\f\n%s,%d,%s\n", filename, total_size_of_entries (head),
+              state);
       free (filename);
+      free (state);
       put_entries (head);
       free_tree (head);
       head = NULL;
@@ -1945,6 +2077,106 @@
   return total;
 }
 
+/*
+ * Handle updating a list of files.  We read the existing TAGS file,
+ * writing entries to a new file.  When we see an entry we plan to
+ * update, we skip it.  This is only called for etags-style files.
+ */
+static void
+update_file (arg_count, arguments)
+     int arg_count;
+     argument *arguments;
+{
+  FILE *from, *to;
+  linebuffer buffer;
+  /* We keep a simple state machine.  States are:
+     -1 between records
+     0  copying
+     1  ignoring
+  */
+  int state = -1;
+  char *tmp = "TAGS.tmp";      /* FIXME */
+
+  if (streq (tagfile, "-"))
+    fatal ("can't use `-u' with output to stdout", NULL);
+
+  from = fopen (tagfile, "r");
+  if (from == NULL)
+    pfatal (tagfile);
+  to = fopen (tmp, "w");
+  if (to == NULL)
+    pfatal (tmp);
+
+  initbuffer (&buffer);
+  while (! feof (from))
+    {
+      readline_internal (&buffer, from);
+      if (buffer.len == 1 && buffer.buffer[0] == '\f')
+       {
+         int i, j;
+         /* We've found a barrier.  Read the next line and see what
+            to do.  */
+         readline_internal (&buffer, from);
+         /* Look for the `,' and see what comes after.  */
+         for (i = 0; i < buffer.len; ++i)
+           if (buffer.buffer[i] == ',')
+             break;
+         if (i == buffer.len)
+           fatal ("badly formatted tags file", NULL);
+         if (ISDIGIT (buffer.buffer[i + 1]))
+           {
+             /* Found tags for a file.  */
+             buffer.buffer[i] = '\0';
+
+             state = 0;
+             for (j = 0; j < arg_count; ++j)
+               {
+                 if (arguments[j].arg_type == at_filename
+                     && streq (buffer.buffer, arguments[j].what))
+                   {
+                     /* Found the file, so omit it.  */
+                     state = 1;
+                     break;
+                   }
+               }
+
+             /* If we're copying, then we need to print the header.  */
+             if (state == 0)
+               {
+                 buffer.buffer[i] = ',';
+                 fprintf (to, "\f\n%s\n", buffer.buffer);
+               }
+           }
+         else
+           {
+             /* Found include or something similar, process and move
+                on.  */
+             fprintf (to, "\f\n%s\n", buffer.buffer);
+             state = -1;
+           }
+       }
+      /* An empty line is ok; they typically occur at the end of the
+        file.  */
+      else if (state == -1 && buffer.len > 0)
+       {
+         fatal ("badly formatted tags file", NULL);
+       }
+      else if (state == 0)
+       {
+         /* Copying.  */
+         fprintf (to, "%s\n", buffer.buffer);
+       }
+    }
+
+  fclose (from);
+  fclose (to);
+
+  if (rename (tmp, tagfile) == -1)
+    pfatal ("renaming TAGS.tmp");
+
+  append_to_tagfile = TRUE;
+}
+
 
 /* C extensions. */
 #define C_EXT  0x00fff         /* C extensions */
@@ -5211,6 +5443,7 @@
   p_head->pat = patbuf;
   p_head->name_pattern = savestr (name);
   p_head->error_signaled = FALSE;
+  p_head->ignore_case = ignore_case;
 }
 
 /*
Index: lisp/ChangeLog
===================================================================
RCS file: /cvs/emacs/lisp/ChangeLog,v
retrieving revision 1.3415
diff -u -r1.3415 ChangeLog
--- lisp/ChangeLog      6 Feb 2002 23:07:08 -0000       1.3415
+++ lisp/ChangeLog      7 Feb 2002 17:29:01 -0000
@@ -1,3 +1,14 @@
+2002-02-06  Tom Tromey  <address@hidden>
+
+       * progmodes/etags.el (etags-file-of-tag): Handle new file line
+       format.
+       (etags-etags-program): New variable.
+       (etags-auto-update-tags-file): New function.
+       (etags-tags-table-files): Look for first `,', not last one.
+       (etags-tags-included-tables): Likewise.
+       (etags-recognize-tags-table): Put etags-auto-update-tags-file on
+       after-save-hook.
+
 2002-02-06  Kim F. Storm  <address@hidden>
 
        * help.el (where-is): Report remapped commands.
Index: lisp/progmodes/etags.el
===================================================================
RCS file: /cvs/emacs/lisp/progmodes/etags.el,v
retrieving revision 1.159
diff -u -r1.159 etags.el
--- lisp/progmodes/etags.el     30 Dec 2001 00:52:52 -0000      1.159
+++ lisp/progmodes/etags.el     7 Feb 2002 17:29:25 -0000
@@ -444,7 +444,7 @@
 
 ;; Subroutine of visit-tags-table-buffer.  Search the current tags tables
 ;; for one that has tags for THIS-FILE (or that includes a table that
-;; does).  Return the name of the first table table listing THIS-FILE; if
+;; does).  Return the name of the first table listing THIS-FILE; if
 ;; the table is one included by another table, it is the master table that
 ;; we return.  If CORE-ONLY is non-nil, check only tags tables that are
 ;; already in buffers--don't visit any new files.
@@ -1179,33 +1179,35 @@
 ;; If the current buffer is a valid etags TAGS file, give it local values of
 ;; the tags table format variables, and return non-nil.
 (defun etags-recognize-tags-table ()
-  (and (etags-verify-tags-table)
-       ;; It is annoying to flash messages on the screen briefly,
-       ;; and this message is not useful.  -- rms
-       ;; (message "%s is an `etags' TAGS file" buffer-file-name)
-       (mapc (lambda (elt) (set (make-local-variable (car elt)) (cdr elt)))
-            '((file-of-tag-function . etags-file-of-tag)
-              (tags-table-files-function . etags-tags-table-files)
-              (tags-completion-table-function . etags-tags-completion-table)
-              (snarf-tag-function . etags-snarf-tag)
-              (goto-tag-location-function . etags-goto-tag-location)
-              (find-tag-regexp-search-function . re-search-forward)
-              (find-tag-regexp-tag-order . (tag-re-match-p))
-              (find-tag-regexp-next-line-after-failure-p . t)
-              (find-tag-search-function . search-forward)
-              (find-tag-tag-order . (tag-exact-file-name-match-p
-                                      tag-file-name-match-p
-                                     tag-exact-match-p
-                                     tag-symbol-match-p
-                                     tag-word-match-p
-                                     tag-partial-file-name-match-p
-                                     tag-any-match-p))
-              (find-tag-next-line-after-failure-p . nil)
-              (list-tags-function . etags-list-tags)
-              (tags-apropos-function . etags-tags-apropos)
-              (tags-included-tables-function . etags-tags-included-tables)
-              (verify-tags-table-function . etags-verify-tags-table)
-              ))))
+  (if (etags-verify-tags-table)
+      (progn
+       (add-hook 'after-save-hook 'etags-auto-update-tags-file)
+       ;; It is annoying to flash messages on the screen briefly,
+       ;; and this message is not useful.  -- rms
+       ;; (message "%s is an `etags' TAGS file" buffer-file-name)
+       (mapc (lambda (elt) (set (make-local-variable (car elt)) (cdr elt)))
+             '((file-of-tag-function . etags-file-of-tag)
+               (tags-table-files-function . etags-tags-table-files)
+               (tags-completion-table-function . etags-tags-completion-table)
+               (snarf-tag-function . etags-snarf-tag)
+               (goto-tag-location-function . etags-goto-tag-location)
+               (find-tag-regexp-search-function . re-search-forward)
+               (find-tag-regexp-tag-order . (tag-re-match-p))
+               (find-tag-regexp-next-line-after-failure-p . t)
+               (find-tag-search-function . search-forward)
+               (find-tag-tag-order . (tag-exact-file-name-match-p
+                                      tag-file-name-match-p
+                                      tag-exact-match-p
+                                      tag-symbol-match-p
+                                      tag-word-match-p
+                                      tag-partial-file-name-match-p
+                                      tag-any-match-p))
+               (find-tag-next-line-after-failure-p . nil)
+               (list-tags-function . etags-list-tags)
+               (tags-apropos-function . etags-tags-apropos)
+               (tags-included-tables-function . etags-tags-included-tables)
+               (verify-tags-table-function . etags-verify-tags-table)
+               )))))
 
 ;; Return non-nil iff the current buffer is a valid etags TAGS file.
 (defun etags-verify-tags-table ()
@@ -1214,11 +1216,61 @@
 
 (defun etags-file-of-tag ()
   (save-excursion
-    (re-search-backward "\f\n\\([^\n]+\\),[0-9]*\n")
+    (re-search-backward "\f\n\\([^\n]+\\),[0-9]*\\(,.*\\)?\n")
     (expand-file-name (buffer-substring (match-beginning 1) (match-end 1))
                      (file-truename default-directory))))
 
 
+(defvar etags-etags-program "etags"
+  "*Name of program to run when updating a tags file.")
+
+;; This is called when the buffer is saved.  If the current tags file
+;; has an entry for this file, then we update the tags file.
+(defun etags-auto-update-tags-file ()
+  (let ((stored-name buffer-file-name))
+    (if stored-name
+       (save-excursion
+         (if (or
+              ;; Search loaded tags files first, and if that fails do
+              ;; another pass to load files.
+              (tags-table-including stored-name t)
+              (tags-table-including stored-name nil))
+             (if (eq file-of-tag-function 'etags-file-of-tag)
+                 (let (done beg name-in-tags)
+                   ;; The tags buffer is the current buffer.
+                   ;; Search for our file.
+                   (goto-char (point-min))
+                   (while (and (not done)
+                               (search-forward "\f\n" nil t))
+                     (setq beg (point))
+                     (skip-chars-forward "^,")
+                     (setq name-in-tags (buffer-substring beg (point)))
+                     (if (equal stored-name
+                                (expand-file-name name-in-tags))
+                         (progn
+                           (skip-chars-forward "0-9")
+                           (if (looking-at ",")
+                               (progn
+                                 (forward-char)
+                                 (setq done t)
+                                 (apply 'call-process
+                                        (append
+                                         (list (expand-file-name
+                                                etags-etags-program
+                                                exec-directory)
+                                               nil nil nil "-u"
+                                               ;; Handle case where we
+                                               ;; have a nonstandard
+                                               ;; file name.
+                                               "-o" buffer-file-name)
+                                         (split-string
+                                          (buffer-substring
+                                           (point) (progn (end-of-line)
+                                                          (point)))
+                                          "\002")
+                                         (list name-in-tags)))))))))))))))
+
+
 (defun etags-tags-completion-table ()
   (let ((table (make-vector 511 0)))
     (save-excursion
@@ -1424,8 +1476,8 @@
     (goto-char (point-min))
     (while (search-forward "\f\n" nil t)
       (setq beg (point))
-      (end-of-line)
-      (skip-chars-backward "^," beg)
+      (skip-chars-forward "^,")
+      (forward-char)
       (or (looking-at "include$")
          (setq files (cons (buffer-substring beg (1- (point))) files))))
     (nreverse files)))
@@ -1436,8 +1488,8 @@
     (goto-char (point-min))
     (while (search-forward "\f\n" nil t)
       (setq beg (point))
-      (end-of-line)
-      (skip-chars-backward "^," beg)
+      (skip-chars-forward "^,")
+      (forward-char)
       (if (looking-at "include$")
          ;; Expand in the default-directory of the tags table buffer.
          (setq files (cons (expand-file-name (buffer-substring beg (1- 
(point))))



reply via email to

[Prev in Thread] Current Thread [Next in Thread]