Manpage changes as suggested by jmc@, thanks.

Index: Makefile
===================================================================
RCS file: /home/sunil/cvs/src/usr.bin/mg/Makefile,v
retrieving revision 1.24
diff -u -p -r1.24 Makefile
--- Makefile    2 Feb 2011 05:21:36 -0000       1.24
+++ Makefile    14 Nov 2011 16:24:08 -0000
@@ -2,8 +2,8 @@
 
 PROG=  mg
 
-LDADD+=        -lcurses
-DPADD+=        ${LIBCURSES}
+LDADD+=        -lcurses -lutil
+DPADD+=        ${LIBCURSES} ${LIBUTIL}
 
 # (Common) compile-time options:
 #
@@ -24,7 +24,7 @@ SRCS= autoexec.c basic.c buffer.c cinfo.
 #
 # More or less standalone extensions.
 #
-SRCS+= cmode.c dired.c grep.c theo.c
+SRCS+= cmode.c dired.c grep.c tags.c theo.c
 
 afterinstall:
        ${INSTALL} -d ${DESTDIR}${DOCDIR}/mg
Index: cinfo.c
===================================================================
RCS file: /home/sunil/cvs/src/usr.bin/mg/cinfo.c,v
retrieving revision 1.15
diff -u -p -r1.15 cinfo.c
--- cinfo.c     13 Dec 2005 06:01:27 -0000      1.15
+++ cinfo.c     11 Nov 2011 16:50:53 -0000
@@ -19,7 +19,12 @@
  * character set, and lets me ask some questions that the
  * standard "ctype" macros cannot ask.
  */
-const char cinfo[256] = {
+/*
+ * Due to incompatible behaviour between "standard" emacs and
+ * ctags word traversing, '_' character's value is changed on 
+ * the fly in ctags mode, hence non-const.
+ */
+char cinfo[256] = {
        _MG_C, _MG_C, _MG_C, _MG_C,                                   /* 0x0X */
        _MG_C, _MG_C, _MG_C, _MG_C,
        _MG_C, _MG_C, _MG_C, _MG_C,
Index: def.h
===================================================================
RCS file: /home/sunil/cvs/src/usr.bin/mg/def.h,v
retrieving revision 1.116
diff -u -p -r1.116 def.h
--- def.h       23 Jan 2011 00:45:03 -0000      1.116
+++ def.h       18 Nov 2011 03:30:55 -0000
@@ -515,6 +515,11 @@ int                 space_to_tabstop(int, int);
 int             backtoindent(int, int);
 int             joinline(int, int);
 
+/* tags.c X */
+int             findtag(int, int);
+int             poptag(int, int);
+int             tagsvisit(int, int);
+
 /* extend.c X */
 int             insert(int, int);
 int             bindtokey(int, int);
@@ -674,7 +679,7 @@ extern int           ttbot;
 extern int              tthue;
 extern int              defb_nmodes;
 extern int              defb_flag;
-extern const char       cinfo[];
+extern char             cinfo[];
 extern char            *keystrings[];
 extern char             pat[NPAT];
 #ifndef NO_DPROMPT
Index: funmap.c
===================================================================
RCS file: /home/sunil/cvs/src/usr.bin/mg/funmap.c,v
retrieving revision 1.34
diff -u -p -r1.34 funmap.c
--- funmap.c    18 Jan 2011 16:25:40 -0000      1.34
+++ funmap.c    18 Nov 2011 06:24:15 -0000
@@ -135,7 +135,10 @@ static struct funmap functnames[] = {
        {prefixregion, "prefix-region",},
        {backline, "previous-line",},
        {prevwind, "previous-window",},
+       {poptag, "pop-tag-mark",},
        {spawncli, "push-shell",},
+       {findtag, "find-tag",},
+       {tagsvisit, "visit-tags-table",},
        {showcwdir, "pwd",},
        {queryrepl, "query-replace",},
 #ifdef REGEX
Index: keymap.c
===================================================================
RCS file: /home/sunil/cvs/src/usr.bin/mg/keymap.c,v
retrieving revision 1.45
diff -u -p -r1.45 keymap.c
--- keymap.c    18 Jan 2011 16:25:40 -0000      1.45
+++ keymap.c    11 Nov 2011 16:50:53 -0000
@@ -204,8 +204,11 @@ static PF metapct[] = {
 };
 
 static PF metami[] = {
+       poptag,                 /* * */
+       rescan,                 /* + */
+       rescan,                 /* , */
        negative_argument,      /* - */
-       rescan,                 /* . */
+       findtag,                /* . */
        rescan,                 /* / */
        digit_argument,         /* 0 */
        digit_argument,         /* 1 */
@@ -298,7 +301,7 @@ struct KEYMAPE (8 + IMAPEXT) metamap = {
                        '%', '%', metapct, NULL
                },
                {
-                       '-', '>', metami, NULL
+                       '*', '>', metami, NULL
                },
                {
                        '[', 'f', metasqf, (KEYMAP *) &metasqlmap
Index: main.c
===================================================================
RCS file: /home/sunil/cvs/src/usr.bin/mg/main.c,v
retrieving revision 1.61
diff -u -p -r1.61 main.c
--- main.c      4 Jun 2009 02:23:37 -0000       1.61
+++ main.c      18 Nov 2011 06:58:07 -0000
@@ -30,6 +30,7 @@ static void    edinit(PF);
 static __dead void usage(void);
 
 extern char    *__progname;
+extern void     closetags(void);
 
 static __dead void
 usage()
@@ -235,6 +236,7 @@ quit(int f, int n)
 #ifdef SYSCLEANUP
                SYSCLEANUP;
 #endif /* SYSCLEANUP */
+               closetags();
                exit(GOOD);
        }
        return (TRUE);
Index: mg.1
===================================================================
RCS file: /home/sunil/cvs/src/usr.bin/mg/mg.1,v
retrieving revision 1.55
diff -u -p -r1.55 mg.1
--- mg.1        2 Sep 2011 02:37:52 -0000       1.55
+++ mg.1        27 Nov 2011 17:21:14 -0000
@@ -73,6 +73,12 @@ the mark at the point of deletion.
 Note: The point and mark are window-specific in
 .Nm ,
 not buffer-specific, as in other emacs flavours.
+.Sh TAGS
+.Nm
+supports tags file created by
+.Xr ctags 1 ,
+allowing user to quickly locate various object definitions.
+Note that emacs uses etags, not ctags.
 .Sh DEFAULT KEY BINDINGS
 Normal editing commands are very similar to GNU Emacs.
 In the following examples, C-x means Control-x, and M-x means Meta-x,
@@ -212,6 +218,10 @@ suspend-emacs
 scroll-other-window
 .It M-SPC
 just-one-space
+.It M-.
+find-tag
+.It M-*
+pop-tag-mark
 .It M-%
 query-replace
 .It M-<
@@ -461,6 +471,8 @@ If the kill fails, or is aborted, revert
 .It find-file-other-window
 Opens the specified file in a second buffer.
 Splits the current window if necessary.
+.It find-tag
+Jump to definition of tag at dot.
 .It forward-char
 Move cursor forwards (or backwards, if
 .Va n
@@ -606,6 +618,8 @@ This command makes the previous (up the 
 current window.
 There are no errors, although the command does not do
 a lot if there is only 1 window.
+.It pop-tag-mark
+Return to position where find-tag was previously invoked.
 .It push-shell
 Suspend
 .Nm
@@ -776,6 +790,8 @@ upper case.
 .It upcase-word
 Move the cursor forward by the specified number of words.
 As it moves, convert any characters to upper case.
+.It visit-tags-table
+Record name of the tags file to be used for subsequent find-tag.
 .It what-cursor-position
 Display a bunch of useful information about the current location of
 dot.
@@ -835,6 +851,7 @@ terminal-specific startup file
 concise tutorial
 .El
 .Sh SEE ALSO
+.Xr ctags 1 ,
 .Xr vi 1
 .Sh CAVEATS
 Since it is written completely in C, there is currently no
Index: tags.c
===================================================================
RCS file: tags.c
diff -N tags.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ tags.c      24 Nov 2011 05:50:13 -0000
@@ -0,0 +1,533 @@
+/*
+ * Copyright (c) 2011 Sunil Nimmagadda <su...@sunilnimmagadda.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/tree.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+#include <util.h>
+
+#include "def.h"
+
+struct ctag;
+
+static int               addctag(char *);
+static int               atbow(void);
+void                     closetags(void);
+static int               ctagcmp(struct ctag *, struct ctag *);
+static int               curtoken(int, int, char *);
+static int               loadbuffer(char *);
+static int               loadtags(const char *);
+static int               pushtag(char *);
+static int               searchpat(char *);
+static struct ctag       *searchtag(char *);
+static char              *strip(char *, size_t);
+static void              unloadtags(void);
+
+#define MAX_TOKEN 64
+#define DEFAULTFN "tags"
+
+char *tagsfn = NULL;
+int  loaded  = FALSE;
+
+/* ctags(1) entries are parsed and maintained in a tree. */
+struct ctag {
+       RB_ENTRY(ctag) entry;
+       char *tag;
+       char *fname;
+       char *pat;
+};
+RB_HEAD(tagtree, ctag) tags = RB_INITIALIZER(&tags);
+RB_GENERATE(tagtree, ctag, entry, ctagcmp);
+
+struct tagpos {
+       SLIST_ENTRY(tagpos) entry;
+       int    doto;
+       int    dotline;
+       char   *bname;
+};
+SLIST_HEAD(tagstack, tagpos) shead = SLIST_HEAD_INITIALIZER(shead);
+
+int
+ctagcmp(struct ctag *s, struct ctag *t)
+{
+       return strcmp(s->tag, t->tag);
+}
+
+/*
+ * Record the filename that contain tags to be used while loading them
+ * on first use. If a filename is already recorded, ask user to retain
+ * already loaded tags (if any) and unload them if user chooses not to.
+ */
+/* ARGSUSED */
+int
+tagsvisit(int f, int n)
+{
+       char fname[NFILEN], *bufp, *temp;
+       struct stat sb;
+       
+       if (getbufcwd(fname, sizeof(fname)) == FALSE)
+               fname[0] = '\0';
+       
+       if (strlcat(fname, DEFAULTFN, sizeof(fname)) >= sizeof(fname)) {
+               ewprintf("Filename too long");
+               return (FALSE);
+       }
+       
+       bufp = eread("visit tags table (default %s): ", fname,
+           NFILEN, EFFILE | EFCR | EFNEW | EFDEF, DEFAULTFN);
+
+       if (stat(bufp, &sb) == -1) {
+               ewprintf("stat: %s", strerror(errno));
+               return (FALSE);
+       } else if (S_ISREG(sb.st_mode) == 0) {
+               ewprintf("Not a regular file");
+               return (FALSE);
+       } else if (access(bufp, R_OK) == -1) {
+               ewprintf("Cannot access file %s", bufp);
+               return (FALSE);
+       }
+       
+       if (tagsfn == NULL) {
+               if (bufp == NULL)
+                       return (ABORT);
+               else if (bufp[0] == '\0') {
+                       if ((tagsfn = strdup(fname)) == NULL) {
+                               ewprintf("Out of memory");
+                               return (FALSE);
+                       }
+               } else {
+                       /* bufp points to local variable, so duplicate. */
+                       if ((tagsfn = strdup(bufp)) == NULL) {
+                               ewprintf("Out of memory");
+                               return (FALSE);
+                       }
+               }
+       } else {
+               if ((temp = strdup(bufp)) == NULL) {
+                       ewprintf("Out of memory");
+                       return (FALSE);
+               }
+               free(tagsfn);
+               tagsfn = temp;
+               if (eyorn("Keep current list of tags table also") == FALSE) {
+                       ewprintf("Starting a new list of tags table");
+                       unloadtags();
+               }
+               loaded = FALSE;
+       }
+       return (TRUE);
+}
+
+/*
+ * Ask user for a tag while treating word at dot as default. Visit tags
+ * file if not yet done, load tags and jump to definition of the tag.
+ */
+int
+findtag(int f, int n)
+{
+       char utok[MAX_TOKEN], dtok[MAX_TOKEN];
+       char *tok, *bufp;
+       int  ret;
+
+       if (curtoken(f, n, dtok) == FALSE)
+               return (FALSE);
+       
+       bufp = eread("Find tag (default %s) ", utok, MAX_TOKEN,
+           EFNUL | EFNEW, dtok);
+
+       if (bufp == NULL)
+               return (ABORT);
+       else if (bufp[0] == '\0')
+               tok = dtok;
+       else
+               tok = utok;
+       
+       if (tok[0] == '\0') {
+               ewprintf("There is no default tag");
+               return (FALSE);
+       }
+       
+       if (tagsfn == NULL)
+               if ((ret = tagsvisit(f, n)) != TRUE)
+                       return (ret);
+       if (!loaded) {
+               if (loadtags(tagsfn) == FALSE) {
+                       free(tagsfn);
+                       tagsfn = NULL;
+                       return (FALSE);
+               }
+               loaded = TRUE;
+       }
+       return pushtag(tok);
+}
+
+/*
+ * Free tags tree.
+ */
+void
+unloadtags(void)
+{
+       struct ctag *var, *nxt;
+       
+       for (var = RB_MIN(tagtree, &tags); var != NULL; var = nxt) {
+               nxt = RB_NEXT(tagtree, &tags, var);
+               RB_REMOVE(tagtree, &tags, var);
+               /* line parsed with fparseln needs to be freed */
+               free(var->tag);
+               free(var);
+       }
+}
+
+/*
+ * Lookup tag passed in tree and if found, push current location and 
+ * buffername onto stack, load the file with tag definition into a new
+ * buffer and position dot at the pattern.
+ */
+/*ARGSUSED */
+int
+pushtag(char *tok)
+{
+       struct ctag *res;
+       struct tagpos *s;
+       char *bname;
+       int doto, dotline;
+       
+       if ((res = searchtag(tok)) == NULL)
+               return (FALSE);
+               
+       doto = curwp->w_doto;
+       dotline = curwp->w_dotline;
+       bname = curbp->b_bname;
+
+       if (loadbuffer(res->fname) == FALSE)
+               return (FALSE);
+       
+       if (searchpat(res->pat) == TRUE) {
+               if ((s = malloc(sizeof(struct tagpos))) == NULL) {
+                       ewprintf("Out of memory");
+                       return (FALSE);
+               }
+               if ((s->bname = strdup(bname)) == NULL) {
+                           ewprintf("Out of memory");
+                           return (FALSE);
+               }
+               s->doto = doto;
+               s->dotline = dotline;
+               SLIST_INSERT_HEAD(&shead, s, entry);
+               return (TRUE);
+       } else {
+               ewprintf("%s: pattern not found", res->tag);
+               return (FALSE);
+       }
+       /* NOTREACHED */
+       return (FALSE);
+}
+
+/*
+ * If tag stack is not empty pop stack and jump to recorded buffer, dot.
+ */
+/* ARGSUSED */
+int
+poptag(int f, int n)
+{
+       struct line *dotp;
+       struct tagpos *s;
+       
+       if (SLIST_EMPTY(&shead)) {
+               ewprintf("No previous location for find-tag invocation");
+               return (FALSE);
+       }
+       s = SLIST_FIRST(&shead);
+       SLIST_REMOVE_HEAD(&shead, entry);
+       if (loadbuffer(s->bname) == FALSE)
+               return (FALSE);
+       curwp->w_dotline = s->dotline;
+       curwp->w_doto = s->doto;
+       
+       /* storing of dotp in tagpos wouldn't work out in cases when
+        * that buffer is killed by user(dangling pointer). Explicitly
+        * traverse till dotline for correct handling. 
+        */
+       dotp = curwp->w_bufp->b_headp;
+       while (s->dotline--)
+               dotp = dotp->l_fp;
+       
+       curwp->w_dotp = dotp;
+       free(s->bname);
+       free(s);
+       return (TRUE);
+}
+
+/*
+ * Parse the tags file and construct the tags tree. Remove escape 
+ * characters while parsing the file.
+ */
+int
+loadtags(const char *fn)
+{
+       char *l;
+       FILE *fd;
+       
+       if ((fd = fopen(fn, "r")) == NULL) {
+               ewprintf("Unable to open tags file: %s", fn);
+               return (FALSE);
+       }
+       while ((l = fparseln(fd, NULL, NULL, "\\\\\0",
+           FPARSELN_UNESCCONT | FPARSELN_UNESCREST)) != NULL) {
+               if (addctag(l) == FALSE) {
+                       fclose(fd);
+                       return (FALSE);
+               }
+       }
+       fclose(fd);
+       return (TRUE);
+}
+
+/*
+ * Cleanup and destroy tree and stack.
+ */
+void
+closetags(void)
+{
+       struct tagpos *s;       
+       
+       while (!SLIST_EMPTY(&shead)) {
+               s = SLIST_FIRST(&shead);
+               SLIST_REMOVE_HEAD(&shead, entry);
+               free(s->bname);
+               free(s);
+       }
+       unloadtags();
+       free(tagsfn);
+}
+
+/*
+ * Strip away any special characters in pattern.
+ * The pattern in ctags isn't a true regular expression. Its of the form
+ * /^xxx$/ or ?^xxx$? and in some cases the "$" would be missing. Strip 
+ * the leading and trailing special characters so the pattern matching
+ * would be a simple string compare. Escape character is taken care by 
+ * fparseln.
+ */
+char *
+strip(char *s, size_t len)
+{
+       /* first strip trailing special chars */        
+       s[len - 1] = '\0';
+       if (s[len - 2] == '$')
+               s[len - 2] = '\0';
+       
+       /* then strip leading special chars */
+       s++;
+       if (*s == '^')
+               s++;
+       
+       return s;
+}
+
+/*
+ * tags line is of the format "<tag>\t<filename>\t<pattern>". Split them
+ * by replacing '\t' with '\0'. This wouldn't alter the size of malloc'ed
+ * l, and can be freed during cleanup.
+ */
+int
+addctag(char *l)
+{
+       struct ctag *t;
+       
+       if ((t = malloc(sizeof(struct ctag))) == NULL) {
+               ewprintf("Out of memory");
+               return (FALSE);
+       }
+       t->tag = l;
+       if ((l = strchr(l, '\t')) == NULL)
+               goto cleanup;
+       *l++ = '\0';
+       t->fname = l;
+       if ((l = strchr(l, '\t')) == NULL)
+               goto cleanup;
+       *l++ = '\0';
+       if (*l == '\0')
+               goto cleanup;
+       t->pat = strip(l, strlen(l));
+       RB_INSERT(tagtree, &tags, t);
+       return (TRUE);
+cleanup:
+       free(t);
+       free(l);
+       return (TRUE);
+}
+
+/*
+ * Search through each line of buffer for pattern.
+ */
+int
+searchpat(char *pat)
+{
+       struct line *lp;
+       int dotline;
+       size_t plen;
+
+       plen = strlen(pat);
+       dotline = 1;
+       lp = lforw(curbp->b_headp);
+       while (lp != curbp->b_headp) {
+               if (ltext(lp) != NULL && plen <= llength(lp) &&
+                   (strncmp(pat, ltext(lp), plen) == 0)) {
+                       curwp->w_doto = 0;
+                       curwp->w_dotp = lp;
+                       curwp->w_dotline = dotline;
+                       return (TRUE);
+               } else {
+                       lp = lforw(lp);
+                       dotline++;
+               }
+       }
+       return (FALSE);
+}
+
+/*
+ * Return TRUE if dot is at beginning of a word or at beginning 
+ * of line, else FALSE.
+ */
+int
+atbow(void)
+{
+       if (curwp->w_doto == 0)
+               return (TRUE);
+       if (ISWORD(curwp->w_dotp->l_text[curwp->w_doto]) &&
+           !ISWORD(curwp->w_dotp->l_text[curwp->w_doto - 1]))
+               return (TRUE);
+       return (FALSE);
+}
+
+/*
+ * Extract the word at dot without changing dot position.
+ */
+int
+curtoken(int f, int n, char *token)
+{
+       struct line *odotp;
+       int odoto, tdoto, odotline, size, r;
+       char c;
+       
+       /* Underscore character is to be treated as "inword" while
+        * processing tokens unlike mg's default word traversal. Save
+        * and restore it's cinfo value so that tag matching works for
+        * identifier with underscore.
+        */
+       c = cinfo['_'];
+       cinfo['_'] = _MG_W;
+       
+       odotp = curwp->w_dotp;
+       odoto = curwp->w_doto;
+       odotline = curwp->w_dotline;
+       
+       /* Move backword unless we are at the beginning of a word or at
+        * beginning of line.
+        */
+       if (!atbow())
+               if ((r = backword(f, n)) == FALSE)
+                       goto cleanup;
+               
+       tdoto = curwp->w_doto;
+
+       if ((r = forwword(f, n)) == FALSE)
+               goto cleanup;
+       
+       /* strip away leading whitespace if any like emacs. */
+       while (ltext(curwp->w_dotp) && 
+           isspace(curwp->w_dotp->l_text[tdoto]))
+               tdoto++;
+
+       size = curwp->w_doto - tdoto;
+       if (size <= 0 || size >= MAX_TOKEN || 
+           ltext(curwp->w_dotp) == NULL) {
+               r = FALSE;
+               goto cleanup;
+       }    
+       strncpy(token, ltext(curwp->w_dotp) + tdoto, size);
+       token[size] = '\0';
+       r = TRUE;
+       
+cleanup:
+       cinfo['_'] = c;
+       curwp->w_dotp = odotp;
+       curwp->w_doto = odoto;
+       curwp->w_dotline = odotline;
+       return (r);
+}
+
+/*
+ * Search tagstree for a given token.
+ */
+struct ctag *
+searchtag(char *tok)
+{
+       struct ctag t, *res;
+
+       t.tag = tok;
+       if ((res = RB_FIND(tagtree, &tags, &t)) == NULL) {
+               ewprintf("No tag containing %s", tok);
+               return (NULL);
+       }
+       return res;
+}
+
+/*
+ * This is equivalent to filevisit from file.c.
+ * Look around to see if we can find the file in another buffer; if we
+ * can't find it, create a new buffer, read in the text, and switch to
+ * the new buffer. *scratch*, *grep*, *compile* needs to be handled 
+ * differently from other buffers which have "filenames".
+ */
+int
+loadbuffer(char *bname)
+{
+       struct buffer *bufp;
+       char *adjf;
+
+       /* check for special buffers which begin with '*' */
+       if (bname[0] == '*') {
+               if ((bufp = bfind(bname, FALSE)) != NULL) {
+                       curbp = bufp;
+                       return (showbuffer(bufp, curwp, WFFULL));
+               } else {
+                       return (FALSE);
+               }
+       } else {        
+               if ((adjf = adjustname(bname, TRUE)) == NULL)
+                       return (FALSE);
+               if ((bufp = findbuffer(adjf)) == NULL)
+                       return (FALSE);
+       }
+       curbp = bufp;
+       if (showbuffer(bufp, curwp, WFFULL) != TRUE)
+               return (FALSE);
+       if (bufp->b_fname[0] == '\0') {
+               if (readin(adjf) != TRUE) {
+                       killbuffer(bufp);
+                       return (FALSE);
+               }
+       }
+       return (TRUE);
+}

Reply via email to