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); +}