/* lqshow.c -- Copyright 1989, 1990, 1993-1995, 1996 Liam R. E. Quin. * All Rights Reserved. * This code is NOT in the public domain. * See the file COPYRIGHT for full details. * * lqshow -- show a file according to keywords, highlighting matches * Liam R. Quin, September 1989 and later... * * $Id: lqshow.c,v 1.36 96/07/04 21:19:37 lee Exp $ */ #include "error.h" #include "globals.h" /* TODO: use SetErrorHandler() and avoid normal lq-text Error() */ #define NBLOCKS 5 #ifdef HAVE_STRING_H # include #else # include #endif #ifdef ultrix # include #else # include #endif #include #include /* for fileinfo.h */ #include #include /* declaration of pid_t wait(int *loc) */ #include #ifdef HAVE_FCNTL_H # include #endif #ifdef HAVE_STDLIB_H # include #else # include #endif #ifdef HAVE_UNISTD_H # include #endif /* Check for old (or BSD) curses: */ #define HASUNDERLINE #ifndef A_STANDOUT # undef HASUNDERLINE # define A_STANDOUT 10193 /* random */ # define A_UNDERLINE 312 # define attrset(a) ((a == 0) ? standend() : standout()) typedef char chtype; /* long on sysV */ static void beep() { fprintf(stderr, "\007"); (void) fflush(stderr); } #else # ifndef beep extern int beep(); # endif #endif #include "fileinfo.h" #include "wordinfo.h" #include "wordrules.h" #include "pblock.h" #include "emalloc.h" #include "lqutil.h" #include "liblqtext.h" #include "wmoffset.h" #include "lqtrace.h" typedef struct { unsigned int Start, Length; unsigned long WordInBlock; unsigned long BlockInFile; int WordsInPhrase; } t_OneMatch; typedef struct { char *Name; char *FullName; t_OneMatch *Matches; long MatchCount; } t_File; /** Unix system calls that need declaring: **/ /** Unix/C Library Functions that need declaring: **/ /** Curses library functions: **/ #ifndef nonl extern int nonl(); #endif #ifndef noecho extern int noecho(); #endif #ifndef wmove extern int wmove(); #endif #ifndef waddstr extern int waddstr(); #endif #ifndef wrefresh extern int wrefresh(); #endif #ifndef wprintw extern int printw(); #endif #ifndef mvwprintw extern int mvwprintw(); #endif #ifndef delwin extern int delwin(); #endif #ifndef wclear extern int wclear(); #endif #ifndef wclrtoeol extern int wclrtoeol(); #endif #ifndef endwin extern int endwin(); #endif /** lqtext library functions that need declaring: **/ /** Functions within this file that are used before being defined: **/ PRIVATE void PageFileName( #ifdef HAVE_PROTO char *Name #endif ); PRIVATE int ShowFile( #ifdef HAVE_PROTO int theFile #endif ); PRIVATE void Output( #ifdef HAVE_PROTO int ch #endif ); PRIVATE int SysFail( #ifdef HAVE_PROTO char *Action, char *What, char *Noun #endif ); PRIVATE void PageStdin( #ifdef HAVE_PROTO int fd #endif ); PRIVATE int MatchCommand( #ifdef HAVE_PROTO t_File *Filep, int theMatch #endif ); PRIVATE int MakeMatchList( #ifdef HAVE_PROTO int argc, char *argv[] #endif ); PRIVATE long fReadMatchFile( #ifdef HAVE_PROTO FILE *FileWithMatches, int *MatchCount, char ** *MatchList #endif ); /** Commands **/ #define NEXT_MATCH 65537 #define PREV_MATCH 65538 #define SAME_MATCH 65539 #define PREV_FILE 65540 #define NEXT_FILE 65541 #define SAME_FILE 65542 #define FIRST_FILE 65543 #define LAST_FILE 65544 #define FIRST_MATCH 65545 #define LAST_MATCH 65546 #define QUIT 70000 #define SCREEN_FORWARD 80001 #define SCREEN_BACKWARD 80002 #define TOP_OF_FILE 80003 #define END_OF_FILE 80004 #define NEXT_WORD 80005 #define PREV_WORD 80007 /** **/ /* number of lines above and below each match to show by default. */ #define DFLTABOVE 7 #define DFLTBELOW 12 int LinesBelow = DFLTBELOW; int LinesAbove = DFLTABOVE; #define DISPLAY_TOP 2 extern int errno; char *progname; int SelectedNames = -1; FILE *InfoStream = 0; static char *Revision = "@(#) $Id: lqshow.c,v 1.36 96/07/04 21:19:37 lee Exp $"; static int FileCount; static int MatchesHaveWordCount = 1; static int NiceWarnIfEmpty = 0; #define MATCHLEN (4 + MatchesHaveWordCount) static t_LQTEXT_Database *db; int main(argc, argv) int argc; char *argv[]; { extern int optind, getopt(); extern char *optarg; /* for getopt */ int ch; /* for getopt */ int ErrFlag = 0; /* see how getopt makes programs cleaner? */ char *FileWithMatches = (char *) 0; char **MatchList; int MatchCount = 0; int theFile; t_lqdbOptions *Options; progname = argv[0]; Options = LQT_InitFromArgv(argc, argv); /* All lq-text programs must call LQT_InitFromArgv() before getopt, and * must then be prepared to ignore options z with arg and Z without. */ while ((ch = getopt(argc, argv, "a:b:ef:Mo:z:ZVvx")) != EOF) { switch (ch) { case 'z': break; /* done by LQT_InitFromArgv(); */ case 'V': fprintf(stderr, "%s version %s\n", progname, Revision); break; case 'a': /* lines above */ LinesAbove = atoi(optarg); /* need cknum() */ break; case 'b': LinesBelow = atoi(optarg); break; case 'e': NiceWarnIfEmpty = 1; break; case 'f': FileWithMatches = optarg; break; case 'M': MatchesHaveWordCount = 0; break; case 'o': /* -o fd --- write the selected files to fp */ if (SelectedNames >= 0) { fprintf(stderr, "%s: -o %d -o %s: you must not give more than one -o option.\n", progname, SelectedNames, optarg); ErrFlag = (-1); } else { if (!isdigit(*optarg)) { fprintf(stderr, "%s: -o must be followed by a number\n", progname); exit(1); } SelectedNames = atoi(optarg); break; } break; case 'x': ErrFlag = (-1); break; case '?': default: ErrFlag = 1; } } if (ErrFlag < 0) { /* -x or -xv was used */ fprintf(stderr, "usage: %s [-xv] [options] [matches...]\n", progname); LQT_PrintDefaultUsage(Options); fprintf(stderr, "\n\ -a above - set the number of lines shown above each matching\n\ match to \"above\" [default is %d]\n", DFLTABOVE); fprintf(stderr, "\ -b below - set the number of lines shown below each match\n\ match to \"above\" [default is %d]\n", DFLTBELOW); fprintf(stderr, "\ -f file -- \"file\" contains a list of matches, one per line\n"); if (LQT_TraceFlagsSet(LQTRACE_VERBOSE|LQTRACE_DEBUG)) { fputs("\ Matches should be in the form of\n\ BlockNumber WordInBlock FileName\n\ where BlockNumber and WordInBlock are positive numbers.\n\ (This is the format produced by the lqword -l command.)\n\ ", stderr); } else { fprintf(stderr, "use %s -x, -xv or -xvv for more detailed explanations.\n", progname ); } exit(0); } else if (ErrFlag > 0) { fprintf(stderr, "use %s -x for an explanation.\n", progname); exit(1); } /* open the file for the selected output */ if (SelectedNames > 0) { if ((InfoStream = fdopen(SelectedNames, "w")) == (FILE *) 0) { Error(E_FATAL|E_SYS, "-o %d: can't open stream for writing", SelectedNames ); } } argv += optind; argc -= optind; db = LQT_OpenDatabase(Options, O_RDONLY, 0); if (!db) { Error(E_FATAL, "couldn't open database.\n"); exit(1); } /* check that we can get at the file containing the matches, if one * was supplied. */ if (FileWithMatches) { if (strcmp(FileWithMatches, "-") == 0) { /* read stdin to EOF, then use /dev/tty */ int i = dup(fileno(stdin)); FILE *f; FILE *tty = fopen("/dev/tty", "rw"); if (i < 0) { Error(E_FATAL|E_SYS, "couldn't read match list; fdup(%d) failed", fileno(stdin) ); } if ((f = fdopen(i, "r")) == (FILE *) 0) { Error(E_FATAL|E_SYS, "couldn't read match list; fdopen(%d, \"r\") failed", i ); } if (fReadMatchFile(f, &MatchCount, &MatchList) < 0) { Error(E_FATAL, "couldn't read list of matches from stdin"); } (void) fclose(f); freopen("/dev/tty", "rw", stdin); (void) fclose(tty); /* this is just so /dev/tty will work */ } else { FILE *f = LQU_fEopen( E_FATAL, FileWithMatches, "list of matches", "r" ); /* Now read the file, and make an array of matches... */ if (fReadMatchFile(f, &MatchCount, &MatchList) < 0) { Error(E_FATAL|E_SYS, "couldn't read matches from \"%s\"", FileWithMatches ); } (void) fclose(f); } } if (MatchCount) { argc = MatchCount; argv = MatchList; } else if (argc == 0) { if (fReadMatchFile(stdin, &MatchCount, &MatchList) < 0) { Error(E_FATAL|E_SYS, "couldn't read matches from standard input" ); } argc = MatchCount; argv = MatchList; } if (argc < MATCHLEN) { Error(E_FATAL|E_USAGE|E_XHINT, "Matches must have at least %d parts", MATCHLEN ); } else if (argc % MATCHLEN) { /* Note: I could detect lqword output here (i.e., without -l) */ Error(E_FATAL|E_USAGE|E_XHINT, "can't understand match format [E%d]", argc ); } (void) MakeMatchList(argc, argv); theFile = 0; initscr(); nonl(); raw(); noecho(); if (LinesAbove > LINES - 6) { LinesAbove = 1; } if (LinesBelow > LINES + 4 + LinesAbove) { LinesBelow = LINES - (LinesAbove + 4); } while (theFile < FileCount) { theFile = ShowFile(theFile); if (theFile < 0) { break; } } wmove(stdscr, LINES - 1, 0); clrtoeol(); /* Try to revent the screen from scrolling when we exit */ wmove(stdscr, LINES - 2, 0); refresh(); endwin(); return 0; } static int CompareMatchPointers(Mpp1, Mpp2) t_OneMatch *Mpp1, *Mpp2; { long L; if ((Mpp1)->BlockInFile == ((Mpp2)->BlockInFile)) { L = (Mpp1)->WordInBlock - (Mpp2)->WordInBlock; } else { L = (Mpp1)->BlockInFile - (Mpp2)->BlockInFile; } /* Since these are all longs, we have to convert the result to * an int before returning it, but we can't simply use truncation... */ if (L > 0) return 1; else if (L == 0) return 0; else return -1; } static t_File *Files; static int MakeMatchList(argc, argv) int argc; char *argv[]; { int i; char *tmp; register t_File *Filep; /* count the files */ tmp = argv[MATCHLEN - 1]; FileCount = 1; for (i = MATCHLEN; i < argc; i += MATCHLEN) { if (!STREQ(argv[i + MATCHLEN - 1], tmp)) { ++FileCount; tmp = argv[i + MATCHLEN - 1]; } } /* FileCount is now a maximum; in practice, a given filename may * be repeated if the input list isn't sorted by filename. * Hence, FileCount is now simply an upper bound. */ Files = (t_File *) emalloc("lqshow:FileCount",sizeof(t_File) * FileCount); /* Now we initialise a file struct for each file, and this time * we ensure that there is only one struct for each file, so that * FileCount will be accurate. If necessary, we'll return any * extra storage to free() afterwards. */ FileCount = 0; for (i = 0; i < argc; i += MATCHLEN) { int Found = 0; for (Filep = &Files[0]; Filep - Files < FileCount; Filep++) { if (STREQ(Filep->Name, argv[i + MATCHLEN - 1])) { Found = 1; break; } } if (!Found) { Filep = &Files[FileCount]; Filep->Name = argv[i + MATCHLEN - 1]; Filep->MatchCount = 1; Filep->Matches = (t_OneMatch *) 0; Filep->FullName = (char *) 0; ++FileCount; } else { Filep->MatchCount++; } } /* Now every file has an entry in Files, and since we know how * many files there are, we can reduce the size of the array if * it was too big. */ Files = (t_File *) erealloc((char *) Files, sizeof(t_File) * FileCount); /* Now we'll make the match structures for each file. */ Filep = &Files[0]; for (i = 0; i < argc; i += MATCHLEN) { /** add the new entry to the right file **/ /* first, find the file's entry in "Files": */ if (!STREQ(Filep->Name, argv[i + MATCHLEN - 1])) { for (Filep = &Files[0]; Filep - Files < FileCount; Filep++) { if (STREQ(Filep->Name, argv[i + MATCHLEN - 1])) { break; } } } /* found it */ /* ASSERT (STREQ(Filep->Name, argv[i + MATCHLEN - 1])) */ if (!STREQ(Filep->Name, argv[i + MATCHLEN - 1])) { Error(E_FATAL|E_BUG, "%s: %d: assertion failed: Filep->Name %s not %s", __FILE__, __LINE__, Filep->Name, argv[i + MATCHLEN - 1] ); } /* Now, add the match */ if (!Filep->Matches) { Filep->Matches = (t_OneMatch *) emalloc( "lqshow:MakeMatchList.Matches", sizeof(t_OneMatch) * Filep->MatchCount ); Filep->MatchCount = 0; } { register t_OneMatch *Mp = &Filep->Matches[Filep->MatchCount]; Mp->Start = Mp->Length = 0L; /* unassigned */ if (MatchesHaveWordCount) { Mp->WordsInPhrase = atoi(argv[i]); Mp->BlockInFile = atol(argv[i + 1]); Mp->WordInBlock = atol(argv[i + 2]); } else { Mp->WordsInPhrase = 1; Mp->BlockInFile = atol(argv[i]); Mp->WordInBlock = atol(argv[i + 1]); } Filep->MatchCount++; } } /* now sort the matches */ for (Filep = &Files[0]; Filep - Files < FileCount; Filep++) { (void) qsort( (char *) &Filep->Matches[0], Filep->MatchCount, sizeof (t_OneMatch), CompareMatchPointers ); } return FileCount; } PRIVATE long fReadMatchFile(FileWithMatches, MatchCount, MatchList) FILE *FileWithMatches; int *MatchCount; char ** *MatchList; { char **Lines; char **Result; long n_matches; int i; char **Lppp; if (!FileWithMatches) { Error(E_FATAL, "match-list file (from -f -) has NULL file!"); } n_matches = LQU_fReadFile( E_FATAL, FileWithMatches, "(standard input)", "match list", &Lines, /* yes, a (char ***) */ LQUF_IGNBLANKS|LQUF_IGNSPACES|LQUF_IGNHASH|LQUF_ESCAPEOK ); if (n_matches < 1L) { if (NiceWarnIfEmpty) { fprintf(stderr, "No matches were found in the database.\n"); exit(1); } else { Error(E_FATAL, "Match file \"%s\" contains no matches", FileWithMatches ); } } Result = (char **) malloc((unsigned) n_matches * MATCHLEN * sizeof(char *)); if (Result == (char **) 0) { Error(E_FATAL|E_MEMORY, "%u bytes for match list \"%s\"", (unsigned) n_matches * sizeof(char *) * MATCHLEN, "standard input" ); } /* Now construc a new argv[] from the file we just read */ Lppp = &Result[0]; for (i = 0; i < n_matches; i++) { register char *p; p = Lines[i]; /* ASSERT: There are no leading or trailing spaces on the line */ if (!*p) { continue; /* blank line */ } if (MatchesHaveWordCount) { *Lppp++ = p; while (*p && !isspace(*p)) { p++; } *p = '\0'; /* terminate the string */ /* move to the start of the next field: */ do { p++; } while (*p && isspace(*p)); } /* block in file */ *Lppp++ = p; while (*p && !isspace(*p)) { p++; } *p = '\0'; /* terminate the string */ /* move to the start of the next one: */ do { p++; } while (*p && isspace(*p)); if (!*p) { Error(E_FATAL, "\"%s\": format is: nw bif wib fid [pathname], not \"number\"", "Standard Input" ); } /* word in block */ *Lppp++ = p; /* find the end */ while (*p && !isspace(*p)) { p++; } *p = '\0'; /* terminate the string */ /* move to the start of the next one: */ do { p++; } while (*p && isspace(*p)); if (!*p) { Error(E_FATAL, "\"%s\": contains a line (%s) with no filename", FileWithMatches, Lines[i] ); } /* file number (FID) */ *Lppp++ = p; while (*p && !isspace(*p)) { p++; } *p = '\0'; /* terminate the string */ /* move to the start of the next one: */ do { p++; } while (*p && isspace(*p)); if (!*p) { Error(E_FATAL, "\"%s\": format is: nw bif wib fid [pathname], not \"number\"", "Standard Input" ); } /* file name, already null-terminated */ *Lppp++ = p; } (*MatchList) = Result; return (*MatchCount = Lppp - Result); } static int ThisRow, ThisCol = 0; #ifdef NOTDONE static int ShowScreen(Filep, StartByte) t_File *Filep; long StartByte; { /* find the data */ /* work out how many lines to show */ /* find the end of the displayed data */ /* identify any matches in that region */ /* display the screen, highlighting matches */ } #endif static int ShowOneMatch(Filep, theMatch) t_File *Filep; int theMatch; { static char *Buffer = 0; int fd; static unsigned int BufLen; int AmountRead; register char *p; int LinesFound; char *Start; t_OffsetPair *OffsetPair; t_OneMatch *Matchp; int FirstBlock; Matchp = &Filep->Matches[theMatch]; wclear(stdscr); ThisRow = DISPLAY_TOP; if (Buffer == (char *) 0) { BufLen = COLS * (LinesAbove + LinesBelow + 1) + 1; /* Make enough space for blocks before and after: */ if (BufLen < LQT_FileBlockSize(db) * NBLOCKS) { BufLen = LQT_FileBlockSize(db) * NBLOCKS; } Buffer = emalloc("lqshow screen buffer", BufLen); } fd = -1; errno = 0; if (!Filep->FullName) { fd = LQT_UnpackAndOpen(db, Filep->Name); if (fd < 0) { int e = errno; char *doc; if ((doc = LQT_FindFile(db, Filep->Name)) == (char *) 0) { errno = e; return SysFail("open", "data file", Filep->Name); } fd = LQT_UnpackAndOpen(db, doc); if (fd < 0) { return SysFail("open", "data file", doc); } Filep->FullName = emalloc("lqshow doc name", strlen(doc) + 1); (void) strcpy(Filep->FullName, doc); } else { Filep->FullName = emalloc("FullName", strlen(Filep->Name) + 1); (void) strcpy(Filep->FullName, Filep->Name); } } else { fd = LQT_UnpackAndOpen(db, Filep->FullName); if (fd < 0) { return SysFail("open", "data file", Filep->FullName); } } /* display a helpful message: */ move(DISPLAY_TOP, 0); clrtoeol(); move(DISPLAY_TOP - 1, 0); clrtoeol(); { char oneBuffer[8192]; int len; char *p2; if (LQT_TraceFlagsSet(LQTRACE_VERBOSE|LQTRACE_DEBUG)) { sprintf(oneBuffer, "File %d/%d, Match %d/%ld (Block %lu/Word %lu) in ", (Filep - Files) + 1, /* count from 1, not 0 */ FileCount, theMatch + 1, /* count from 1, not 0! */ Filep->MatchCount, Matchp->BlockInFile, Matchp->WordInBlock ); } else { sprintf(oneBuffer, "File %d/%d, Match %d/%ld in ", (Filep - Files) + 1, /* count from 1, not 0 */ FileCount, theMatch + 1, /* count from 1, not 0! */ Filep->MatchCount ); } mvwprintw(stdscr, 0, 0, oneBuffer); len = strlen(oneBuffer); attrset(A_UNDERLINE); p2 = Filep->FullName; if (strlen(p2) >= COLS - len) { p2 = &p2[strlen(p2) - (COLS + 2 - len)]; } waddstr(stdscr, p2); attrset(0); } ThisRow = DISPLAY_TOP; ThisCol = 0; if (Matchp->BlockInFile < (NBLOCKS + 1) / 2) { FirstBlock = 0; } else { FirstBlock = Matchp->BlockInFile - (NBLOCKS / 2); } if (lseek( fd, (long) (FirstBlock * LQT_FileBlockSize(db)), 0 ) < 0) { int i = SysFail("lseek", "data file", Filep->FullName); (void) close(fd); return i; } if ((AmountRead = read(fd, Buffer, BufLen)) < db->MinWordLength) { (void) close(fd); return SysFail("read block", "data file", Filep->FullName); } (void) close(fd); /* clear the bottom bit of screen */ { register int i; for (i = ThisRow; i < LINES; i++) { move(i, 0); wclrtoeol(stdscr); } } /** Find the required word */ if (!Matchp->Length) { OffsetPair = LQT_FindMatchEnds( db, Buffer, AmountRead, &Buffer[(Matchp->BlockInFile - FirstBlock) * LQT_FileBlockSize(db)], Matchp->BlockInFile - FirstBlock, Matchp->WordInBlock, Matchp->WordsInPhrase ); if (!OffsetPair) { return NEXT_MATCH; } Matchp->Start = OffsetPair->Start - &Buffer[(Matchp->BlockInFile - FirstBlock) * LQT_FileBlockSize(db)]; Matchp->Length = OffsetPair->End - OffsetPair->Start; Start = OffsetPair->Start; } else { Start = &Buffer[ (Matchp->BlockInFile - FirstBlock)*LQT_FileBlockSize(db) + Matchp->Start ]; } /* Find N lines before it */ LinesFound = 0; for (p = Start; p > Buffer; --p) { if (*p == '\n') { if (++LinesFound > LinesAbove) break; } } /* display them */ while (p < Start) { #ifdef ASCIITRACE if (LQT_TraceFlagsSet(LQTRACE_DEBUG)) { if (((p - Buffer) % LQT_FileBlockSize(db)) == 0) { Output('}'); Output('{'); } } #endif Output(*p); /* Output might be a macro later */ p++; } /* find N lines after it */ /* TODO: highlight all of the matches! NOTDONE FIXME */ LinesFound = 0; while (p - Buffer < AmountRead) { if (p == Start) { attrset(A_STANDOUT); } else if (p - Start == Matchp->Length) { attrset(0); } if (*p == '\n') { if (++LinesFound > LinesBelow) break; if (ThisRow >= LINES - 1) break; } #ifdef ASCIITRACE if (LQT_TraceFlagsSet(LQTRACE_DEBUG)) { if (((p - Buffer) % LQT_FileBlockSize(db)) == 0) { Output('}'); Output('{'); } } #endif Output(*p); p++; } attrset(A_STANDOUT); if (Filep - Files == FileCount - 1) { if (theMatch >= Filep->MatchCount - 1) { mvwaddstr(stdscr, LINES - 1, 0, "Press ?, q, or space to quit" ); } else { mvwaddstr(stdscr, LINES - 1, 0, "Press ?, q, or space to see next match" ); } } else { if (theMatch >= Filep->MatchCount - 1) { mvwaddstr(stdscr, LINES - 1, 0, "Press ?, q, or space to see next match" ); } else { mvwaddstr(stdscr, LINES - 1, 0, "Press ?, q, or space to see next file" ); } } attrset(0); (void) refresh(); return MatchCommand(Filep, theMatch); } PRIVATE void Output(ch) int ch; { static int UnderlineFlag = 0; switch(ch) { default: if (iscntrl(ch)) { attrset(A_UNDERLINE); Output('^'); Output(ch ^ 64); attrset(0); return; } if (++ThisCol > COLS) { if (++ThisRow >= LINES - 1) { return; } ThisCol = 0; } if (ThisCol <= 0) { ThisCol = 0; move(ThisRow, ThisCol); clrtoeol(); } wmove(stdscr, ThisRow, ThisCol); if (UnderlineFlag) { int c = inch(); if ((ch == '_' && c && c != ' ') || (c == '_' && ch != '_')) { #ifdef HASUNDERLINE attrset(A_UNDERLINE); #endif if (ch == '_') ch = c; } else { #ifdef HASUNDERLINE attrset(0); /* BUG what if we were standouting? */ #endif UnderlineFlag = 0; } } mvwaddch(stdscr, ThisRow, ThisCol, (chtype) ch); break; case '\b': if (ThisCol > 0) { --ThisCol; UnderlineFlag = 1; } break; case '\t': do { if (++ThisCol >= COLS) { if (++ThisRow >= LINES - 1) { ThisRow = DISPLAY_TOP; } ThisCol = 0; break; } mvwaddch(stdscr, ThisRow, ThisCol, (chtype) ' '); } while (ThisCol & 07); break; case '\r': attrset(A_UNDERLINE); Output('^'); Output('M'); attrset(0); break; case '\f': attrset(A_UNDERLINE); Output('^'); Output('L'); Output('\n'); attrset(0); break; case '\n': if (++ThisRow >= LINES - 1) { ThisRow = DISPLAY_TOP; } ThisCol = (-1); move(ThisRow, 0); clrtoeol(); break; #if 0 /* this code makes spaces look on inside reverse-video matches! */ case ' ': ThisCol++; #endif } } PRIVATE int ShowFile(theFile) int theFile; { t_File *Filep = &Files[theFile]; int theMatch = 0; t_OneMatch *Matchp = 0; do { switch (ShowOneMatch(Filep, theMatch)) { case PREV_WORD: Matchp = &Filep->Matches[theMatch]; Matchp->Length = 0; if (Matchp->WordInBlock != 0) { Matchp->WordInBlock--; } break; case NEXT_WORD: Matchp = &Filep->Matches[theMatch]; Matchp->WordInBlock++; Matchp->Length = 0; break; case NEXT_FILE: return theFile + 1; case NEXT_MATCH: ++theMatch; break; case PREV_MATCH: if (theMatch > 0) { --theMatch; break; } /* else fall through */ case PREV_FILE: if (theFile > 0) { return theFile - 1; } else { beep(); } case QUIT: return -FileCount; case FIRST_MATCH: theMatch = 0; break; case LAST_MATCH: theMatch = Filep->MatchCount - 1; break; case FIRST_FILE: return 0; break; case LAST_FILE: return FileCount - 1; break; case SAME_MATCH: break; default: beep(); } } while (theMatch < Filep->MatchCount); return theFile + 1; } PRIVATE void PageFileName(Name) char *Name; { char *Pager; char Buffer[2048]; int status; Pager = getenv("PAGER"); if (!Pager || !*Pager) { Pager = DEFAULT_PAGER; } (void) sprintf(Buffer, "%s \"%s\"", Pager, Name); LQU_CursesSafeystem(Buffer, &status); } PRIVATE void PageStdin(fd) int fd; { char *Pager; int pid; int i; char *Argv[10]; Pager = getenv("PAGER"); if (!Pager || !*Pager) { Pager = DEFAULT_PAGER; } echo(); nl(); noraw(); endwin(); switch (pid = fork()) { case -1: Error(E_WARN|E_SYS, "couldn't create pager process, fork failed" ); sleep(3); break; case 0: /* child */ for (i = 3; i < 20; i++) { if (i != fd) { (void) close(i); } } (void) close(0); if ((i = dup(fd)) == 0) { close(fd); Argv[0] = "sh"; Argv[1] = "-c"; Argv[2] = Pager; Argv[3] = (char *) 0; execvp("/bin/sh", Argv); } else { Error(E_SYS|E_WARN, "File descripter woes, dup(%d) -> %d, not 0", fd, i ); sleep(2); } /* not normally reached */ exit(1); default: /* loving parent... */ sleep(5); waitpid(pid, &i, 0); /* (void) wait(&i); */ /* ...is as patient as only love can be */ /* we ignore the return status assuming that no other * processes are going to return. It would be better * to use wait3() really. */ } initscr(); nonl(); raw(); noecho(); } static INLINE int Getch() { register int ch; do { ch = getch(); /* simulate a quit on EOF or interrupt, since in raw mode * we don't get interrupt signals: */ if (ch == 003 || ch == 255 || ch == EOF) { return 'q'; } /* ignore control-Q and control-S: */ } while (ch == ('S' ^ 64) || ch == ('Q' ^ 64)); return ch; } PRIVATE int MatchCommand(Filep, theMatch) t_File *Filep; int theMatch; { switch (Getch()) { case '?': case 'x': case 'h': case 'i': #ifdef KEY_HELP case KEY_HELP: #endif { WINDOW *HelpWin = newwin(14, 40, 5, (COLS - 40) / 2); if (HelpWin == (WINDOW *) 0) { (void) beep(); } else { #ifndef ACS_HLINE box(HelpWin, '|', '+'); #else box(HelpWin, 0, 0); /* Versions of curses with ASC_HLINE take 0 to * mean that line-drawing should be done * "properly". */ #endif wmove(HelpWin, 1, 2); mvwprintw(HelpWin, 1,2, "x, ? -- print this explanation"); mvwprintw(HelpWin, 2,2, "space -- go to next match"); mvwprintw(HelpWin, 3,2, "return -- go to next match"); mvwprintw(HelpWin, 4,2, "0, ^, F -- go to First match"); mvwprintw(HelpWin, 5,2, "$, L -- go to the Last match"); mvwprintw(HelpWin, 6,2, "n, + -- go to the next file/match"); mvwprintw(HelpWin, 7,2, "p, - -- go to previous file/match"); mvwprintw(HelpWin, 8,2, "v, - -- view file in $PAGER"); if (InfoStream) { mvwprintw(HelpWin, 9,2, "s, g -- save this filename"); mvwprintw(HelpWin, 10,2, "u, d -- drop this filename"); } mvwprintw(HelpWin, 11,2, "q, Q -- quit browsing"); mvwprintw(HelpWin, 12,2, " (press space to continue) "); wrefresh(HelpWin); if (Getch() == 'q') { return QUIT; } delwin(HelpWin); #ifndef CURSESX /* This is because 4.2 BSD has a brain-dead curses... */ clearok(stdscr, TRUE); wrefresh(stdscr); #endif } } break; case 'q': case 'Q': return QUIT; case '0': /* reset to beginning */ case '1': case 'f': case 'F': case '^': case '6': /* (6 is often unshifted ^) */ return FIRST_FILE; break; case '$': /* to the end */ case 'l': case 'L': /* Last match */ return LAST_FILE; case 'v': /* view the file -- use PAGER */ { #ifndef DEFAULT_PAGER # define DEFAULT_PAGER "more" #endif char *doc; if ((doc = LQT_FindFile(db, Filep->Name)) == (char *) 0) { int fd = LQT_UnpackAndOpen(db, Filep->FullName); if (fd >= 0) { PageStdin(fd); } else { (void) SysFail("find file", "document", Filep->FullName); } } else { PageFileName(doc); } } return SAME_MATCH; case 's': /* keep this filename for later use */ case 'k': case 'g': /* keep, get */ if (InfoStream) { fprintf(InfoStream, "%c %s\n", 's', Filep->FullName); } else { (void) beep(); } break; case 'd': /* delete this file from the list */ case 'u': if (InfoStream) { fprintf(InfoStream, "%c %s\n", 'd', Filep->FullName); } else { (void) beep(); } break; case 'p': return PREV_FILE; case 'n': return NEXT_FILE; case 'R' ^ 64: /* control-R */ case 'L' ^ 64: /* control-L */ clearok(stdscr, TRUE); wrefresh(stdscr); break; case ' ': case '\r': case '\n': case '+': return NEXT_MATCH; case 'b': case '-': return PREV_MATCH; case '<': return PREV_WORD; case '>': return NEXT_WORD; default: (void) beep(); return SAME_MATCH; } return SAME_MATCH; } PRIVATE int SysFail(Action, What, Noun) char *Action; char *What; char *Noun; { extern int sys_nerr; /* see perror(3) */ extern CONST char * CHARsys_errlist[]; WINDOW *ErrorWin; int len, i; char *emsg; if (errno < sys_nerr) { emsg = sys_errlist[errno]; } else { emsg = "unknown system error"; } /* do the lengths in order of probably longest first! */ len = strlen(emsg); i = strlen(Noun); if (i > len) { len = i; } if (i > len) len = i; i = strlen(Action); if (i > len) { len = i; } i = strlen(What); if (i > len) { len = i; } ErrorWin = newwin(6, len + 5, 5, (COLS - (len + 4)) / 2); if (ErrorWin == (WINDOW *) 0) { (void) beep(); } else { #ifndef ACS_HLINE box(ErrorWin, '#', '#'); #else box(ErrorWin, 0, 0); /* Versions of curses with ASC_HLINE take 0 to * mean that line-drawing should be done * "properly". */ #endif mvwprintw(ErrorWin, 1,2, "Can't %s", Action); mvwprintw(ErrorWin, 2,2, What), mvwprintw(ErrorWin, 3,2, Noun); mvwprintw(ErrorWin, 4,2, emsg); wrefresh(ErrorWin); if (Getch() == 'q') { return QUIT; } delwin(ErrorWin); #ifndef CURSESX /* This is because 4.2 BSD has a brain-dead curses... */ clearok(stdscr, TRUE); wrefresh(stdscr); #endif } return NEXT_FILE; }