diff options
author | lum <lum@cvs.openbsd.org> | 2011-11-28 04:41:40 +0000 |
---|---|---|
committer | lum <lum@cvs.openbsd.org> | 2011-11-28 04:41:40 +0000 |
commit | b5aa1ee4118f4c7f9a9189dd9421148d8e9c78a8 (patch) | |
tree | 90302c020b2aa4660fc1214e9fd22855926cc321 /usr.bin/mg | |
parent | 4214ea6a927d3e8694341d40f0387eacca0c1aca (diff) |
Add some ctags support to mg. From Sunil Nimmagadda.
Man page review and suggestions from jmc@
Revewied and tested by myself, and ok deraadt@
Diffstat (limited to 'usr.bin/mg')
-rw-r--r-- | usr.bin/mg/Makefile | 8 | ||||
-rw-r--r-- | usr.bin/mg/cinfo.c | 9 | ||||
-rw-r--r-- | usr.bin/mg/def.h | 9 | ||||
-rw-r--r-- | usr.bin/mg/funmap.c | 5 | ||||
-rw-r--r-- | usr.bin/mg/keymap.c | 9 | ||||
-rw-r--r-- | usr.bin/mg/main.c | 4 | ||||
-rw-r--r-- | usr.bin/mg/mg.1 | 21 | ||||
-rw-r--r-- | usr.bin/mg/tags.c | 533 |
8 files changed, 583 insertions, 15 deletions
diff --git a/usr.bin/mg/Makefile b/usr.bin/mg/Makefile index fb02b129837..4f65776b5eb 100644 --- a/usr.bin/mg/Makefile +++ b/usr.bin/mg/Makefile @@ -1,9 +1,9 @@ -# $OpenBSD: Makefile,v 1.24 2011/02/02 05:21:36 lum Exp $ +# $OpenBSD: Makefile,v 1.25 2011/11/28 04:41:39 lum Exp $ 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.c dir.c display.c \ # # 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 diff --git a/usr.bin/mg/cinfo.c b/usr.bin/mg/cinfo.c index 0a580445ccd..f5688975851 100644 --- a/usr.bin/mg/cinfo.c +++ b/usr.bin/mg/cinfo.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cinfo.c,v 1.15 2005/12/13 06:01:27 kjell Exp $ */ +/* $OpenBSD: cinfo.c,v 1.16 2011/11/28 04:41:39 lum Exp $ */ /* This file is in the public domain. */ @@ -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, diff --git a/usr.bin/mg/def.h b/usr.bin/mg/def.h index 9366d71bc64..2ebd4dd4161 100644 --- a/usr.bin/mg/def.h +++ b/usr.bin/mg/def.h @@ -1,4 +1,4 @@ -/* $OpenBSD: def.h,v 1.116 2011/01/23 00:45:03 kjell Exp $ */ +/* $OpenBSD: def.h,v 1.117 2011/11/28 04:41:39 lum Exp $ */ /* This file is in the public domain. */ @@ -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 diff --git a/usr.bin/mg/funmap.c b/usr.bin/mg/funmap.c index b274aa208da..b128c2300e4 100644 --- a/usr.bin/mg/funmap.c +++ b/usr.bin/mg/funmap.c @@ -1,4 +1,4 @@ -/* $OpenBSD: funmap.c,v 1.34 2011/01/18 16:25:40 kjell Exp $ */ +/* $OpenBSD: funmap.c,v 1.35 2011/11/28 04:41:39 lum Exp $ */ /* This file is in the public domain */ @@ -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 diff --git a/usr.bin/mg/keymap.c b/usr.bin/mg/keymap.c index 099405ef24d..2ea2bb83829 100644 --- a/usr.bin/mg/keymap.c +++ b/usr.bin/mg/keymap.c @@ -1,4 +1,4 @@ -/* $OpenBSD: keymap.c,v 1.45 2011/01/18 16:25:40 kjell Exp $ */ +/* $OpenBSD: keymap.c,v 1.46 2011/11/28 04:41:39 lum Exp $ */ /* This file is in the public domain. */ @@ -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 diff --git a/usr.bin/mg/main.c b/usr.bin/mg/main.c index 4f1b1602dbb..dd9a4c392a7 100644 --- a/usr.bin/mg/main.c +++ b/usr.bin/mg/main.c @@ -1,4 +1,4 @@ -/* $OpenBSD: main.c,v 1.61 2009/06/04 02:23:37 kjell Exp $ */ +/* $OpenBSD: main.c,v 1.62 2011/11/28 04:41:39 lum Exp $ */ /* This file is in the public domain. */ @@ -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); diff --git a/usr.bin/mg/mg.1 b/usr.bin/mg/mg.1 index 9ea83074000..d18cb94c0b7 100644 --- a/usr.bin/mg/mg.1 +++ b/usr.bin/mg/mg.1 @@ -1,7 +1,7 @@ -.\" $OpenBSD: mg.1,v 1.55 2011/09/02 02:37:52 lum Exp $ +.\" $OpenBSD: mg.1,v 1.56 2011/11/28 04:41:39 lum Exp $ .\" This file is in the public domain. .\" -.Dd $Mdocdate: September 2 2011 $ +.Dd $Mdocdate: November 28 2011 $ .Dt MG 1 .Os .Sh NAME @@ -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 to the original file. .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 screen) window 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 diff --git a/usr.bin/mg/tags.c b/usr.bin/mg/tags.c new file mode 100644 index 00000000000..0796781117d --- /dev/null +++ b/usr.bin/mg/tags.c @@ -0,0 +1,533 @@ +/* + * Copyright (c) 2011 Sunil Nimmagadda <sunil@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); +} |