On Fri, Sep 02, 2011 at 10:17:02AM -0700, Matthew Dempsky wrote:
> On Fri, Sep 2, 2011 at 8:55 AM, Sunil Nimmagadda
> <su...@sunilnimmagadda.com> wrote:
> > This diff adds tags support to Mg. I am NOT an emacs user so if this
> > combination is a bit odd then please excuse. It parses the tags file
> > generated by ctags(1) and maintains a tree. M-. on first character of
> > a word jumps to it's definition and M-* jumps back to previous location.
> 
> I'd love to have ctags support in mg.
> 
> > -# $OpenBSD: Makefile,v 1.24 2011/02/02 05:21:36 lum Exp $
> > +# $OpenBSD: Makefile,v 1.23 2011/01/18 17:35:42 lum Exp $
> 
> Can you recreate the diff without these changes?
> 
> 
> > +/*
> > + * Helper function to append a character to a C string.
> > + */
> > +static void
> > +append(char *s, int c)
> > +{
> > + ? ? ? ?size_t l = strlen(s);
> > + ? ? ? s[l++] = c;
> > + ? ? ? s[l] = '\0';
> > +}
> 
> I don't like this.  I don't see any evidence that the length of s here
> will be bounded, and it appears to be used to fill a local fixed-sized
> buffer.
> 
> Having to call strlen() for each character append is suboptimal too.
> Means building up the string takes O(n^2) time.
> 
> (I haven't had a chance to look at the diff in depth yet, but this
> issue stood out to me.)

Being a little less stupid this time. Rewrote the string handling
part, style(9) formatting and refactored pushtag function. 

Mg and emacs unlike nvi don't consider '_' as "inword" i.e., if you
forward-word on "tags_init", the cursor is positioned at '_' instead of
at the end of the word. This causes lookup on identifiers with '_" fail.
The cinfo.c part of following diff fixes this. I am not sure if this
offends mg users. I will take back cinfo.c changes if its wrong to do so.

Index: Makefile
===================================================================
RCS file: /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    3 Sep 2011 11:39:41 -0000
@@ -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 theo.c tags.c
 
 afterinstall:
        ${INSTALL} -d ${DESTDIR}${DOCDIR}/mg
Index: cinfo.c
===================================================================
RCS file: /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     3 Sep 2011 11:39:41 -0000
@@ -43,7 +43,7 @@ const char cinfo[256] = {
        _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W,   /* 0x5X */
        _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W,
        _MG_U | _MG_W, _MG_U | _MG_W, _MG_U | _MG_W, 0,
-       0, 0, 0, 0,
+       0, 0, 0, _MG_W,
        0, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W,               /* 0x6X */
        _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W,
        _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W, _MG_L | _MG_W,
Index: def.h
===================================================================
RCS file: /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       3 Sep 2011 11:39:41 -0000
@@ -514,6 +514,10 @@ int                 backdel(int, int);
 int             space_to_tabstop(int, int);
 int             backtoindent(int, int);
 int             joinline(int, int);
+
+/* tags.c X */
+int             pushtag(int, int);
+int             poptag(int, int);
 
 /* extend.c X */
 int             insert(int, int);
Index: keymap.c
===================================================================
RCS file: /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    3 Sep 2011 11:39:42 -0000
@@ -204,8 +204,11 @@ static PF metapct[] = {
 };
 
 static PF metami[] = {
+       poptag,                 /* * */
+       rescan,                 /* + */
+       rescan,                 /* , */
        negative_argument,      /* - */
-       rescan,                 /* . */
+       pushtag,                /* . */
        rescan,                 /* / */
        digit_argument,         /* 0 */
        digit_argument,         /* 1 */
@@ -231,7 +234,7 @@ static PF metasqf[] = {
        joinline,               /* ^ */
        rescan,                 /* _ */
        rescan,                 /* ` */
-       rescan,                 /* a */
+       rescan,                 /* a */
        backword,               /* b */
        capword,                /* c */
        delfword,               /* d */
@@ -298,7 +301,7 @@ struct KEYMAPE (8 + IMAPEXT) metamap = {
                        '%', '%', metapct, NULL
                },
                {
-                       '-', '>', metami, NULL
+                       '*', '>', metami, NULL
                },
                {
                        '[', 'f', metasqf, (KEYMAP *) &metasqlmap
Index: main.c
===================================================================
RCS file: /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      3 Sep 2011 11:39:42 -0000
@@ -77,11 +77,13 @@ main(int argc, char **argv)
                extern void theo_init(void);
                extern void cmode_init(void);
                extern void dired_init(void);
-
+               extern void tags_init(void);
+               
                dired_init();
                grep_init();
                theo_init();
                cmode_init();
+               tags_init();
        }
 
        if (init_fcn_name &&
@@ -217,6 +219,7 @@ edinit(PF init_fcn)
        wp->w_rflag = WFMODE | WFFULL;          /* Full.                 */
 }
 
+extern void tags_close(void);
 /*
  * Quit command.  If an argument, always quit.  Otherwise confirm if a buffer
  * has been changed and not written out.  Normally bound to "C-X C-C".
@@ -235,8 +238,10 @@ quit(int f, int n)
 #ifdef SYSCLEANUP
                SYSCLEANUP;
 #endif /* SYSCLEANUP */
+               tags_close();
                exit(GOOD);
        }
+
        return (TRUE);
 }
 
Index: tags.c
===================================================================
RCS file: tags.c
diff -N tags.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ tags.c      3 Sep 2011 11:39:42 -0000
@@ -0,0 +1,364 @@
+/* This file is in public domain. */
+
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/tree.h>
+
+#include <err.h>
+#include <regex.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "def.h"
+
+#define MAX_TOKEN 128
+
+struct ctagsnode;
+
+static void              addctag(char *);
+static int               curtoken(int, int, char *);
+static void              re_tag_conv(const char *, char *);
+static int               patsearch(char *);
+static struct ctagnode   *findtag(int, int);
+static int               loadbuffer(char *);
+
+
+/* ctags(1) entries are parsed and maintained in a tree. */
+struct ctagnode {
+       RB_ENTRY(ctagnode) entry;
+       char *tok;
+       char *fname;
+       char *pat;
+};
+
+static int
+ctagcmp(struct ctagnode *s, struct ctagnode *t)
+{
+       return strcmp(s->tok, t->tok);
+}
+
+RB_HEAD(tagtree, ctagnode) tags = RB_INITIALIZER(&tags);
+RB_GENERATE(tagtree, ctagnode, entry, ctagcmp)
+
+
+/* The tag stack used by pushtag and poptag. */
+struct stacknode {
+       SLIST_ENTRY(stacknode) entry;
+       struct line *dotp;
+       char   *bfname;
+       int    doto;
+};
+
+SLIST_HEAD(tagstack, stacknode) shead = SLIST_HEAD_INITIALIZER(shead);
+
+/*
+ * If the current word starting at dot has a ctags entry, then load the
+ * file as a new buffer if it's not already open and match the pattern.
+ * If the pattern matches, push buffer name and dot onto tag stack
+ * before setting the dot to the new match site of the pattern.
+ */
+/*ARGSUSED */
+int
+pushtag(int f, int n)
+{
+       struct ctagnode  *res;
+       struct stacknode *s;
+       char *bfname;
+       struct line *dotp;
+       int doto;
+
+       if ((res = findtag(f, n)) == NULL)
+               return (FALSE);
+
+       dotp = curwp->w_dotp;
+       doto = curwp->w_doto;
+       bfname = curbp->b_fname;
+       
+       if (loadbuffer(res->fname) == FALSE)
+               return (FALSE);
+       
+       if (patsearch(res->pat) == TRUE) {
+               if ((s = malloc(sizeof(struct stacknode))) == NULL)
+                       err(1, NULL);
+               if ((s->bfname = strdup(bfname)) == NULL)
+                           err(1, NULL);
+               s->dotp = dotp;
+               s->doto = doto;
+               SLIST_INSERT_HEAD(&shead, s, entry);
+               return (TRUE);
+       } else {
+               ewprintf("%s: pattern not found", res->tok);
+               return (FALSE);
+       }
+       /* NOTREACHED */
+}
+
+/*
+ * If tag stack is not empty pop stack and jump to recorded
+ * buffer and dot.
+ */
+/* ARGSUSED */
+int
+poptag(int f, int n)
+{
+       struct buffer *bufp;
+       struct stacknode *s;
+       if (SLIST_EMPTY(&shead)) {
+               ewprintf("tag stack empty.");
+               return (FALSE);
+       }
+       s = SLIST_FIRST(&shead);
+       SLIST_REMOVE_HEAD(&shead, entry);
+       bufp = findbuffer(s->bfname);
+       curbp = bufp;
+       showbuffer(bufp, curwp, WFFRAME | WFFULL);
+       curwp->w_dotp = s->dotp;
+       curwp->w_doto = s->doto;
+       free(s->bfname);
+       free(s);
+       return (TRUE);
+}
+
+/*
+ * Parse the tags file if it's present and construct the tags tree.
+ */
+void
+tags_init()
+{
+       char line[NLINE];
+       FILE *tagfile;
+       if (!(tagfile = fopen("tags", "r"))) 
+               return;
+       while (fgets(line, sizeof(line), tagfile))
+               addctag(line);
+       fclose(tagfile);
+       return;
+}
+
+/*
+ * Cleanup and destroy tree and stack.
+ */
+void
+tags_close()
+{
+       struct ctagnode *var, *nxt;
+       struct stacknode *s;
+       
+       for (var = RB_MIN(tagtree, &tags); var !=NULL; var = nxt) {
+               nxt = RB_NEXT(tagtree, &tags, var);
+               RB_REMOVE(tagtree, &tags, var);
+               free(var->tok);
+               free(var->fname);
+               free(var->pat);
+               free(var);
+       }
+       
+       while (!SLIST_EMPTY(&shead)) {
+               s = SLIST_FIRST(&shead);
+               SLIST_REMOVE_HEAD(&shead, entry);
+               free(s->bfname);
+               free(s);
+       }
+}
+
+/*
+ * tags line is of the format "<token>\t<filename>\t<pattern>\n".
+ */
+void
+addctag(char *ctagline)
+{
+       struct ctagnode *t;
+       char *token;
+       char *tok   = NULL;
+       char *fname = NULL;
+       char *pat   = NULL;
+       
+       token = strsep(&ctagline, "\t");
+       if (token == NULL)
+               goto cleanup;
+       if ((tok = strdup(token)) == NULL)
+               err(1, NULL);
+       token = strsep(&ctagline, "\t");
+       if (token == NULL)
+               goto cleanup;
+       if ((fname = strdup(token)) == NULL)
+               err(1, NULL);
+       token = strsep(&ctagline, "\n");
+       if (token == NULL)
+               goto cleanup;
+       if ((pat = strdup(token)) == NULL)
+               err(1, NULL);
+       if ((t = malloc(sizeof(struct ctagnode))) == NULL)
+               err(1, NULL);
+       t->tok = tok;
+       t->fname = fname;
+       t->pat = pat;
+       RB_INSERT(tagtree, &tags, t);
+       return;
+cleanup:
+       free(tok);
+       free(fname);
+       free(pat);
+}
+
+/*
+ * Borrowed from ex.
+ * Convert ctags re to POSIX 1003.2 RE
+ *
+ */
+void
+re_tag_conv(const char *p, char *t)
+{
+       int lastdollar;
+       size_t len;
+       
+       len = strlen(p);
+       
+       /* If the last character is '/' or '?', we just strip it. */
+       if (len > 0 && (p[len - 1] == '/' || p[len - 1] == '?'))
+               --len;
+               
+       /* If the next to last character is a '$',  it's magic. */
+       if (len > 0 && p[len - 1] == '$') {
+               --len;
+               lastdollar = 1;
+       } else
+               lastdollar = 0;
+               
+       /* If the first character is '/' or '?', we just strip it. */
+       if (len > 0 && (p[0] == '/' || p[0] == '?')) {
+               ++p;
+               --len;
+       }
+       
+       /* If the first or second character is '^', it' magic. */
+       if (p[0] == '^') {
+               *t++ = *p++;
+               --len;
+       }
+       
+       /*
+        * Escape every other magic character we can find, meanwhile
+        * stripping the backslashes ctags inserts when escaping the
+        * search delimiter characters.
+        */
+       for (; len > 0; --len) {
+               if (p[0] == '\\' && (p[1] == '/' || p[1] == '?')) {
+                       ++p;
+                       --len;
+               } else if (strchr("^.[]$*", p[0]))
+                       *t++ = '\\';
+               *t++ = *p++;
+               if (len == 0)
+                       break;
+       }
+       if (lastdollar)
+               *t++ = '$';
+       
+       *t = '\0';
+}
+
+#define PAT_MATCH 1
+static regex_t    re_buff;
+static regmatch_t re_match[PAT_MATCH];
+
+/*
+ * Convert ctags pattern to POSIX 1003.2 RE format and iterate 
+ * through current buffer to match RE. If found move dot to 
+ * matched line.
+ * TODO: Cache searched tokens location.
+ */
+int
+patsearch(char *pat)
+{
+       struct line *clp;
+       char re_pat[NLINE];
+       int error;      
+
+       clp = lforw(curbp->b_headp);
+
+       re_tag_conv(pat, re_pat);
+       if (regcomp(&re_buff, re_pat, REG_BASIC) != 0) {
+               return (FALSE);
+       }       
+
+       while (clp != curbp->b_headp) {
+               re_match[0].rm_so = 0;
+               re_match[0].rm_eo = llength(clp);
+               error = regexec(&re_buff, ltext(clp), PAT_MATCH, 
+                   re_match, REG_STARTEND);
+               if (error != 0) {
+                       clp = lforw(clp);
+               } else {
+                       curwp->w_doto = 0;
+                       curwp->w_dotp = clp;
+                       return (TRUE);
+               }
+       }
+       return (FALSE);
+}
+
+/*
+ * Extract the word at dot without changing dot position.
+ */
+int
+curtoken(int f, int n, char *token)
+{
+       struct line *dotp;
+       int doto, size, r;
+       
+       dotp = curwp->w_dotp;
+       doto = curwp->w_doto;
+       
+       if ((r = forwword(f, n)) == FALSE) {
+               goto cleanup;
+       }
+
+       size = curwp->w_doto - doto;
+       if (size >= MAX_TOKEN || ltext(curwp->w_dotp) == NULL) {
+               r = FALSE;
+               goto cleanup;
+       }
+       strncpy(token, ltext(curwp->w_dotp) + doto, size);
+       token[size] = '\0';
+       r = TRUE;
+       
+cleanup:
+       curwp->w_dotp = dotp;
+       curwp->w_doto = doto;
+       return (r);
+}
+
+struct ctagnode *
+findtag(int f, int n)
+{
+       struct ctagnode t, *res;
+       char tok[MAX_TOKEN];
+
+       if (!curtoken(f, n, tok))
+               return (NULL);
+       t.tok = tok;    
+       if ((res = RB_FIND(tagtree, &tags, &t)) == NULL) {
+               ewprintf("%s: tag not found", tok);
+       }
+       return res;
+}
+
+int
+loadbuffer(char *bfname)
+{
+       struct buffer *bufp;
+       char *adjf;
+
+       adjf = adjustname(bfname, TRUE);
+       bufp = findbuffer(adjf);
+       curbp = bufp;
+       showbuffer(bufp, curwp, WFFULL);
+       if (bufp->b_fname[0] == '\0') {
+               if (readin(adjf) != TRUE) {
+                       killbuffer(bufp);
+                       return (FALSE);
+               }
+       }
+       return (TRUE);
+}

Reply via email to