Hello,
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.
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 2 Sep 2011 15:00:39 -0000
@@ -1,4 +1,4 @@
-# $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 $
PROG= mg
@@ -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: buffer.c
===================================================================
RCS file: /cvs/src/usr.bin/mg/buffer.c,v
retrieving revision 1.77
diff -u -p -r1.77 buffer.c
--- buffer.c 23 Jan 2011 00:45:03 -0000 1.77
+++ buffer.c 2 Sep 2011 15:00:39 -0000
@@ -1,4 +1,4 @@
-/* $OpenBSD: buffer.c,v 1.77 2011/01/23 00:45:03 kjell Exp $ */
+/* $OpenBSD: buffer.c,v 1.76 2011/01/21 19:10:13 kjell Exp $ */
/* This file is in the public domain. */
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 2 Sep 2011 15:00:39 -0000
@@ -1,4 +1,4 @@
-/* $OpenBSD: def.h,v 1.116 2011/01/23 00:45:03 kjell Exp $ */
+/* $OpenBSD: def.h,v 1.115 2011/01/18 16:25:40 kjell Exp $ */
/* This file is in the public domain. */
@@ -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 2 Sep 2011 15:00:40 -0000
@@ -1,4 +1,4 @@
-/* $OpenBSD: keymap.c,v 1.45 2011/01/18 16:25:40 kjell Exp $ */
+/* $OpenBSD: keymap.c,v 1.44 2011/01/17 03:12:06 kjell Exp $ */
/* This file is in the public domain. */
@@ -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 2 Sep 2011 15:00:40 -0000
@@ -1,4 +1,4 @@
-/* $OpenBSD: main.c,v 1.61 2009/06/04 02:23:37 kjell Exp $ */
+/* $OpenBSD: main.c,v 1.60 2008/06/13 18:51:02 kjell Exp $ */
/* This file is in the public domain. */
@@ -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,11 @@ quit(int f, int n)
#ifdef SYSCLEANUP
SYSCLEANUP;
#endif /* SYSCLEANUP */
+ tags_close();
exit(GOOD);
}
+
+ tags_close();
return (TRUE);
}
Index: tags.c
===================================================================
RCS file: tags.c
diff -N tags.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ tags.c 2 Sep 2011 15:00:40 -0000
@@ -0,0 +1,350 @@
+/* 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
+
+/*
+ * 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;
+ char *bfname;
+ struct line *dotp;
+ int doto;
+};
+
+SLIST_HEAD(tagstack, stacknode) shead = SLIST_HEAD_INITIALIZER(shead);
+
+/*
+ * tags line is of the format "<token>\t<filename>\t<pattern>\n".
+ */
+static void
+addctag(char *ctagline)
+{
+ char *token;
+ struct ctagnode *t;
+ 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);
+}
+
+/*
+ * 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);
+ }
+}
+
+/*
+ * 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';
+}
+
+RSIZE countfword();
+/*
+ * Extract the word at dot without changing dot position.
+ * TODO: lgetc returns when it encounters "_". If a function is named
+ * for example "tags_init", this function can only extact "tags" and
+ * tag lookup would fail.
+ */
+static int
+curtoken(int f, int n, char *token)
+{
+ struct line *dotp;
+ int doto;
+ int c, size;
+ dotp = curwp->w_dotp;
+ doto = curwp->w_doto;
+ if (n < 0)
+ return (FALSE);
+ while (n--) {
+ while (inword() == FALSE) {
+ if (forwchar(FFRAND, 1) == FALSE)
+ return (TRUE);
+ }
+ size = countfword();
+ while (inword() != FALSE) {
+ c = lgetc(curwp->w_dotp, curwp->w_doto);
+ if(size < MAX_TOKEN - 1)
+ append(token, c);
+ if (forwchar(FFRAND, 1) == FALSE)
+ break;
+ }
+ }
+ curwp->w_dotp = dotp;
+ curwp->w_doto = doto;
+ return (TRUE);
+}
+
+/*
+ * Borrowed from ex.
+ * Convert ctags re to POSIX 1003.2 RE
+ *
+ */
+static void
+re_tag_conv(const char *p, char *t)
+{
+ int lastdollar;
+ int 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 pattern to reduce linear lookup.
+ */
+static int
+patsearch(char *pat)
+{
+ int error;
+ char re_pat[NLINE];
+ struct line *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);
+}
+
+/*
+ * 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 previous buffer name and dot onto tag
+ * stack before setting the dot to the match site of the pattern.
+ */
+/*ARGSUSED */
+int
+pushtag(int f, int n)
+{
+ char *adjf;
+ struct buffer *bufp;
+ struct ctagnode t, *res;
+ struct stacknode *s;
+ char *bfname;
+ struct line *odotp;
+ int odoto;
+ char tok[MAX_TOKEN];
+ tok[0] = '\0';
+ if(!curtoken(f, n, tok))
+ return (FALSE);
+ t.tok = tok;
+ if((res = RB_FIND(tagtree, &tags, &t)) == NULL) {
+ ewprintf("%s: tag not found", tok);
+ return (FALSE);
+ }
+ odotp = curwp->w_dotp;
+ odoto = curwp->w_doto;
+ bfname = curbp->b_fname;
+ adjf = adjustname(res->fname, TRUE);
+ bufp = findbuffer(adjf);
+ curbp = bufp;
+ showbuffer(bufp, curwp, WFFULL);
+ if(bufp->b_fname[0] == '\0') {
+ if(readin(adjf) != TRUE) {
+ killbuffer(bufp);
+ return (FALSE);
+ }
+ }
+ if(patsearch(res->pat) == TRUE) {
+ if((s = malloc(sizeof(struct stacknode))) == NULL)
+ err(1, NULL);
+ s->bfname = strdup(bfname);
+ s->dotp = odotp;
+ s->doto = odoto;
+ SLIST_INSERT_HEAD(&shead, s, entry);
+ return (TRUE);
+ }
+ else {
+ ewprintf("%s: pattern not found", tok);
+ return (FALSE);
+ }
+ return (TRUE);
+}
+
+/*
+ * 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;
+ s = SLIST_FIRST(&shead);
+ if(s == NULL) {
+ ewprintf("tag stack empty.");
+ return (FALSE);
+ }
+ 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);
+}