summaryrefslogtreecommitdiff
path: root/usr.bin/mg
diff options
context:
space:
mode:
authorlum <lum@cvs.openbsd.org>2011-11-28 04:41:40 +0000
committerlum <lum@cvs.openbsd.org>2011-11-28 04:41:40 +0000
commitb5aa1ee4118f4c7f9a9189dd9421148d8e9c78a8 (patch)
tree90302c020b2aa4660fc1214e9fd22855926cc321 /usr.bin/mg
parent4214ea6a927d3e8694341d40f0387eacca0c1aca (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/Makefile8
-rw-r--r--usr.bin/mg/cinfo.c9
-rw-r--r--usr.bin/mg/def.h9
-rw-r--r--usr.bin/mg/funmap.c5
-rw-r--r--usr.bin/mg/keymap.c9
-rw-r--r--usr.bin/mg/main.c4
-rw-r--r--usr.bin/mg/mg.121
-rw-r--r--usr.bin/mg/tags.c533
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);
+}