Changeset View
Changeset View
Standalone View
Standalone View
contrib/mg/cscope.c
- This file was added.
/* $OpenBSD: cscope.c,v 1.20 2021/03/01 10:51:14 lum Exp $ */ | |||||
/* | |||||
* This file is in the public domain. | |||||
* | |||||
* Author: Sunil Nimmagadda <sunil@openbsd.org> | |||||
*/ | |||||
#include <sys/queue.h> | |||||
#include <sys/stat.h> | |||||
#include <sys/types.h> | |||||
#include <ctype.h> | |||||
#include <errno.h> | |||||
#include <fcntl.h> | |||||
#include <fnmatch.h> | |||||
#include <limits.h> | |||||
#include <signal.h> | |||||
#include <stdio.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include <unistd.h> | |||||
#include "def.h" | |||||
#define CSSYMBOL 0 | |||||
#define CSDEFINITION 1 | |||||
#define CSCALLEDFUNCS 2 | |||||
#define CSCALLERFUNCS 3 | |||||
#define CSTEXT 4 | |||||
#define CSEGREP 6 | |||||
#define CSFINDFILE 7 | |||||
#define CSINCLUDES 8 | |||||
struct cstokens { | |||||
const char *fname; | |||||
const char *function; | |||||
const char *lineno; | |||||
const char *pattern; | |||||
}; | |||||
struct csmatch { | |||||
TAILQ_ENTRY(csmatch) entry; | |||||
int lineno; | |||||
}; | |||||
struct csrecord { | |||||
TAILQ_ENTRY(csrecord) entry; | |||||
char *filename; | |||||
TAILQ_HEAD(matches, csmatch) matches; | |||||
}; | |||||
static TAILQ_HEAD(csrecords, csrecord) csrecords = TAILQ_HEAD_INITIALIZER(csrecords); | |||||
static struct csrecord *addentryr; | |||||
static struct csrecord *currecord; | |||||
static struct csmatch *curmatch; | |||||
static const char *addentryfn; | |||||
static const char *csprompt[] = { | |||||
"Find this symbol: ", | |||||
"Find this global definition: ", | |||||
"Find functions called by this function: ", | |||||
"Find functions calling this function: ", | |||||
"Find this text string: ", | |||||
"Change this text string: ", | |||||
"Find this egrep pattern: ", | |||||
"Find this file: ", | |||||
"Find files #including this file: " | |||||
}; | |||||
static int addentry(struct buffer *, char *); | |||||
static void csflush(void); | |||||
static int do_cscope(int); | |||||
static int csexists(const char *); | |||||
static int getattr(char *, struct cstokens *); | |||||
static int jumptomatch(void); | |||||
static void prettyprint(struct buffer *, struct cstokens *); | |||||
static const char *ltrim(const char *); | |||||
/* | |||||
* Find this symbol. Bound to C-c s s | |||||
*/ | |||||
/* ARGSUSED */ | |||||
int | |||||
cssymbol(int f, int n) | |||||
{ | |||||
return (do_cscope(CSSYMBOL)); | |||||
} | |||||
/* | |||||
* Find this global definition. Bound to C-c s d | |||||
*/ | |||||
/* ARGSUSED */int | |||||
csdefinition(int f, int n) | |||||
{ | |||||
return (do_cscope(CSDEFINITION)); | |||||
} | |||||
/* | |||||
* Find functions called by this function. Bound to C-c s l | |||||
*/ | |||||
/* ARGSUSED */ | |||||
int | |||||
csfuncalled(int f, int n) | |||||
{ | |||||
return (do_cscope(CSCALLEDFUNCS)); | |||||
} | |||||
/* | |||||
* Find functions calling this function. Bound to C-c s c | |||||
*/ | |||||
/* ARGSUSED */ | |||||
int | |||||
cscallerfuncs(int f, int n) | |||||
{ | |||||
return (do_cscope(CSCALLERFUNCS)); | |||||
} | |||||
/* | |||||
* Find this text. Bound to C-c s t | |||||
*/ | |||||
/* ARGSUSED */ | |||||
int | |||||
csfindtext(int f, int n) | |||||
{ | |||||
return (do_cscope(CSTEXT)); | |||||
} | |||||
/* | |||||
* Find this egrep pattern. Bound to C-c s e | |||||
*/ | |||||
/* ARGSUSED */ | |||||
int | |||||
csegrep(int f, int n) | |||||
{ | |||||
return (do_cscope(CSEGREP)); | |||||
} | |||||
/* | |||||
* Find this file. Bound to C-c s f | |||||
*/ | |||||
/* ARGSUSED */ | |||||
int | |||||
csfindfile(int f, int n) | |||||
{ | |||||
return (do_cscope(CSFINDFILE)); | |||||
} | |||||
/* | |||||
* Find files #including this file. Bound to C-c s i | |||||
*/ | |||||
/* ARGSUSED */ | |||||
int | |||||
csfindinc(int f, int n) | |||||
{ | |||||
return (do_cscope(CSINCLUDES)); | |||||
} | |||||
/* | |||||
* Create list of files to index in the given directory | |||||
* using cscope-indexer. | |||||
*/ | |||||
/* ARGSUSED */ | |||||
int | |||||
cscreatelist(int f, int n) | |||||
{ | |||||
struct buffer *bp; | |||||
struct stat sb; | |||||
FILE *fpipe; | |||||
char dir[NFILEN], cmd[BUFSIZ], title[BUFSIZ], *line, *bufp; | |||||
size_t sz; | |||||
ssize_t len; | |||||
int clen; | |||||
line = NULL; | |||||
sz = 0; | |||||
if (getbufcwd(dir, sizeof(dir)) == FALSE) | |||||
dir[0] = '\0'; | |||||
bufp = eread("Index files in directory: ", dir, | |||||
sizeof(dir), EFCR | EFDEF | EFNEW | EFNUL); | |||||
if (bufp == NULL) | |||||
return (ABORT); | |||||
else if (bufp[0] == '\0') | |||||
return (FALSE); | |||||
if (stat(dir, &sb) == -1) | |||||
return(dobeep_msgs("stat: %s", strerror(errno))); | |||||
else if (S_ISDIR(sb.st_mode) == 0) | |||||
return(dobeep_msgs("%s: Not a directory", dir)); | |||||
if (csexists("cscope-indexer") == FALSE) | |||||
return(dobeep_msg("no such file or directory, cscope-indexer")); | |||||
clen = snprintf(cmd, sizeof(cmd), "cscope-indexer -v %s", dir); | |||||
if (clen < 0 || clen >= sizeof(cmd)) | |||||
return (FALSE); | |||||
if ((fpipe = popen(cmd, "r")) == NULL) | |||||
return(dobeep_msg("problem opening pipe")); | |||||
bp = bfind("*cscope*", TRUE); | |||||
if (bclear(bp) != TRUE) { | |||||
pclose(fpipe); | |||||
return (FALSE); | |||||
} | |||||
bp->b_flag |= BFREADONLY; | |||||
clen = snprintf(title, sizeof(title), "%s%s", | |||||
"Creating cscope file list 'cscope.files' in: ", dir); | |||||
if (clen < 0 || clen >= sizeof(title)) { | |||||
pclose(fpipe); | |||||
return (FALSE); | |||||
} | |||||
addline(bp, title); | |||||
addline(bp, ""); | |||||
while ((len = getline(&line, &sz, fpipe)) != -1) { | |||||
if (line[len - 1] == *bp->b_nlchr) | |||||
line[len - 1] = '\0'; | |||||
addline(bp, line); | |||||
} | |||||
free(line); | |||||
if (ferror(fpipe)) | |||||
ewprintf("Problem reading pipe"); | |||||
pclose(fpipe); | |||||
return (popbuftop(bp, WNONE)); | |||||
} | |||||
/* | |||||
* Next Symbol. Bound to C-c s n | |||||
*/ | |||||
/* ARGSUSED */ | |||||
int | |||||
csnextmatch(int f, int n) | |||||
{ | |||||
struct csrecord *r; | |||||
struct csmatch *m; | |||||
if (curmatch == NULL) { | |||||
if ((r = TAILQ_FIRST(&csrecords)) == NULL) | |||||
return(dobeep_msg("The *cscope* buffer does " | |||||
"not exist yet")); | |||||
currecord = r; | |||||
curmatch = TAILQ_FIRST(&r->matches); | |||||
} else { | |||||
m = TAILQ_NEXT(curmatch, entry); | |||||
if (m == NULL) { | |||||
r = TAILQ_NEXT(currecord, entry); | |||||
if (r == NULL) { | |||||
return(dobeep_msg("The end of *cscope* buffer " | |||||
"has been reached")); | |||||
} else { | |||||
currecord = r; | |||||
curmatch = TAILQ_FIRST(&currecord->matches); | |||||
} | |||||
} else | |||||
curmatch = m; | |||||
} | |||||
return (jumptomatch()); | |||||
} | |||||
/* | |||||
* Previous Symbol. Bound to C-c s p | |||||
*/ | |||||
/* ARGSUSED */ | |||||
int | |||||
csprevmatch(int f, int n) | |||||
{ | |||||
struct csmatch *m; | |||||
struct csrecord *r; | |||||
if (curmatch == NULL) | |||||
return (FALSE); | |||||
else { | |||||
m = TAILQ_PREV(curmatch, matches, entry); | |||||
if (m) | |||||
curmatch = m; | |||||
else { | |||||
r = TAILQ_PREV(currecord, csrecords, entry); | |||||
if (r == NULL) { | |||||
return(dobeep_msg("The beginning of *cscope* " | |||||
"buffer has been reached")); | |||||
} else { | |||||
currecord = r; | |||||
curmatch = TAILQ_LAST(&currecord->matches, | |||||
matches); | |||||
} | |||||
} | |||||
} | |||||
return (jumptomatch()); | |||||
} | |||||
/* | |||||
* Next file. | |||||
*/ | |||||
int | |||||
csnextfile(int f, int n) | |||||
{ | |||||
struct csrecord *r; | |||||
if (curmatch == NULL) { | |||||
if ((r = TAILQ_FIRST(&csrecords)) == NULL) | |||||
return(dobeep_msg("The *cscope* buffer does not " | |||||
"exist yet")); | |||||
} else { | |||||
if ((r = TAILQ_NEXT(currecord, entry)) == NULL) | |||||
return(dobeep_msg("The end of *cscope* buffer has " | |||||
"been reached")); | |||||
} | |||||
currecord = r; | |||||
curmatch = TAILQ_FIRST(&currecord->matches); | |||||
return (jumptomatch()); | |||||
} | |||||
/* | |||||
* Previous file. | |||||
*/ | |||||
int | |||||
csprevfile(int f, int n) | |||||
{ | |||||
struct csrecord *r; | |||||
if (curmatch == NULL) { | |||||
if ((r = TAILQ_FIRST(&csrecords)) == NULL) | |||||
return(dobeep_msg("The *cscope* buffer does not" | |||||
"exist yet")); | |||||
} else { | |||||
if ((r = TAILQ_PREV(currecord, csrecords, entry)) == NULL) | |||||
return(dobeep_msg("The beginning of *cscope* buffer " | |||||
"has been reached")); | |||||
} | |||||
currecord = r; | |||||
curmatch = TAILQ_FIRST(&currecord->matches); | |||||
return (jumptomatch()); | |||||
} | |||||
/* | |||||
* The current symbol location is extracted from currecord->filename and | |||||
* curmatch->lineno. Load the file similar to filevisit and goto the | |||||
* lineno recorded. | |||||
*/ | |||||
int | |||||
jumptomatch(void) | |||||
{ | |||||
struct buffer *bp; | |||||
char *adjf; | |||||
if (curmatch == NULL || currecord == NULL) | |||||
return (FALSE); | |||||
adjf = adjustname(currecord->filename, TRUE); | |||||
if (adjf == NULL) | |||||
return (FALSE); | |||||
if ((bp = findbuffer(adjf)) == NULL) | |||||
return (FALSE); | |||||
curbp = bp; | |||||
if (showbuffer(bp, curwp, WFFULL) != TRUE) | |||||
return (FALSE); | |||||
if (bp->b_fname[0] == '\0') { | |||||
if (readin(adjf) != TRUE) | |||||
killbuffer(bp); | |||||
} | |||||
gotoline(FFARG, curmatch->lineno); | |||||
return (TRUE); | |||||
} | |||||
/* | |||||
* Ask for the symbol, construct cscope commandline with the symbol | |||||
* and passed in index. Popen cscope, read the output into *cscope* | |||||
* buffer and pop it. | |||||
*/ | |||||
int | |||||
do_cscope(int i) | |||||
{ | |||||
struct buffer *bp; | |||||
FILE *fpipe; | |||||
char pattern[MAX_TOKEN], cmd[BUFSIZ], title[BUFSIZ]; | |||||
char *p, *buf; | |||||
int clen, nores = 0; | |||||
size_t sz; | |||||
ssize_t len; | |||||
buf = NULL; | |||||
sz = 0; | |||||
/* If current buffer isn't a source file just return */ | |||||
if (fnmatch("*.[chy]", curbp->b_fname, 0) != 0) | |||||
return(dobeep_msg("C-c s not defined")); | |||||
if (curtoken(0, 1, pattern) == FALSE) | |||||
return (FALSE); | |||||
p = eread("%s", pattern, MAX_TOKEN, EFNEW | EFCR | EFDEF, csprompt[i]); | |||||
if (p == NULL) | |||||
return (ABORT); | |||||
else if (p[0] == '\0') | |||||
return (FALSE); | |||||
if (csexists("cscope") == FALSE) | |||||
return(dobeep_msg("no such file or directory, cscope")); | |||||
csflush(); | |||||
clen = snprintf(cmd, sizeof(cmd), "cscope -L -%d %s 2>/dev/null", | |||||
i, pattern); | |||||
if (clen < 0 || clen >= sizeof(cmd)) | |||||
return (FALSE); | |||||
if ((fpipe = popen(cmd, "r")) == NULL) | |||||
return(dobeep_msg("problem opening pipe")); | |||||
bp = bfind("*cscope*", TRUE); | |||||
if (bclear(bp) != TRUE) { | |||||
pclose(fpipe); | |||||
return (FALSE); | |||||
} | |||||
bp->b_flag |= BFREADONLY; | |||||
clen = snprintf(title, sizeof(title), "%s%s", csprompt[i], pattern); | |||||
if (clen < 0 || clen >= sizeof(title)) { | |||||
pclose(fpipe); | |||||
return (FALSE); | |||||
} | |||||
addline(bp, title); | |||||
addline(bp, ""); | |||||
addline(bp, "-------------------------------------------------------------------------------"); | |||||
while ((len = getline(&buf, &sz, fpipe)) != -1) { | |||||
if (buf[len - 1] == *bp->b_nlchr) | |||||
buf[len - 1] = '\0'; | |||||
if (addentry(bp, buf) != TRUE) { | |||||
free(buf); | |||||
return (FALSE); | |||||
} | |||||
nores = 1; | |||||
} | |||||
free(buf); | |||||
if (ferror(fpipe)) | |||||
ewprintf("Problem reading pipe"); | |||||
pclose(fpipe); | |||||
addline(bp, "-------------------------------------------------------------------------------"); | |||||
if (nores == 0) | |||||
ewprintf("No matches were found."); | |||||
return (popbuftop(bp, WNONE)); | |||||
} | |||||
/* | |||||
* For each line read from cscope output, extract the tokens, | |||||
* add them to list and pretty print a line in *cscope* buffer. | |||||
*/ | |||||
int | |||||
addentry(struct buffer *bp, char *csline) | |||||
{ | |||||
struct csrecord *r; | |||||
struct csmatch *m; | |||||
struct cstokens t; | |||||
int lineno; | |||||
char buf[BUFSIZ]; | |||||
const char *errstr; | |||||
r = NULL; | |||||
if (getattr(csline, &t) == FALSE) | |||||
return (FALSE); | |||||
lineno = strtonum(t.lineno, INT_MIN, INT_MAX, &errstr); | |||||
if (errstr) | |||||
return (FALSE); | |||||
if (addentryfn == NULL || strcmp(addentryfn, t.fname) != 0) { | |||||
if ((r = malloc(sizeof(struct csrecord))) == NULL) | |||||
return (FALSE); | |||||
addentryr = r; | |||||
if ((r->filename = strndup(t.fname, NFILEN)) == NULL) | |||||
goto cleanup; | |||||
addentryfn = r->filename; | |||||
TAILQ_INIT(&r->matches); | |||||
if ((m = malloc(sizeof(struct csmatch))) == NULL) | |||||
goto cleanup; | |||||
m->lineno = lineno; | |||||
TAILQ_INSERT_TAIL(&r->matches, m, entry); | |||||
TAILQ_INSERT_TAIL(&csrecords, r, entry); | |||||
addline(bp, ""); | |||||
if (snprintf(buf, sizeof(buf), "*** %s", t.fname) < 0) | |||||
goto cleanup; | |||||
addline(bp, buf); | |||||
} else { | |||||
if ((m = malloc(sizeof(struct csmatch))) == NULL) | |||||
goto cleanup; | |||||
m->lineno = lineno; | |||||
TAILQ_INSERT_TAIL(&addentryr->matches, m, entry); | |||||
} | |||||
prettyprint(bp, &t); | |||||
return (TRUE); | |||||
cleanup: | |||||
free(r); | |||||
return (FALSE); | |||||
} | |||||
/* | |||||
* Cscope line: <filename> <function> <lineno> <pattern> | |||||
*/ | |||||
int | |||||
getattr(char *line, struct cstokens *t) | |||||
{ | |||||
char *p; | |||||
if ((p = strchr(line, ' ')) == NULL) | |||||
return (FALSE); | |||||
*p++ = '\0'; | |||||
t->fname = line; | |||||
line = p; | |||||
if ((p = strchr(line, ' ')) == NULL) | |||||
return (FALSE); | |||||
*p++ = '\0'; | |||||
t->function = line; | |||||
line = p; | |||||
if ((p = strchr(line, ' ')) == NULL) | |||||
return (FALSE); | |||||
*p++ = '\0'; | |||||
t->lineno = line; | |||||
if (*p == '\0') | |||||
return (FALSE); | |||||
t->pattern = p; | |||||
return (TRUE); | |||||
} | |||||
void | |||||
prettyprint(struct buffer *bp, struct cstokens *t) | |||||
{ | |||||
char buf[BUFSIZ]; | |||||
if (snprintf(buf, sizeof(buf), "%s[%s]\t\t%s", | |||||
t->function, t->lineno, ltrim(t->pattern)) < 0) | |||||
return; | |||||
addline(bp, buf); | |||||
} | |||||
const char * | |||||
ltrim(const char *s) | |||||
{ | |||||
while (isblank((unsigned char)*s)) | |||||
s++; | |||||
return s; | |||||
} | |||||
void | |||||
csflush(void) | |||||
{ | |||||
struct csrecord *r; | |||||
struct csmatch *m; | |||||
while ((r = TAILQ_FIRST(&csrecords)) != NULL) { | |||||
free(r->filename); | |||||
while ((m = TAILQ_FIRST(&r->matches)) != NULL) { | |||||
TAILQ_REMOVE(&r->matches, m, entry); | |||||
free(m); | |||||
} | |||||
TAILQ_REMOVE(&csrecords, r, entry); | |||||
free(r); | |||||
} | |||||
addentryr = NULL; | |||||
addentryfn = NULL; | |||||
currecord = NULL; | |||||
curmatch = NULL; | |||||
} | |||||
/* | |||||
* Check if the cmd exists in $PATH. Split on ":" and iterate through | |||||
* all paths in $PATH. | |||||
*/ | |||||
int | |||||
csexists(const char *cmd) | |||||
{ | |||||
char fname[NFILEN], *dir, *path, *pathc, *tmp; | |||||
int len, dlen; | |||||
/* Special case if prog contains '/' */ | |||||
if (strchr(cmd, '/')) { | |||||
if (access(cmd, F_OK) == -1) | |||||
return (FALSE); | |||||
else | |||||
return (TRUE); | |||||
} | |||||
if ((tmp = getenv("PATH")) == NULL) | |||||
return (FALSE); | |||||
if ((pathc = path = strndup(tmp, NFILEN)) == NULL) | |||||
return(dobeep_msg("out of memory")); | |||||
while ((dir = strsep(&path, ":")) != NULL) { | |||||
if (*dir == '\0') | |||||
continue; | |||||
dlen = strlen(dir); | |||||
while (dlen > 0 && dir[dlen-1] == '/') | |||||
dir[--dlen] = '\0'; /* strip trailing '/' */ | |||||
len = snprintf(fname, sizeof(fname), "%s/%s", dir, cmd); | |||||
if (len < 0 || len >= sizeof(fname)) { | |||||
(void)dobeep_msg("path too long"); | |||||
goto cleanup; | |||||
} | |||||
if(access(fname, F_OK) == 0) { | |||||
free(pathc); | |||||
return (TRUE); | |||||
} | |||||
} | |||||
cleanup: | |||||
free(pathc); | |||||
return (FALSE); | |||||
} |