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); | |||||
| } | |||||