On Fri, Sep 02, 2011 at 10:17:02AM -0700, Matthew Dempsky wrote:
> On Fri, Sep 2, 2011 at 8:55 AM, Sunil Nimmagadda
> <[email protected]> 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);
+}