/* tagelvis.c */
/* Elvis uses this file to scan a tags file, and built a list of the matching
* tags, sorted by name and likelyhood that they're the intended tag.
*/
#include "elvis.h"
#ifdef FEATURE_RCSID
char id_tagelvis[] = "$Id: tagelvis.c,v 1.42 2003/10/18 04:47:18 steve Exp $";
#endif
/* Each call to tetag() sets this option, to indicate whether the cursor
* position should be saved.
*/
static ELVBOOL newtag = ElvTrue;
#ifdef FEATURE_TAGS
/* This option can be set via temodified to indicate that the current tag
* couldn't be loaded in a previous attempt because some other buffer was
* modified. The current tag hasn't been rejected; the next tetag() call
* should return the same tag and leave the history unchanged.
*/
static ELVBOOL sametag = ElvFalse;
/* Cause the next tetag() to return the same tag as the previous tetag() call */
void tesametag()
{
/* Note: taglist will always be non-NULL if we got here via a normal
* tag search. But if we got here by trying to follow a hypertext
* link from a modified buffer, then it will be NULL... in which case
* the sametag variable is irrelevant. So don't set it if taglist=NULL.
*/
if (taglist)
sametag = ElvTrue;
}
/* Locate a tag. Return the tag if it exists, or NULL if there is none.
* Also, update the tag history for successful or unsuccessful searches.
*/
TAG *tetag(select)
CHAR *select;
{
static char *tfilename; /* pathname of the previous "tags" file */
char *tmp;
CHAR *scan;
CHAR *args[2];
int i;
/* Decide whether the previous search was successful or not, by
* checking whether the current argument consists of only the tagname
* of the previous tag.
*/
if (taglist && !(select && *select && CHARcmp(select, toCHAR(taglist->TAGNAME))))
{
/* if supposed to return the same tag as last time, do that. */
if (sametag && taglist != NULL)
{
sametag = ElvFalse;
return taglist;
}
/* failed search */
tsadjust(taglist, '-');
/* try the next one in the list. If we hit the end of the
* list for this particular tags file, then try the next one
* from the path.
*/
tagdelete(ElvFalse);
if (!taglist && tfilename)
{
/* find the next tags file from tagpath */
for (tmp = iopath(tochar8(o_tags), "tags", ElvTrue);
tmp && strcmp(tmp, tfilename);
tmp = iopath(NULL, "tags", ElvTrue))
{
}
if (tmp)
tmp = iopath(NULL, "tags", ElvTrue);
/* process the following tag files until we get tags */
while (tmp && !taglist)
{
tsfile(tmp, o_taglength);
tmp = iopath(NULL, "tags", ElvTrue);
}
/* if we found tags, then remember the tags file name */
if (tmp)
{
safefree(tfilename);
tfilename = safedup(tmp);
}
}
/* return the next matching tag, if any */
newtag = ElvFalse;
goto Finish;
}
newtag = ElvTrue;
/* if no tag given on command line, then use o_previoustag */
if (!select || !*select)
{
if (!o_previoustag)
{
msg(MSG_ERROR, "no previous tag");
sametag = ElvFalse;
return NULL;
}
select = o_previoustag;
}
else
{
/* tag given... or maybe it is a selection expression.
* Wipe out the old value of previoustag */
if (o_previoustag)
safefree(o_previoustag);
o_previoustag = NULL;
/* Determine whether we're given a tag or a more complex
* restriction expression.
*/
for (scan = select; *scan && *scan != ':' && !elvspace(*scan); scan++)
if (*scan == '\\' && scan[1])
scan++;
/* If given a simple tag, then remember it as the value of
* o_previoustag. Strip out any backslashes used as quotes.
*
* Note: If we find any matching tags, then the value set here
* will be overridden by the name of the found tag, so really
* the value we set here only matters if there is no matching
* tag.
*/
if (!*scan)
{
for (scan = select; *scan; scan++)
{
if (*scan == '\\' && scan[1])
scan++;
buildCHAR(&o_previoustag, *scan);
}
}
}
/* Previous tag search (if any) was apparently successful */
if (taglist)
tsadjust(taglist, '+');
/* Delete all tags from previous search */
tagdelete(ElvTrue);
/* using internal tag search, or external? */
if (o_tagprg && *o_tagprg)
{
/* external tag search */
/* Wipe out the set of restrictions */
tsreset();
tmp = tochar8(calculate(toCHAR("file:+(filename)"),NULL, CALC_MSG));
assert(tmp);
tsparse(tmp);
/* evaluate the tagprg string with $1 set to the args */
args[0] = select;
args[1] = NULL;
scan = calculate(o_tagprg, args, CALC_MSG);
if (!scan)
goto Finish;
/* add a "!" to the front of the command string, so tsfile
* will know it is a command and not a weird file name.
*/
tmp = (char *)safealloc(CHARlen(scan) + 2, sizeof(char));
tmp[0] = '!';
for (i = 1; *scan; i++, scan++)
tmp[i] = *scan;
/* read tags from the program */
tsfile(tochar8(tmp), o_taglength);
safefree(tmp);
/* wipe out the tag file name */
if (tfilename)
safefree(tfilename);
tfilename = NULL;
}
else
{
/* internal tag search */
/* Build a new set of restrictions */
tsreset();
tsparse(tochar8(select));
tmp = tochar8(calculate(toCHAR("file:+(filename)"),NULL, CALC_MSG));
assert(tmp);
tsparse(tmp);
/* locate the first set of tags */
for (tmp = iopath(tochar8(o_tags), "tags", ElvTrue);
tmp && !taglist;
tmp = iopath(NULL, "tags", ElvTrue))
{
tsfile(tmp, o_taglength);
}
/* remember the tag file name */
if (tfilename)
safefree(tfilename);
tfilename = (tmp ? safedup(tmp) : NULL);
}
Finish:
/* return the first matching tag, if any */
if (!taglist)
msg(MSG_ERROR, newtag ? "no matching tags" : "no more matching tags");
else if (!o_previoustag || CHARcmp(o_previoustag, toCHAR(taglist->TAGNAME)))
{
if (o_previoustag)
safefree(o_previoustag);
o_previoustag = CHARdup(toCHAR(taglist->TAGNAME));
}
sametag = ElvFalse;
return taglist;
}
#ifdef FEATURE_BROWSE
/* Build a browser document for a given set of restrictions */
BUFFER tebrowse(all, select)
ELVBOOL all; /* scan all tags files? (else only first) */
CHAR *select; /* the restrictions, from command line */
{
BUFFER buf; /* the buffer containing the new browser file */
MARKBUF from, to; /* positions in the buffer */
char *proto; /* name of tags file or prototype file */
CHAR *item; /* format of a single item, from prototype */
CHAR *address; /* the tagaddress of a tag, converted to HTML */
CHAR *url; /* the URL of a tag */
CHAR *args[5]; /* tagname, tagfile, tagaddress, url, NULL */
CHAR *dflt = toCHAR("
\n");
CHAR *cp;
CHAR prev, prev2;
long qty;
CHAR qtystr[20];
TAG *qtytag;
char *tmp;
int i;
/* forget any old tag info */
tagdelete(ElvTrue);
tsreset();
/* default args are none */
if (!select)
select = toCHAR("");
if (o_tagprg && *o_tagprg)
{
/* external tag search */
/* make an HTML copy of the string */
cp = (CHAR *)safealloc(CHARlen(select) + 8, sizeof(CHAR));
CHARcpy(cp, toCHAR("Browse "));
CHARcat(cp, select);
/* evaluate the tagprg string with $1 set to the args */
args[0] = select;
args[1] = NULL;
select = cp;
cp = calculate(o_tagprg, args, CALC_MSG);
if (!cp)
return NULL;
/* add a "!" to the front of the command string, so tsfile
* will know it is a command and not a weird file name.
*/
tmp = (char *)safealloc(CHARlen(cp) + 2, sizeof(char));
tmp[0] = '!';
for (i = 1; *cp; i++, cp++)
tmp[i] = *cp;
/* read tags from the program */
tsfile(tochar8(tmp), o_taglength);
safefree(tmp);
}
else
{
/* internal tag search */
/* parse the restrictions & make an HTML copy of the string */
cp = (CHAR *)safealloc(CHARlen(select) + 8, sizeof(CHAR));
CHARcpy(cp, toCHAR("Browse "));
CHARcat(cp, select);
tsparse(tochar8(select));
select = cp;
/* build the tags list */
for (proto = iopath(tochar8(o_tags), "tags", ElvTrue);
proto && (!taglist || all);
proto = iopath(NULL, "tags", ElvTrue))
{
tsfile(proto, o_taglength);
}
}
/* if no tags, then fail */
if (!taglist)
{
return NULL;
}
/* count the tags */
for (qty = 0L, qtytag = taglist; qtytag; qty++, qtytag = qtytag->next)
{
}
sprintf(tochar8(qtystr), "%ld", qty);
/* Create a buffer to hold the browser document. If possible, load
* the document format from a file named "elvis.bro"
*/
proto = iopath(tochar8(o_elvispath), BROWSER_FILE, ElvFalse);
if (proto)
buf = bufload(select, proto, ElvTrue);
else
buf = bufalloc(select, 0, ElvFalse);
/* Set some of the buffer's options */
o_initialsyntax(buf) = ElvFalse;
o_readonly(buf) = ElvTrue;
if (o_filename(buf))
{
if (optflags(o_filename(buf)) & OPT_FREE)
safefree(o_filename(buf));
o_filename(buf) = NULL;
}
if (o_bufdisplay(buf))
{
if (optflags(o_bufdisplay(buf)) & OPT_FREE)
safefree(o_bufdisplay(buf));
optflags(o_bufdisplay(buf)) &= ~OPT_FREE;
}
o_bufdisplay(buf) = toCHAR("html");
if (o_mapmode(buf))
{
if (optflags(o_mapmode(buf)) & OPT_FREE)
safefree(o_mapmode(buf));
optflags(o_mapmode(buf)) &= ~OPT_FREE;
}
o_mapmode(buf) = toCHAR("html");
/* if no format was read from "elvis.bro" then use the default */
if (o_bufchars(buf) == 0L)
bufreplace(marktmp(from, buf, 0L), &from, dflt, CHARlen(dflt));
/* Parse the document; i.e., locate the item section, copy it into RAM,
* and then delete it from the buffer. The item section is delimited
* by blank lines.
*/
to = from;
for (scanalloc(&cp, marktmp(from, buf, 0L)), prev = prev2 = '\0';
cp;
prev2 = prev, prev = *cp, scannext(&cp))
{
/* watch for multiple-newlines */
if (prev2 == '\n' && prev == '\n' && *cp != '\n')
{
if (markoffset(&from) == 0L)
from = *scanmark(&cp);
else
to = *scanmark(&cp);
}
/* watch for $1 or $2 in the header */
if (markoffset(&from) == 0 && prev2 == '$' && (prev == '1' || prev == '2'))
{
to = *scanmark(&cp);
scanfree(&cp);
from = to;
markaddoffset(&from, -2);
switch (prev)
{
case '1':
args[0] = select;
args[1] = NULL;
cp = calculate(toCHAR("htmlsafe($1)"), args, CALC_MSG);
break;
case '2':
cp = qtystr;
break;
}
bufreplace(&from, &to, cp, CHARlen(cp));
markaddoffset(&from, CHARlen(cp));
scanalloc(&cp, &from);
marksetoffset(&from, 0);
to = from;
}
}
scanfree(&cp);
if (markoffset(&to) == 0L)
{
msg(MSG_ERROR, "bad elvis.bdf");
return NULL;
}
markaddoffset(&to, -1);
item = bufmemory(&from, &to);
markaddoffset(&from, -1);
markaddoffset(&to, 1);
bufreplace(&from, &to, NULL, 0L);
/* for each tag in the list... */
for ( ; taglist; tagdelete(ElvFalse))
{
/* Convert the address to a plaintext line */
if (elvdigit(*taglist->TAGADDR))
{
/* line number -- make sure it isn't JUST a number! */
address = NULL;
buildstr(&address, "(line ");
buildstr(&address, taglist->TAGADDR);
buildCHAR(&address, ')');
}
else
{
/* strip /^ and $/, along with any backslashes */
for (address = NULL, proto = taglist->TAGADDR + 2;
proto[2];
proto++)
{
if (*proto == '\\' && proto[1])
proto++;
buildCHAR(&address, *proto);
}
if (!address)
address = (CHAR *)safealloc(1, sizeof(CHAR));
}
/* Generate an URL. Note that the tagaddress is URL-encoded */
url = NULL;
buildstr(&url, taglist->TAGFILE);
buildCHAR(&url, '?');
for (cp = toCHAR(taglist->TAGADDR); *cp; cp++)
{
switch (*cp)
{
case '\t': buildstr(&url, "%09"); break;
case '+': buildstr(&url, "%2B"); break;
case '"': buildstr(&url, "%22"); break;
case '%': buildstr(&url, "%25"); break;
case '<': buildstr(&url, "%3C"); break;
case '>': buildstr(&url, "%3E"); break;
case ' ': buildCHAR(&url, '+'); break;
default: buildCHAR(&url, *cp);
}
}
/* evaluate the item line with this tag's values */
args[0] = toCHAR(taglist->TAGNAME);
args[1] = toCHAR(taglist->TAGFILE);
args[2] = address;
args[3] = url;
args[4] = NULL;
cp = calculate(item, args, CALC_MSG);
if (!cp)
cp = item; /* error -- but give user a clue */
/* stuff the tag into the document */
bufreplace(&from, &from, cp, CHARlen(cp));
markaddoffset(&from, CHARlen(cp));
/* free the temporary stuff */
safefree(address);
safefree(url);
}
/* turn off the "modified" flag. */
o_modified(buf) = ElvFalse;
buf->docursor = 0L;
/* return the buffer */
return buf;
}
#endif /* FEATURE_BROWSE */
#ifdef DISPLAY_SYNTAX
/* Scan a tags file for any tags which are callable from the current language
* (i.e., the most recently loaded via descr_open(), not descr_recall()), and
* add them to a dictionary. The tag's name is the word that gets added. The
* word's flags are set to denote the font; the font name is derived by adding
* a prefix to the value of the tag's "kind". For example, if prefix="lib"
* and we're adding a function named "foo", then the dictionary will receive
* the word "foo" with font "libf".
*
* Returns the new root of the spell dictionary.
*/
spell_t *telibrary(tagfile, dict, ignorecase, prefix)
char *tagfile; /* name of the tags file */
spell_t *dict; /* dictionary to which tags are added */
ELVBOOL ignorecase; /* convert to lowercase before adding? */
CHAR *prefix; /* prefix for font names */
{
CHAR tagline[1000]; /* input buffer */
ELVBOOL allnext; /* does tagline[] contain the whole next line?*/
int bytes; /* number of bytes in tagline */
CHAR *src, *dst; /* for manipulating tagline[] */
TAG *tag; /* a tag parsed from tagline[] */
ELVBOOL inside; /* is tag's scope limited? */
CHAR fontname[50];
CHAR descr[30];
int font;
char prevkind[50];
long flags;
int genericfont, letterfont[26];
int i;
/* open the file */
if (!ioopen(tagfile, 'r', ElvFalse, ElvFalse, 't'))
return dict;
/* Arrange certain parameters to always appear in the same elements of
* the tag->attr[] array.
*/
#define TEKIND attr[3]
#define TEFILE attr[4]
#define TEFIRST 6
tagnamereset();
CHARcpy(tagline, toCHAR("x\tx\t1;\"\tkind:x\tfile:x\tln:x\tenum:x"));
tagparse(tochar8(tagline));
/* create a generic font name */
prevkind[0] = '\0';
font = genericfont = colorfind(prefix);
CHARcpy(descr, toCHAR("like normal"));
if (genericfont)
{
colorset(genericfont, descr, ElvFalse);
/* after this, others should default to be "like" this font */
CHARcpy(descr, toCHAR("like "));
CHARcat(descr, prefix);
}
flags = genericfont << 8;
/* single-letter fonts aren't allocated yet */
memset(letterfont, 0, sizeof letterfont);
/* For each line from the tags file */
bytes = ioread(tagline, QTY(tagline) - 1);
while (bytes > 5) /* shortest possible legal tag line */
{
/* find the end of this line */
for (src = tagline; src < &tagline[bytes] && *src != '\n'; src++)
{
}
/* parse it */
*src = '\0';
tag = tagparse(tochar8(tagline));
if (!tag)
break;
/* see if the tag's scope is limited, unless it's a function */
inside = ElvFalse;
if (!tag->TEKIND || tag->TEKIND[0] != 'f' || tag->TEKIND[1])
for (i = TEFIRST; i < QTY(tag->attr); i++)
if (tag->attr[i])
{
inside = ElvTrue;
break;
}
/* if not static, not a non-function limited to some other
* scope, and not already in dictionary, and is defined in a
* file which is callable by this one...
*/
if (!tag->TEFILE
&& !inside
&& !SPELL_IS_GOOD(spellfindword(dict, toCHAR(tag->TAGNAME), 0))
&& descr_cancall(toCHAR(tag->TAGFILE)))
{
/* some easy cases: no "kind" then use generic */
if (!tag->TEKIND)
flags = genericfont << 8;
/* single-letter "kind" and letterfont is set, use it */
else if (tag->TEKIND[0] >= 'a'
&& tag->TEKIND[0] <= 'z'
&& tag->TEKIND[1] == '\0'
&& letterfont[tag->TEKIND[0] - 'a'] != 0)
flags = letterfont[tag->TEKIND[0] - 'a'] << 8;
/* same font as last time */
else if (!strcmp(prevkind, tag->TEKIND))
flags = font << 8;
else
{
/* no special cases, just do it the hard way */
strcpy(prevkind, tag->TEKIND);
CHARcpy(fontname, prefix);
CHARcat(fontname, toCHAR(prevkind));
font = colorfind(fontname);
if (font)
colorset(font, descr, ElvFalse);
/* if single-letter, remember it */
if (tag->TEKIND[0] >= 'a'
&& tag->TEKIND[0] <= 'z'
&& tag->TEKIND[1] == '\0')
letterfont[tag->TEKIND[0] - 'a'] = font;
/* set the flags to use this font */
flags = font << 8;
}
/* if ignorecase, then force the name to be lowercase */
if (ignorecase)
{
char *conv;
for (conv = tag->TAGNAME; *conv; conv++)
*conv = elvtolower(*conv);
}
/* add the word, with the proper font flags */
dict = spelladdword(dict, toCHAR(tag->TAGNAME), flags);
}
/* delete this line from tagline[] */
for (dst = tagline, src++, allnext = ElvFalse; src < &tagline[bytes]; )
{
if (*src == '\n')
allnext = ElvTrue;
*dst++ = *src++;
}
bytes = (int)(dst - tagline);
/* if the next line is incomplete, read some more text
* from the tags file.
*/
if (!allnext)
{
bytes += ioread(dst, (int)QTY(tagline) - bytes - 1);
}
}
(void)ioclose();
return dict;
}
#endif /* DISPLAY_SYNTAX */
#ifdef FEATURE_SHOWTAG
/* build a list of all top-level tags defined in this buffer */
void tebuilddef(buf)
BUFFER buf;
{
/* for building the new tagdef array */
TEDEF *tagdef; /* the new tagdef array */
TEDEF *bigger; /* used while enlarging tagdef */
int allocated; /* number of items allocated for tagdef */
int ntagdefs; /* number of items in tagdef */
/* for scanning the tags file... */
CHAR tagline[1000]; /* input buffer */
ELVBOOL allnext; /* does tagline[] contain the whole next line?*/
int bytes; /* number of bytes in tagline */
CHAR *src, *dst; /* for manipulating tagline[] */
TAG *tag; /* a tag parsed from tagline[] */
/* for locating a tag defintion within this buffer */
EXINFO xinfb; /* dummy ex command, for parsing tag address */
ELVBOOL wasmagic; /* stores the normal value of o_magic */
ELVBOOL wassaveregexp; /* stores the normal value of o_saveregexp */
ELVBOOL wasmsghide; /* stores the msghide() flag */
CHAR *cp; /* for scanning the line */
long offset; /* offset of the tag within this buffer */
int i;
/* Destroy the old list, if any */
tefreedef(buf);
/* if the show option doesn't contain "tag", then do nothing more */
if (!o_show)
return;
for (src = o_show; src && *src; src++)
if (CHARncmp(src, "tag", 3))
break;
if (!*src)
return;
/* if this buffer contains no file, then do nothing */
if (!o_filename(buf))
return;
/* Scan the "tags" file. Note that we're using the lower-level
* tag reading functions for a couple of reasons: speed of course,
* but also because we don't want to clobber any existing tag list.
* That's important because tebuilddef() will be called whenever
* :tag causes a file to be loaded, and we want to keep the remainder
* of that tag list in case we just loaded the wrong one.
*/
/* If there is no tags file, then do nothing. This check is
* necessary because the ioopen() function displays an error
* message when the file it's trying to read doesn't exist.
*/
if (dirperm("tags") == DIR_NEW)
return;
/* open the file */
if (!ioopen("tags", 'r', ElvFalse, ElvFalse, 't'))
return;
/* For each line from the tags file */
ntagdefs = allocated = 0;
tagdef = NULL;
wasmsghide = msghide(ElvTrue);
bytes = ioread(tagline, QTY(tagline) - 1);
while (bytes > 5) /* shortest possible legal tag line */
{
/* find the end of this line */
for (src = tagline; src < &tagline[bytes] && *src != '\n'; src++)
{
}
/* parse it */
*src = '\0';
tag = tagparse(tochar8(tagline));
if (!tag)
break;
/* if for this file, and its definition isn't indented... */
if (!strcmp(tag->TAGFILE, tochar8(o_filename(buf)))
&& (elvdigit(tag->TAGADDR[0]) || !elvspace(tag->TAGADDR[2])))
{
/* if the tag has a "ln" attribute, start searching
* there -- saves *a lot* of time.
*/
memset((char *)&xinfb, 0, sizeof xinfb);
(void)marktmp(xinfb.defaddr, buf, 0);
for (i = 3; i < MAXATTR && tagattrname[i] && strcmp(tagattrname[i], "ln"); i++)
{
}
if (i < MAXATTR && tag->attr[i] && (offset = atol(tag->attr[i])) > 1L)
(void)marksetline(&xinfb.defaddr, offset - 1);
/* locate the tag's definition within this buffer */
scanstring(&cp, toCHAR(tag->TAGADDR));
wasmagic = o_magic;
o_magic = ElvFalse;
wassaveregexp = o_saveregexp;
o_saveregexp = ElvFalse;
if (!exparseaddress(&cp, &xinfb))
{
scanfree(&cp);
o_magic = wasmagic;
o_saveregexp = wassaveregexp;
goto NotFound;
}
scanfree(&cp);
o_magic = wasmagic;
o_saveregexp = wassaveregexp;
offset = lowline(bufbufinfo(buf), xinfb.to);
exfree(&xinfb);
/* enlarge tagdef[] if necessary */
if (ntagdefs + 1 > allocated)
{
allocated += 50;
bigger = (TEDEF *)safealloc(allocated, sizeof(TEDEF));
if (tagdef)
{
memcpy(bigger, tagdef, ntagdefs * sizeof(TEDEF));
safefree(tagdef);
}
tagdef = bigger;
}
/* insert this tag into the array, sorted by offset */
for (i = ntagdefs;
i > 0 && markoffset(tagdef[i - 1].where) > offset;
i--)
{
tagdef[i] = tagdef[i - 1];
}
tagdef[i].where = markalloc(buf, offset);
tagdef[i].label = NULL;
buildstr(&tagdef[i].label, tag->TAGNAME);
ntagdefs++;
}
NotFound:
/* delete this line from tagline[] */
for (dst = tagline, src++, allnext = ElvFalse; src < &tagline[bytes]; )
{
if (*src == '\n')
allnext = ElvTrue;
*dst++ = *src++;
}
bytes = (int)(dst - tagline);
/* if the next line is incomplete, read some more text
* from the tags file.
*/
if (!allnext)
{
bytes += ioread(dst, (int)QTY(tagline) - bytes - 1);
}
}
(void)ioclose();
msghide(wasmsghide);
/* store the list */
buf->tagdef = tagdef;
buf->ntagdefs = ntagdefs;
safeinspect();
}
/* free the tag definition info for a given buffer */
void tefreedef(buf)
BUFFER buf;
{
int i;
safeinspect();
if (buf->tagdef)
{
for (i = 0; i < buf->ntagdefs; i++)
{
markfree(buf->tagdef[i].where);
safefree(buf->tagdef[i].label);
}
safefree((void *)buf->tagdef);
buf->tagdef = NULL;
buf->ntagdefs = 0;
}
safeinspect();
}
/* return the info about the tag that is defined at the cursor location. */
CHAR *telabel(cursor)
MARK cursor;
{
int i;
static CHAR noinfo[1];
TEDEF *tagdef = markbuffer(cursor)->tagdef;
/* if buffer has no tags, then return no info */
if (!tagdef)
return noinfo;
/* search for this MARK in the list */
for (i = 0; i < markbuffer(cursor)->ntagdefs; i++)
{
if (markoffset(tagdef[i].where) > markoffset(cursor))
break;
}
/* report what (if anything) we found */
if (i == 0)
return noinfo;
else
return tagdef[i - 1].label;
}
#endif /* defined(FEATURE_SHOWTAG) */
#ifdef FEATURE_COMPLETE
/* This function is used for completing a tag name. It searches backward
* from the provided cursor position to collect the characters of a partial
* tag name, and then it looks for any known tags whose name matches that
* partial name. It returns a string containing any new characters that it
* could match.
*/
CHAR *tagcomplete(win, m)
WINDOW win; /* the window where multiple matches are listed */
MARK m; /* the cursor position (end of partial name) */
{
char rest[300];
static CHAR retbuf[100];
int plen; /* length of partial string */
CHAR *cp;
int mlen;
TAG *tag, *scan;
long oldtaglength;
TAG *oldtaglist;
ELVBOOL oldmsghide;
ELVBOOL oldexrefresh;
CHAR *oldprevioustag;
DRAWSTATE olddrawstate;
/* Ignore the inputtab=identifier setting unless there is a "tags"
* file in the current directory.
*/
if (o_inputtab(markbuffer(win->cursor)) == 'i'
&& dirperm("tags") < DIR_READONLY)
{
/* return an ordinary tab character */
retbuf[0] = '\t';
retbuf[1] = '\0';
return retbuf;
}
/* collect the characters of the partial name */
rest[0] = '\0';
for (scanalloc(&cp, m), plen = 0;
scanprev(&cp) && (elvalnum(*cp) || *cp == '_') && plen < QTY(rest) - 1;
)
{
memmove(rest + 1, rest, QTY(rest) - 1);
rest[0] = *cp;
plen++;
}
scanfree(&cp);
/* if no text, or a keyword, then just return a tab character */
if (plen == 0 || dmskeyword(win, toCHAR(rest)))
{
retbuf[0] = '\t';
retbuf[1] = '\0';
return retbuf;
}
/* find the matching tags */
oldtaglist = taglist;
taglist = NULL;
oldtaglength = o_taglength;
oldmsghide = msghide(ElvTrue);
oldprevioustag = o_previoustag ? CHARdup(o_previoustag) : NULL;
o_previoustag = NULL;
o_taglength = plen;
if (o_filename(markbuffer(m))
&& strlen(rest) + 2 + CHARlen(o_filename(markbuffer(m))) < QTY(rest))
sprintf(rest + strlen(rest), " file:%s", tochar8(o_filename(markbuffer(m))) );
tag = tetag(toCHAR(rest));
taglist = oldtaglist;
msghide(oldmsghide);
o_taglength = oldtaglength;
if (o_previoustag)
safefree(o_previoustag);
o_previoustag = oldprevioustag;
/* if no matches, then return a space */
if (!tag)
{
CHARcpy(retbuf, toCHAR(" "));
if (plen == 0)
*retbuf = '\0';
return retbuf;
}
/* eliminate duplicates */
for (scan = tag; scan->next; )
{
if (!strcmp(scan->TAGNAME, scan->next->TAGNAME))
scan->next = tagfree(scan->next);
else
scan = scan->next;
}
/* if only one match, then return the remainder of its name plus
* a space.
*/
if (!tag->next)
{
CHARcpy(retbuf, toCHAR(tag->TAGNAME + plen));
CHARcat(retbuf, toCHAR(" "));
while (tag)
tag = tagfree(tag);
return retbuf;
}
/* We have multiple matches. Can we add any characters? */
mlen = strlen(tag->TAGNAME);
for (scan = tag->next; scan; scan = scan->next)
{
while (strncmp(tag->TAGNAME, scan->TAGNAME, mlen) != 0)
mlen--;
}
/* If we can add some chars then do so */
if (mlen > plen)
{
CHARncpy(retbuf, toCHAR(tag->TAGNAME + plen), mlen - plen);
retbuf[mlen - plen] = '\0';
while (tag)
tag = tagfree(tag);
return retbuf;
}
/* Else list all matches */
plen = 0;
olddrawstate = win->di->drawstate;
for (scan = tag; scan; scan = scan->next)
{
mlen = strlen(scan->TAGNAME);
if (plen + mlen + 1 >= o_columns(win))
{
drawextext(win, toCHAR("\n"), 1);
plen = 0;
olddrawstate = win->di->drawstate;
}
else if (plen > 0)
{
drawextext(win, blanks, 1);
plen++;
}
drawextext(win, toCHAR(scan->TAGNAME), mlen);
plen += mlen;
}
/* complete the last output line. Note that we try to do this in
* a clever way which avoids prompting the user to "Hit to
* continue" if the whole list fits on a single line.
*/
if (olddrawstate == DRAW_VISUAL)
{
oldexrefresh = o_exrefresh;
o_exrefresh = ElvTrue;
drawextext(win, retbuf, 0);
o_exrefresh = oldexrefresh;
win->di->drawstate = DRAW_VMSG;
}
else
drawextext(win, toCHAR("\n"), 1);
/* we weren't able to extend the partial name at all */
*retbuf = '\0';
return retbuf;
}
#endif
#endif /* FEATURE_TAGS */
/* Save the current cursor position on the tag stack. */
void tepush(win, label)
WINDOW win; /* window where push should occur */
CHAR *label; /* dynamically-allocated name of old position */
{
int i;
if (o_tagstack
&& newtag
&& (o_filename(markbuffer(win->cursor))
|| o_bufchars(markbuffer(win->cursor))))
{
/* The oldest tag will be lost. If it had pointers to any
* dynamically allocated memory, then free that memory now.
*/
if (win->tagstack[TAGSTK - 1].prevtag)
safefree(win->tagstack[TAGSTK - 1].prevtag);
if (win->tagstack[TAGSTK - 1].origin)
markfree(win->tagstack[TAGSTK - 1].origin);
/* Shift the tag stack; top is always win->tagstack[0] */
for (i = TAGSTK - 1; i > 0; i--)
{
win->tagstack[i] = win->tagstack[i - 1];
}
/* insert data into the top slot */
win->tagstack[0].origin = markdup(win->cursor);
win->tagstack[0].display = win->md->name;
win->tagstack[0].prevtag = label;
}
else
{
safefree(label);
}
/* always leave newtag set to "true" */
newtag = ElvTrue;
}