4.4BSD/usr/src/contrib/nvi/nvi/ex/ex_tag.c
/*-
* Copyright (c) 1992, 1993
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* David Hitz of Auspex Systems, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef lint
static char sccsid[] = "@(#)ex_tag.c 8.1 (Berkeley) 6/9/93";
#endif /* not lint */
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "vi.h"
#include "excmd.h"
#include "tag.h"
static char *binary_search __P((char *, char *, char *));
static int compare __P((char *, char *, char *));
static char *linear_search __P((char *, char *, char *));
static int search __P((char *, char *, char **));
static int tag_change __P((SCR *, EXF *, char *, char *, char *, int));
static int tag_get __P((SCR *, char *, char **, char **, char **));
/*
* ex_tagfirst --
* The tag code can be entered from main, i.e. "vi -t tag".
*/
int
ex_tagfirst(sp, tagarg)
SCR *sp;
char *tagarg;
{
EXF *tep;
MARK m;
long tl;
int sval;
char *p, *tag, *fname, *search, *argv[2];
/* Taglength may limit the number of characters. */
if ((tl = O_VAL(sp, O_TAGLENGTH)) != 0 && strlen(tagarg) > tl)
tagarg[tl] = '\0';
/* Get the tag information. */
if (tag_get(sp, tagarg, &tag, &fname, &search))
return (1);
/* Create the file entry. */
argv[0] = fname;
argv[1] = NULL;
if (file_set(sp, 1, argv) ||
(tep = file_first(sp, 0)) == NULL ||
(tep = file_start(sp, tep, NULL)) == NULL)
return (1);
/*
* Search for the tag; cheap fallback for C functions
* if the name is the same but the arguments have changed.
*/
m.lno = 1;
m.cno = 0;
sval = f_search(sp, tep, &m, &m, search,
NULL, SEARCH_FILE | SEARCH_TAG | SEARCH_TERM);
if (sval && (p = strrchr(search, '(')) != NULL) {
p[1] = '\0';
sval = f_search(sp, tep, &m, &m, search,
NULL, SEARCH_FILE | SEARCH_TAG | SEARCH_TERM);
}
if (sval) {
msgq(sp, M_ERR, "%s: search pattern not found.", tag);
return (1);
}
/* Set up the screen/file. */
sp->lno = m.lno;
sp->cno = m.cno;
sp->ep = tep;
F_CLR(tep, F_NOSETPOS);
/* Might as well make this a default tag. */
if ((sp->tlast = strdup(tagarg)) == NULL) {
msgq(sp, M_ERR, "Error: %s", strerror(errno));
return (1);
}
return (0);
}
/*
* ex_tagpush -- :tag [file]
* Move to a new tag.
*/
int
ex_tagpush(sp, ep, cmdp)
SCR *sp;
EXF *ep;
EXCMDARG *cmdp;
{
TAG *tp;
long tl;
char *fname, *search, *tag;
switch (cmdp->argc) {
case 1:
if (sp->tlast != NULL)
free(sp->tlast);
if ((sp->tlast = strdup((char *)cmdp->argv[0])) == NULL) {
msgq(sp, M_ERR, "Error: %s", strerror(errno));
return (1);
}
break;
case 0:
if (sp->tlast == NULL) {
msgq(sp, M_ERR, "No previous tag entered.");
return (1);
}
break;
default:
abort();
}
/* Taglength may limit the number of characters. */
if ((tl = O_VAL(sp, O_TAGLENGTH)) != 0 && strlen(sp->tlast) > tl)
sp->tlast[tl] = '\0';
/* Get the tag information. */
if (tag_get(sp, sp->tlast, &tag, &fname, &search))
return (1);
/* Save enough information that we can get back. */
if ((tp = malloc(sizeof(TAG))) == NULL) {
msgq(sp, M_ERR, "Error: %s.", strerror(errno));
return (1);
}
tp->ep = ep;
tp->lno = sp->lno;
tp->cno = sp->cno;
/* Try to change. */
if (tag_change(sp, ep, tag, fname, search, F_ISSET(cmdp, E_FORCE))) {
free(tag);
FREE(tp, sizeof(TAG));
return (1);
}
/* Push saved information. */
HDR_APPEND(tp, &sp->taghdr, next, prev, TAG);
return (0);
}
/*
* ex_tagpop -- :tagp[op][!]
* Pop the tag stack.
*/
int
ex_tagpop(sp, ep, cmdp)
SCR *sp;
EXF *ep;
EXCMDARG *cmdp;
{
TAG *tp;
/* Pop newest saved information. */
tp = sp->taghdr.next;
if (tp == (TAG *)&sp->taghdr) {
msgq(sp, M_INFO, "The tags stack is empty.");
return (1);
}
/* If not switching files, it's easy; else do the work. */
if (tp->ep == ep) {
sp->lno = tp->lno;
sp->cno = tp->cno;
} else {
MODIFY_CHECK(sp, ep, F_ISSET(cmdp, E_FORCE));
sp->enext = tp->ep;
set_altfname(sp, ep->name);
F_SET(sp, S_FSWITCH);
}
/* Delete the saved information from the stack. */
HDR_DELETE(tp, next, prev, TAG);
return (0);
}
/*
* ex_tagtop -- :tagt[op][!]
* Clear the tag stack.
*/
int
ex_tagtop(sp, ep, cmdp)
SCR *sp;
EXF *ep;
EXCMDARG *cmdp;
{
TAG *tp;
/* Pop oldest saved information. */
tp = sp->taghdr.prev;
if (tp == (TAG *)&sp->taghdr) {
msgq(sp, M_INFO, "The tags stack is empty.");
return (1);
}
/* If not switching files, it's easy; else do the work. */
if (tp->ep == ep) {
sp->lno = tp->lno;
sp->cno = tp->cno;
} else {
MODIFY_CHECK(sp, ep, F_ISSET(cmdp, E_FORCE));
sp->enext = tp->ep;
sp->lno = tp->lno;
sp->cno = tp->cno;
set_altfname(sp, ep->name);
F_SET(sp, S_FSWITCH);
}
/* Delete the stack. */
while ((tp = sp->taghdr.next) != (TAG *)&sp->taghdr) {
HDR_DELETE(tp, next, prev, TAG);
FREE(tp, sizeof(TAG));
}
return (0);
}
static int
tag_change(sp, ep, tag, fname, search, force)
SCR *sp;
EXF *ep;
char *tag, *fname, *search;
int force;
{
enum {TC_CHANGE, TC_CURRENT} which;
EXF *tep;
MARK m;
int sval;
char *p;
if ((tep = file_get(sp, ep, fname, 1)) == NULL)
return (1);
if (ep == tep)
which = TC_CURRENT;
else {
MODIFY_CHECK(sp, ep, force);
if ((tep = file_start(sp, tep, NULL)) == NULL)
return (1);
which = TC_CHANGE;
}
/*
* Search for the tag; cheap fallback for C functions
* if the name is the same but the arguments have changed.
*/
m.lno = 1;
m.cno = 0;
sval = f_search(sp, tep == NULL ? ep : tep, &m, &m, search,
NULL, SEARCH_FILE | SEARCH_TAG | SEARCH_TERM);
if (sval && (p = strrchr(search, '(')) != NULL) {
p[1] = '\0';
sval = f_search(sp, tep == NULL ? ep : tep, &m, &m, search,
NULL, SEARCH_FILE | SEARCH_TAG | SEARCH_TERM);
}
if (sval) {
msgq(sp, M_ERR, "%s: search pattern not found.", tag);
switch (which) {
case TC_CHANGE:
sp->enext = tep;
set_altfname(sp, ep->name);
F_SET(sp, S_FSWITCH);
return (0);
case TC_CURRENT:
return (1);
}
} else switch (which) {
case TC_CHANGE:
sp->lno = m.lno;
sp->cno = m.cno;
F_CLR(tep, F_NOSETPOS);
sp->enext = tep;
set_altfname(sp, ep->name);
F_SET(sp, S_FSWITCH);
break;
case TC_CURRENT:
sp->lno = m.lno;
sp->cno = m.cno;
break;
}
return (0);
}
/*
* tag_get --
* Get a tag from the tags files.
*/
static int
tag_get(sp, tag, tagp, filep, searchp)
SCR *sp;
char *tag, **tagp, **filep, **searchp;
{
char *p;
TAGF **tfp;
/* Find the tag, only display missing file messages once. */
p = NULL;
for (tfp = sp->tfhead; *tfp != NULL && p == NULL; ++tfp)
if (search((*tfp)->fname, tag, &p) &&
(errno != ENOENT || !F_ISSET((*tfp), TAGF_ERROR))) {
msgq(sp, M_ERR,
"%s: %s", (*tfp)->fname, strerror(errno));
F_SET((*tfp),TAGF_ERROR);
}
if (p == NULL) {
msgq(sp, M_ERR, "%s: tag not found.", tag);
return (1);
}
/*
* Set the return pointers; tagp points to the tag, and, incidentally
* the allocated string, filep points to the nul-terminated file name,
* searchp points to the nul-terminated search string.
*/
for (*tagp = p; *p && !isspace(*p); ++p);
if (*p == '\0')
goto malformed;
for (*p++ = '\0'; isspace(*p); ++p);
for (*filep = p; *p && !isspace(*p); ++p);
if (*p == '\0')
goto malformed;
for (*p++ = '\0'; isspace(*p); ++p);
*searchp = p;
if (*p == '\0') {
malformed: free(*tagp);
msgq(sp, M_ERR, "%s: corrupted tag in %s.", tag, (*tfp)->fname);
return (1);
}
return (0);
}
#define EQUAL 0
#define GREATER 1
#define LESS (-1)
/*
* search --
* Search a file for a tag.
*/
static int
search(fname, tname, tag)
char *fname, *tname, **tag;
{
struct stat sb;
int fd, len;
char *endp, *back, *front, *p;
if ((fd = open(fname, O_RDONLY, 0)) < 0)
return (1);
if (fstat(fd, &sb) || (front = mmap(NULL,
sb.st_size, PROT_READ, 0, fd, (off_t)0)) == (caddr_t)-1) {
(void)close(fd);
return (1);
}
back = front + sb.st_size;
front = binary_search(tname, front, back);
front = linear_search(tname, front, back);
if (front == NULL)
goto done;
if ((endp = strchr(front, '\n')) == NULL)
goto done;
len = endp - front;
if ((p = malloc(len + 1)) == NULL) {
done: (void)close(fd);
*tag = NULL;
return (0);
}
memmove(p, front, len);
p[len] = '\0';
(void)close(fd);
*tag = p;
return (0);
}
/*
* Binary search for "string" in memory between "front" and "back".
*
* This routine is expected to return a pointer to the start of a line at
* *or before* the first word matching "string". Relaxing the constraint
* this way simplifies the algorithm.
*
* Invariants:
* front points to the beginning of a line at or before the first
* matching string.
*
* back points to the beginning of a line at or after the first
* matching line.
*
* Base of the Invariants.
* front = NULL;
* back = EOF;
*
* Advancing the Invariants:
*
* p = first newline after halfway point from front to back.
*
* If the string at "p" is not greater than the string to match,
* p is the new front. Otherwise it is the new back.
*
* Termination:
*
* The definition of the routine allows it return at any point,
* since front is always at or before the line to print.
*
* In fact, it returns when the chosen "p" equals "back". This
* implies that there exists a string is least half as long as
* (back - front), which in turn implies that a linear search will
* be no more expensive than the cost of simply printing a string or two.
*
* Trying to continue with binary search at this point would be
* more trouble than it's worth.
*/
#define SKIP_PAST_NEWLINE(p, back) while (p < back && *p++ != '\n');
static char *
binary_search(string, front, back)
register char *string, *front, *back;
{
register char *p;
p = front + (back - front) / 2;
SKIP_PAST_NEWLINE(p, back);
while (p != back) {
if (compare(string, p, back) == GREATER)
front = p;
else
back = p;
p = front + (back - front) / 2;
SKIP_PAST_NEWLINE(p, back);
}
return (front);
}
/*
* Find the first line that starts with string, linearly searching from front
* to back.
*
* Return NULL for no such line.
*
* This routine assumes:
*
* o front points at the first character in a line.
* o front is before or at the first line to be printed.
*/
static char *
linear_search(string, front, back)
char *string, *front, *back;
{
while (front < back) {
switch (compare(string, front, back)) {
case EQUAL: /* Found it. */
return (front);
break;
case LESS: /* No such string. */
return (NULL);
break;
case GREATER: /* Keep going. */
break;
}
SKIP_PAST_NEWLINE(front, back);
}
return (NULL);
}
/*
* Return LESS, GREATER, or EQUAL depending on how the string1 compares with
* string2 (s1 ??? s2).
*
* o Matches up to len(s1) are EQUAL.
* o Matches up to len(s2) are GREATER.
*
* The string "s1" is null terminated. The string s2 is '\n' terminated (or
* "back" terminated).
*/
static int
compare(s1, s2, back)
register char *s1, *s2, *back;
{
for (; *s1 && s2 < back && *s2 != '\n'; ++s1, ++s2)
if (*s1 != *s2)
return (*s1 < *s2 ? LESS : GREATER);
return (*s1 ? GREATER : EQUAL);
}