xref: /csrg-svn/old/more/more.c (revision 13536)
1 static	char *sccsid = "@(#)more.c	4.13 (Berkeley) 83/07/01";
2 
3 /*
4 ** more.c - General purpose tty output filter and file perusal program
5 **
6 **	by Eric Shienbrood, UC Berkeley
7 **
8 **	modified by Geoff Peck, UCB to add underlining, single spacing
9 **	modified by John Foderaro, UCB to add -c and MORE environment variable
10 */
11 
12 #include <stdio.h>
13 #include <ctype.h>
14 #include <signal.h>
15 #include <errno.h>
16 #include <sgtty.h>
17 #include <setjmp.h>
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <local/uparm.h>
21 
22 /* Help file will eventually go in libpath(more.help) on all systems */
23 
24 #define HELPFILE	libpath(more.help)
25 #define VI		binpath(vi)
26 
27 #define Fopen(s,m)	(Currline = 0,file_pos=0,fopen(s,m))
28 #define Ftell(f)	file_pos
29 #define Fseek(f,off)	(file_pos=off,fseek(f,off,0))
30 #define Getc(f)		(++file_pos, getc(f))
31 #define Ungetc(c,f)	(--file_pos, ungetc(c,f))
32 
33 #define MBIT	CBREAK
34 #define stty(fd,argp)	ioctl(fd,TIOCSETN,argp)
35 
36 #define TBUFSIZ	1024
37 #define LINSIZ	256
38 #define ctrl(letter)	('letter' & 077)
39 #define RUBOUT	'\177'
40 #define ESC	'\033'
41 #define QUIT	'\034'
42 
43 struct sgttyb 	otty;
44 long		file_pos, file_size;
45 int		fnum, no_intty, no_tty, slow_tty;
46 int		dum_opt, dlines, onquit(), end_it();
47 int		onsusp();
48 int		nscroll = 11;	/* Number of lines scrolled by 'd' */
49 int		fold_opt = 1;	/* Fold long lines */
50 int		stop_opt = 1;	/* Stop after form feeds */
51 int		ssp_opt = 0;	/* Suppress white space */
52 int		ul_opt = 1;	/* Underline as best we can */
53 int		promptlen;
54 int		Currline;	/* Line we are currently at */
55 int		startup = 1;
56 int		firstf = 1;
57 int		notell = 1;
58 int		bad_so;	/* True if overwriting does not turn off standout */
59 int		inwait, Pause, errors;
60 int		within;	/* true if we are within a file,
61 			false if we are between files */
62 int		hard, dumb, noscroll, hardtabs, clreol;
63 int		catch_susp;	/* We should catch the SIGTSTP signal */
64 char		**fnames;	/* The list of file names */
65 int		nfiles;		/* Number of files left to process */
66 char		*shell;		/* The name of the shell to use */
67 int		shellp;		/* A previous shell command exists */
68 char		ch;
69 jmp_buf		restore;
70 char		obuf[BUFSIZ];	/* stdout buffer */
71 char		Line[LINSIZ];	/* Line buffer */
72 int		Lpp = 24;	/* lines per page */
73 char		*Clear;		/* clear screen */
74 char		*eraseln;	/* erase line */
75 char		*Senter, *Sexit;/* enter and exit standout mode */
76 char		*ULenter, *ULexit;	/* enter and exit underline mode */
77 char		*chUL;		/* underline character */
78 char		*chBS;		/* backspace character */
79 char		*Home;		/* go to home */
80 char		*cursorm;	/* cursor movement */
81 char		cursorhome[40];	/* contains cursor movement to home */
82 char		*EodClr;	/* clear rest of screen */
83 char		*tgetstr();
84 int		Mcol = 80;	/* number of columns */
85 int		Wrap = 1;	/* set if automargins */
86 long		fseek();
87 char		*getenv();
88 struct {
89     long chrctr, line;
90 } context, screen_start;
91 extern char	PC;		/* pad character */
92 extern short	ospeed;
93 
94 
95 main(argc, argv)
96 int argc;
97 char *argv[];
98 {
99     register FILE	*f;
100     register char	*s;
101     register char	*p;
102     register char	ch;
103     register int	left;
104     int			prnames = 0;
105     int			initopt = 0;
106     int			srchopt = 0;
107     int			clearit = 0;
108     int			initline;
109     char		initbuf[80];
110     FILE		*checkf();
111 
112     nfiles = argc;
113     fnames = argv;
114     initterm ();
115     if(s = getenv("MORE")) argscan(s);
116     while (--nfiles > 0) {
117 	if ((ch = (*++fnames)[0]) == '-') {
118 	    argscan(*fnames+1);
119 	}
120 	else if (ch == '+') {
121 	    s = *fnames;
122 	    if (*++s == '/') {
123 		srchopt++;
124 		for (++s, p = initbuf; p < initbuf + 79 && *s != '\0';)
125 		    *p++ = *s++;
126 		*p = '\0';
127 	    }
128 	    else {
129 		initopt++;
130 		for (initline = 0; *s != '\0'; s++)
131 		    if (isdigit (*s))
132 			initline = initline*10 + *s -'0';
133 		--initline;
134 	    }
135 	}
136 	else break;
137     }
138     /* allow clreol only if Home and eraseln and EodClr strings are
139      *  defined, and in that case, make sure we are in noscroll mode
140      */
141     if(clreol)
142     {
143 	if ((*Home == '\0') || (*eraseln == '\0') || (*EodClr == '\0'))
144 	    clreol = 0;
145 	else noscroll = 1;
146     }
147 
148     if (dlines == 0)
149 	dlines = Lpp - (noscroll ? 1 : 2);
150     left = dlines;
151     if (nfiles > 1)
152 	prnames++;
153     if (!no_intty && nfiles == 0) {
154 	fputs("Usage: ",stderr);
155 	fputs(argv[0],stderr);
156 	fputs(" [-dfln] [+linenum | +/pattern] name1 name2 ...\n",stderr);
157 	exit(1);
158     }
159     else
160 	f = stdin;
161     if (!no_tty) {
162 	signal(SIGQUIT, onquit);
163 	signal(SIGINT, end_it);
164 	if (signal (SIGTSTP, SIG_IGN) == SIG_DFL) {
165 	    signal(SIGTSTP, onsusp);
166 	    catch_susp++;
167 	}
168 	stty (2, &otty);
169     }
170     if (no_intty) {
171 	if (no_tty)
172 	    copy_file (stdin);
173 	else {
174 	    if ((ch = Getc (f)) == '\f')
175 		doclear();
176 	    else {
177 		Ungetc (ch, f);
178 		if (noscroll && (ch != EOF)) {
179 		    if (clreol)
180 			home ();
181 		    else
182 			doclear ();
183 		}
184 	    }
185 	    if (srchopt)
186 	    {
187 		search (initbuf, stdin, 1);
188 		if (noscroll)
189 		    left--;
190 	    }
191 	    else if (initopt)
192 		skiplns (initline, stdin);
193 	    screen (stdin, left);
194 	}
195 	no_intty = 0;
196 	prnames++;
197 	firstf = 0;
198     }
199 
200     while (fnum < nfiles) {
201 	if ((f = checkf (fnames[fnum], &clearit)) != NULL) {
202 	    context.line = context.chrctr = 0;
203 	    Currline = 0;
204 	    if (firstf) setjmp (restore);
205 	    if (firstf) {
206 		firstf = 0;
207 		if (srchopt)
208 		{
209 		    search (initbuf, f, 1);
210 		    if (noscroll)
211 			left--;
212 		}
213 		else if (initopt)
214 		    skiplns (initline, f);
215 	    }
216 	    else if (fnum < nfiles && !no_tty) {
217 		setjmp (restore);
218 		left = command (fnames[fnum], f);
219 	    }
220 	    if (left != 0) {
221 		if ((noscroll || clearit) && (file_size != 0x7fffffffffffffffL))
222 		    if (clreol)
223 			home ();
224 		    else
225 			doclear ();
226 		if (prnames) {
227 		    if (bad_so)
228 			erase (0);
229 		    if (clreol)
230 			cleareol ();
231 		    pr("::::::::::::::");
232 		    if (promptlen > 14)
233 			erase (14);
234 		    printf ("\n");
235 		    if(clreol) cleareol();
236 		    printf("%s\n", fnames[fnum]);
237 		    if(clreol) cleareol();
238 		    printf("::::::::::::::\n", fnames[fnum]);
239 		    if (left > Lpp - 4)
240 			left = Lpp - 4;
241 		}
242 		if (no_tty)
243 		    copy_file (f);
244 		else {
245 		    within++;
246 		    screen(f, left);
247 		    within = 0;
248 		}
249 	    }
250 	    setjmp (restore);
251 	    fflush(stdout);
252 	    fclose(f);
253 	    screen_start.line = screen_start.chrctr = 0L;
254 	    context.line = context.chrctr = 0L;
255 	}
256 	fnum++;
257 	firstf = 0;
258     }
259     reset_tty ();
260     exit(0);
261 }
262 
263 argscan(s)
264 char *s;
265 {
266 	for (dlines = 0; *s != '\0'; s++)
267 	{
268 		switch (*s)
269 		{
270 		  case '0': case '1': case '2':
271 		  case '3': case '4': case '5':
272 		  case '6': case '7': case '8':
273 		  case '9':
274 			dlines = dlines*10 + *s - '0';
275 			break;
276 		  case 'd':
277 			dum_opt = 1;
278 			break;
279 		  case 'l':
280 			stop_opt = 0;
281 			break;
282 		  case 'f':
283 			fold_opt = 0;
284 			break;
285 		  case 'p':
286 			noscroll++;
287 			break;
288 		  case 'c':
289 			clreol++;
290 			break;
291 		  case 's':
292 			ssp_opt = 1;
293 			break;
294 		  case 'u':
295 			ul_opt = 0;
296 			break;
297 		}
298 	}
299 }
300 
301 
302 /*
303 ** Check whether the file named by fs is an ASCII file which the user may
304 ** access.  If it is, return the opened file. Otherwise return NULL.
305 */
306 
307 FILE *
308 checkf (fs, clearfirst)
309 register char *fs;
310 int *clearfirst;
311 {
312     struct stat stbuf;
313     register FILE *f;
314     char c;
315 
316     if (stat (fs, &stbuf) == -1) {
317 	fflush(stdout);
318 	if (clreol)
319 	    cleareol ();
320 	perror(fs);
321 	return (NULL);
322     }
323     if ((stbuf.st_mode & S_IFMT) == S_IFDIR) {
324 	printf("\n*** %s: directory ***\n\n", fs);
325 	return (NULL);
326     }
327     if ((f=Fopen(fs, "r")) == NULL) {
328 	fflush(stdout);
329 	perror(fs);
330 	return (NULL);
331     }
332     c = Getc(f);
333 
334     /* Try to see whether it is an ASCII file */
335 
336     switch ((c | *f->_ptr << 8) & 0177777) {
337     case 0405:
338     case 0407:
339     case 0410:
340     case 0411:
341     case 0413:
342     case 0177545:
343 	printf("\n******** %s: Not a text file ********\n\n", fs);
344 	fclose (f);
345 	return (NULL);
346     default:
347 	break;
348     }
349     if (c == '\f')
350 	*clearfirst = 1;
351     else {
352 	*clearfirst = 0;
353 	Ungetc (c, f);
354     }
355     if ((file_size = stbuf.st_size) == 0)
356 	file_size = 0x7fffffffffffffffL;
357     return (f);
358 }
359 
360 /*
361 ** A real function, for the tputs routine in termlib
362 */
363 
364 putch (ch)
365 char ch;
366 {
367     putchar (ch);
368 }
369 
370 /*
371 ** Print out the contents of the file f, one screenful at a time.
372 */
373 
374 #define STOP -10
375 
376 screen (f, num_lines)
377 register FILE *f;
378 register int num_lines;
379 {
380     register int c;
381     register int nchars;
382     int length;			/* length of current line */
383     static int prev_len = 1;	/* length of previous line */
384 
385     for (;;) {
386 	while (num_lines > 0 && !Pause) {
387 	    if ((nchars = getline (f, &length)) == EOF)
388 	    {
389 		if (clreol)
390 		    clreos();
391 		return;
392 	    }
393 	    if (ssp_opt && length == 0 && prev_len == 0)
394 		continue;
395 	    prev_len = length;
396 	    if (bad_so || (Senter && *Senter == ' ') && promptlen > 0)
397 		erase (0);
398 	    /* must clear before drawing line since tabs on some terminals
399 	     * do not erase what they tab over.
400 	     */
401 	    if (clreol)
402 		cleareol ();
403 	    prbuf (Line, length);
404 	    if (nchars < promptlen)
405 		erase (nchars);	/* erase () sets promptlen to 0 */
406 	    else promptlen = 0;
407 	    /* is this needed?
408 	     * if (clreol)
409 	     *	cleareol();	/* must clear again in case we wrapped *
410 	     */
411 	    if (nchars < Mcol || !fold_opt)
412 		putchar('\n');
413 	    if (nchars == STOP)
414 		break;
415 	    num_lines--;
416 	}
417 	fflush(stdout);
418 	if ((c = Getc(f)) == EOF)
419 	{
420 	    if (clreol)
421 		clreos ();
422 	    return;
423 	}
424 
425 	if (Pause && clreol)
426 	    clreos ();
427 	Ungetc (c, f);
428 	setjmp (restore);
429 	Pause = 0; startup = 0;
430 	if ((num_lines = command (NULL, f)) == 0)
431 	    return;
432 	if (hard && promptlen > 0)
433 		erase (0);
434 	if (noscroll && num_lines >= dlines)
435 	{
436 	    if (clreol)
437 		home();
438 	    else
439 		doclear ();
440 	}
441 	screen_start.line = Currline;
442 	screen_start.chrctr = Ftell (f);
443     }
444 }
445 
446 /*
447 ** Come here if a quit signal is received
448 */
449 
450 onquit()
451 {
452     signal(SIGQUIT, SIG_IGN);
453     if (!inwait) {
454 	putchar ('\n');
455 	if (!startup) {
456 	    signal(SIGQUIT, onquit);
457 	    longjmp (restore, 1);
458 	}
459 	else
460 	    Pause++;
461     }
462     else if (!dum_opt && notell) {
463 	write (2, "[Use q or Q to quit]", 20);
464 	promptlen += 20;
465 	notell = 0;
466     }
467     signal(SIGQUIT, onquit);
468 }
469 
470 /*
471 ** Clean up terminal state and exit. Also come here if interrupt signal received
472 */
473 
474 end_it ()
475 {
476 
477     reset_tty ();
478     if (clreol) {
479 	putchar ('\r');
480 	clreos ();
481 	fflush (stdout);
482     }
483     else if (!clreol && (promptlen > 0)) {
484 	kill_line ();
485 	fflush (stdout);
486     }
487     else
488 	write (2, "\n", 1);
489     _exit(0);
490 }
491 
492 copy_file(f)
493 register FILE *f;
494 {
495     register int c;
496 
497     while ((c = getc(f)) != EOF)
498 	putchar(c);
499 }
500 
501 /* Simplified printf function */
502 
503 printf (fmt, args)
504 register char *fmt;
505 int args;
506 {
507 	register int *argp;
508 	register char ch;
509 	register int ccount;
510 
511 	ccount = 0;
512 	argp = &args;
513 	while (*fmt) {
514 		while ((ch = *fmt++) != '%') {
515 			if (ch == '\0')
516 				return (ccount);
517 			ccount++;
518 			putchar (ch);
519 		}
520 		switch (*fmt++) {
521 		case 'd':
522 			ccount += printd (*argp);
523 			break;
524 		case 's':
525 			ccount += pr ((char *)*argp);
526 			break;
527 		case '%':
528 			ccount++;
529 			argp--;
530 			putchar ('%');
531 			break;
532 		case '0':
533 			return (ccount);
534 		default:
535 			break;
536 		}
537 		++argp;
538 	}
539 	return (ccount);
540 
541 }
542 
543 /*
544 ** Print an integer as a string of decimal digits,
545 ** returning the length of the print representation.
546 */
547 
548 printd (n)
549 int n;
550 {
551     int a, nchars;
552 
553     if (a = n/10)
554 	nchars = 1 + printd(a);
555     else
556 	nchars = 1;
557     putchar (n % 10 + '0');
558     return (nchars);
559 }
560 
561 /* Put the print representation of an integer into a string */
562 static char *sptr;
563 
564 scanstr (n, str)
565 int n;
566 char *str;
567 {
568     sptr = str;
569     Sprintf (n);
570     *sptr = '\0';
571 }
572 
573 Sprintf (n)
574 {
575     int a;
576 
577     if (a = n/10)
578 	Sprintf (a);
579     *sptr++ = n % 10 + '0';
580 }
581 
582 static char bell = ctrl(G);
583 
584 strlen (s)
585 char *s;
586 {
587     register char *p;
588 
589     p = s;
590     while (*p++)
591 	;
592     return (p - s - 1);
593 }
594 
595 /* See whether the last component of the path name "path" is equal to the
596 ** string "string"
597 */
598 
599 tailequ (path, string)
600 char *path;
601 register char *string;
602 {
603 	register char *tail;
604 
605 	tail = path + strlen(path);
606 	while (tail >= path)
607 		if (*(--tail) == '/')
608 			break;
609 	++tail;
610 	while (*tail++ == *string++)
611 		if (*tail == '\0')
612 			return(1);
613 	return(0);
614 }
615 
616 prompt (filename)
617 char *filename;
618 {
619     if (clreol)
620 	cleareol ();
621     else if (promptlen > 0)
622 	kill_line ();
623     if (!hard) {
624 	promptlen = 8;
625 	if (Senter && Sexit)
626 	    tputs (Senter, 1, putch);
627 	if (clreol)
628 	    cleareol ();
629 	pr("--More--");
630 	if (filename != NULL) {
631 	    promptlen += printf ("(Next file: %s)", filename);
632 	}
633 	else if (!no_intty) {
634 	    promptlen += printf ("(%d%%)", (int)((file_pos * 100) / file_size));
635 	}
636 	if (dum_opt) {
637 	    promptlen += pr("[Hit space to continue, Rubout to abort]");
638 	}
639 	if (Senter && Sexit)
640 	    tputs (Sexit, 1, putch);
641 	if (clreol)
642 	    clreos ();
643 	fflush(stdout);
644     }
645     else
646 	write (2, &bell, 1);
647     inwait++;
648 }
649 
650 /*
651 ** Get a logical line
652 */
653 
654 getline(f, length)
655 register FILE *f;
656 int *length;
657 {
658     register int	c;
659     register char	*p;
660     register int	column;
661     static int		colflg;
662 
663     p = Line;
664     column = 0;
665     c = Getc (f);
666     if (colflg && c == '\n') {
667 	Currline++;
668 	c = Getc (f);
669     }
670     while (p < &Line[LINSIZ - 1]) {
671 	if (c == EOF) {
672 	    if (p > Line) {
673 		*p = '\0';
674 		*length = p - Line;
675 		return (column);
676 	    }
677 	    *length = p - Line;
678 	    return (EOF);
679 	}
680 	if (c == '\n') {
681 	    Currline++;
682 	    break;
683 	}
684 	*p++ = c;
685 	if (c == '\t')
686 	    if (hardtabs && column < promptlen && !hard) {
687 		if (eraseln && !dumb) {
688 		    column = 1 + (column | 7);
689 		    tputs (eraseln, 1, putch);
690 		    promptlen = 0;
691 		}
692 		else {
693 		    for (--p; column & 7 && p < &Line[LINSIZ - 1]; column++) {
694 			*p++ = ' ';
695 		    }
696 		    if (column >= promptlen) promptlen = 0;
697 		}
698 	    }
699 	    else
700 		column = 1 + (column | 7);
701 	else if (c == '\b' && column > 0)
702 	    column--;
703 	else if (c == '\r')
704 	    column = 0;
705 	else if (c == '\f' && stop_opt) {
706 		p[-1] = '^';
707 		*p++ = 'L';
708 		column += 2;
709 		Pause++;
710 	}
711 	else if (c == EOF) {
712 	    *length = p - Line;
713 	    return (column);
714 	}
715 	else if (c >= ' ' && c != RUBOUT)
716 	    column++;
717 	if (column >= Mcol && fold_opt) break;
718 	c = Getc (f);
719     }
720     if (column >= Mcol && Mcol > 0) {
721 	if (!Wrap) {
722 	    *p++ = '\n';
723 	}
724     }
725     colflg = column == Mcol && fold_opt;
726     *length = p - Line;
727     *p = 0;
728     return (column);
729 }
730 
731 /*
732 ** Erase the rest of the prompt, assuming we are starting at column col.
733 */
734 
735 erase (col)
736 register int col;
737 {
738 
739     if (promptlen == 0)
740 	return;
741     if (hard) {
742 	putchar ('\n');
743     }
744     else {
745 	if (col == 0)
746 	    putchar ('\r');
747 	if (!dumb && eraseln)
748 	    tputs (eraseln, 1, putch);
749 	else
750 	    for (col = promptlen - col; col > 0; col--)
751 		putchar (' ');
752     }
753     promptlen = 0;
754 }
755 
756 /*
757 ** Erase the current line entirely
758 */
759 
760 kill_line ()
761 {
762     erase (0);
763     if (!eraseln || dumb) putchar ('\r');
764 }
765 
766 /*
767  * force clear to end of line
768  */
769 cleareol()
770 {
771     tputs(eraseln, 1, putch);
772 }
773 
774 clreos()
775 {
776     tputs(EodClr, 1, putch);
777 }
778 
779 /*
780 **  Print string and return number of characters
781 */
782 
783 pr(s1)
784 char	*s1;
785 {
786     register char	*s;
787     register char	c;
788 
789     for (s = s1; c = *s++; )
790 	putchar(c);
791     return (s - s1 - 1);
792 }
793 
794 
795 /* Print a buffer of n characters */
796 
797 prbuf (s, n)
798 register char *s;
799 register int n;
800 {
801     char c;				/* next ouput character */
802     register int state;			/* next output char's UL state */
803     static int pstate = 0;		/* current terminal UL state (off) */
804 
805     while (--n >= 0)
806 	if (!ul_opt)
807 	    putchar (*s++);
808 	else {
809 	    if (n >= 2 && s[0] == '_' && s[1] == '\b') {
810 		n -= 2;
811 	        s += 2;
812 		c = *s++;
813 		state = 1;
814 	    } else if (n >= 2 && s[1] == '\b' && s[2] == '_') {
815 		n -= 2;
816 		c = *s++;
817 		s += 2;
818 		state = 1;
819 	    } else {
820 		c = *s++;
821 		state = 0;
822 	    }
823 	    if (state != pstate)
824 		tputs(state ? ULenter : ULexit, 1, putch);
825 	    pstate = state;
826 	    putchar(c);
827 	    if (state && *chUL) {
828 		pr(chBS);
829 		tputs(chUL, 1, putch);
830 	    }
831 	}
832 }
833 
834 /*
835 **  Clear the screen
836 */
837 
838 doclear()
839 {
840     if (Clear && !hard) {
841 	tputs(Clear, 1, putch);
842 
843 	/* Put out carriage return so that system doesn't
844 	** get confused by escape sequences when expanding tabs
845 	*/
846 	putchar ('\r');
847 	promptlen = 0;
848     }
849 }
850 
851 /*
852  * Go to home position
853  */
854 home()
855 {
856     tputs(Home,1,putch);
857 }
858 
859 static int lastcmd, lastarg, lastp;
860 static int lastcolon;
861 char shell_line[132];
862 
863 /*
864 ** Read a command and do it. A command consists of an optional integer
865 ** argument followed by the command character.  Return the number of lines
866 ** to display in the next screenful.  If there is nothing more to display
867 ** in the current file, zero is returned.
868 */
869 
870 command (filename, f)
871 char *filename;
872 register FILE *f;
873 {
874     register int nlines;
875     register int retval;
876     register char c;
877     char colonch;
878     FILE *helpf;
879     int done;
880     char comchar, cmdbuf[80], *p;
881 
882 #define ret(val) retval=val;done++;break
883 
884     done = 0;
885     if (!errors)
886 	prompt (filename);
887     else
888 	errors = 0;
889     if (MBIT == RAW && slow_tty) {
890 	otty.sg_flags |= MBIT;
891 	stty(2, &otty);
892     }
893     for (;;) {
894 	nlines = number (&comchar);
895 	lastp = colonch = 0;
896 	if (comchar == '.') {	/* Repeat last command */
897 		lastp++;
898 		comchar = lastcmd;
899 		nlines = lastarg;
900 		if (lastcmd == ':')
901 			colonch = lastcolon;
902 	}
903 	lastcmd = comchar;
904 	lastarg = nlines;
905 	if (comchar == otty.sg_erase) {
906 	    kill_line ();
907 	    prompt (filename);
908 	    continue;
909 	}
910 	switch (comchar) {
911 	case ':':
912 	    retval = colon (filename, colonch, nlines);
913 	    if (retval >= 0)
914 		done++;
915 	    break;
916 	case ' ':
917 	case 'z':
918 	    if (nlines == 0) nlines = dlines;
919 	    else if (comchar == 'z') dlines = nlines;
920 	    ret (nlines);
921 	case 'd':
922 	case ctrl(D):
923 	    if (nlines != 0) nscroll = nlines;
924 	    ret (nscroll);
925 	case RUBOUT:
926 	case 'q':
927 	case 'Q':
928 	    end_it ();
929 	case 's':
930 	case 'f':
931 	    if (nlines == 0) nlines++;
932 	    if (comchar == 'f')
933 		nlines *= dlines;
934 	    putchar ('\r');
935 	    erase (0);
936 	    printf ("\n");
937 	    if (clreol)
938 		cleareol ();
939 	    printf ("...skipping %d line", nlines);
940 	    if (nlines > 1)
941 		pr ("s\n");
942 	    else
943 		pr ("\n");
944 
945 	    if (clreol)
946 		cleareol ();
947 	    pr ("\n");
948 
949 	    while (nlines > 0) {
950 		while ((c = Getc (f)) != '\n')
951 		    if (c == EOF) {
952 			retval = 0;
953 			done++;
954 			goto endsw;
955 		    }
956 		    Currline++;
957 		    nlines--;
958 	    }
959 	    ret (dlines);
960 	case '\n':
961 	    if (nlines != 0)
962 		dlines = nlines;
963 	    else
964 		nlines = 1;
965 	    ret (nlines);
966 	case '\f':
967 	    if (!no_intty) {
968 		doclear ();
969 		Fseek (f, screen_start.chrctr);
970 		Currline = screen_start.line;
971 		ret (dlines);
972 	    }
973 	    else {
974 		write (2, &bell, 1);
975 		break;
976 	    }
977 	case '\'':
978 	    if (!no_intty) {
979 		kill_line ();
980 		pr ("\n***Back***\n\n");
981 		Fseek (f, context.chrctr);
982 		Currline = context.line;
983 		ret (dlines);
984 	    }
985 	    else {
986 		write (2, &bell, 1);
987 		break;
988 	    }
989 	case '=':
990 	    kill_line ();
991 	    promptlen = printd (Currline);
992 	    fflush (stdout);
993 	    break;
994 	case 'n':
995 	    lastp++;
996 	case '/':
997 	    if (nlines == 0) nlines++;
998 	    kill_line ();
999 	    pr ("/");
1000 	    promptlen = 1;
1001 	    fflush (stdout);
1002 	    if (lastp) {
1003 		write (2,"\r", 1);
1004 		search (NULL, f, nlines);	/* Use previous r.e. */
1005 	    }
1006 	    else {
1007 		ttyin (cmdbuf, 78, '/');
1008 		write (2, "\r", 1);
1009 		search (cmdbuf, f, nlines);
1010 	    }
1011 	    ret (dlines-1);
1012 	case '!':
1013 	    do_shell (filename);
1014 	    break;
1015 	case 'h':
1016 	    if ((helpf = fopen (HELPFILE, "r")) == NULL)
1017 		error ("Can't open help file");
1018 	    if (noscroll) doclear ();
1019 	    copy_file (helpf);
1020 	    close (helpf);
1021 	    prompt (filename);
1022 	    break;
1023 	case 'v':	/* This case should go right before default */
1024 	    if (!no_intty) {
1025 		kill_line ();
1026 		cmdbuf[0] = '+';
1027 		scanstr (Currline, &cmdbuf[1]);
1028 		pr ("vi "); pr (cmdbuf); putchar (' '); pr (fnames[fnum]);
1029 		execute (filename, VI, "vi", cmdbuf, fnames[fnum], 0);
1030 		break;
1031 	    }
1032 	default:
1033 	    write (2, &bell, 1);
1034 	    break;
1035 	}
1036 	if (done) break;
1037     }
1038     putchar ('\r');
1039 endsw:
1040     inwait = 0;
1041     notell++;
1042     if (MBIT == RAW && slow_tty) {
1043 	otty.sg_flags &= ~MBIT;
1044 	stty(2, &otty);
1045     }
1046     return (retval);
1047 }
1048 
1049 char ch;
1050 
1051 /*
1052  * Execute a colon-prefixed command.
1053  * Returns <0 if not a command that should cause
1054  * more of the file to be printed.
1055  */
1056 
1057 colon (filename, cmd, nlines)
1058 char *filename;
1059 int cmd;
1060 int nlines;
1061 {
1062 	if (cmd == 0)
1063 		ch = readch ();
1064 	else
1065 		ch = cmd;
1066 	lastcolon = ch;
1067 	switch (ch) {
1068 	case 'f':
1069 		kill_line ();
1070 		if (!no_intty)
1071 			promptlen = printf ("\"%s\" line %d", fnames[fnum], Currline);
1072 		else
1073 			promptlen = printf ("[Not a file] line %d", Currline);
1074 		fflush (stdout);
1075 		return (-1);
1076 	case 'n':
1077 		if (nlines == 0) {
1078 			if (fnum >= nfiles - 1)
1079 				end_it ();
1080 			nlines++;
1081 		}
1082 		putchar ('\r');
1083 		erase (0);
1084 		skipf (nlines);
1085 		return (0);
1086 	case 'p':
1087 		if (no_intty) {
1088 			write (2, &bell, 1);
1089 			return (-1);
1090 		}
1091 		putchar ('\r');
1092 		erase (0);
1093 		if (nlines == 0)
1094 			nlines++;
1095 		skipf (-nlines);
1096 		return (0);
1097 	case '!':
1098 		do_shell (filename);
1099 		return (-1);
1100 	case 'q':
1101 	case 'Q':
1102 		end_it ();
1103 	default:
1104 		write (2, &bell, 1);
1105 		return (-1);
1106 	}
1107 }
1108 
1109 /*
1110 ** Read a decimal number from the terminal. Set cmd to the non-digit which
1111 ** terminates the number.
1112 */
1113 
1114 number(cmd)
1115 char *cmd;
1116 {
1117 	register int i;
1118 
1119 	i = 0; ch = otty.sg_kill;
1120 	for (;;) {
1121 		ch = readch ();
1122 		if (ch >= '0' && ch <= '9')
1123 			i = i*10 + ch - '0';
1124 		else if (ch == otty.sg_kill)
1125 			i = 0;
1126 		else {
1127 			*cmd = ch;
1128 			break;
1129 		}
1130 	}
1131 	return (i);
1132 }
1133 
1134 do_shell (filename)
1135 char *filename;
1136 {
1137 	char cmdbuf[80];
1138 
1139 	kill_line ();
1140 	pr ("!");
1141 	fflush (stdout);
1142 	promptlen = 1;
1143 	if (lastp)
1144 		pr (shell_line);
1145 	else {
1146 		ttyin (cmdbuf, 78, '!');
1147 		if (expand (shell_line, cmdbuf)) {
1148 			kill_line ();
1149 			promptlen = printf ("!%s", shell_line);
1150 		}
1151 	}
1152 	fflush (stdout);
1153 	write (2, "\n", 1);
1154 	promptlen = 0;
1155 	shellp = 1;
1156 	execute (filename, shell, shell, "-c", shell_line, 0);
1157 }
1158 
1159 /*
1160 ** Search for nth ocurrence of regular expression contained in buf in the file
1161 */
1162 
1163 search (buf, file, n)
1164 char buf[];
1165 FILE *file;
1166 register int n;
1167 {
1168     long startline = Ftell (file);
1169     register long line1 = startline;
1170     register long line2 = startline;
1171     register long line3 = startline;
1172     register int lncount;
1173     int saveln, rv, re_exec();
1174     char *s, *re_comp();
1175 
1176     context.line = saveln = Currline;
1177     context.chrctr = startline;
1178     lncount = 0;
1179     if ((s = re_comp (buf)) != 0)
1180 	error (s);
1181     while (!feof (file)) {
1182 	line3 = line2;
1183 	line2 = line1;
1184 	line1 = Ftell (file);
1185 	rdline (file);
1186 	lncount++;
1187 	if ((rv = re_exec (Line)) == 1)
1188 		if (--n == 0) {
1189 		    if (lncount > 3 || (lncount > 1 && no_intty))
1190 		    {
1191 			pr ("\n");
1192 			if (clreol)
1193 			    cleareol ();
1194 			pr("...skipping\n");
1195 		    }
1196 		    if (!no_intty) {
1197 			Currline -= (lncount >= 3 ? 3 : lncount);
1198 			Fseek (file, line3);
1199 			if (noscroll)
1200 			    if (clreol) {
1201 				home ();
1202 				cleareol ();
1203 			    }
1204 			    else
1205 				doclear ();
1206 		    }
1207 		    else {
1208 			kill_line ();
1209 			if (noscroll)
1210 			    if (clreol) {
1211 			        home ();
1212 			        cleareol ();
1213 			    }
1214 			    else
1215 				doclear ();
1216 			pr (Line);
1217 			putchar ('\n');
1218 		    }
1219 		    break;
1220 		}
1221 	else if (rv == -1)
1222 	    error ("Regular expression botch");
1223     }
1224     if (feof (file)) {
1225 	if (!no_intty) {
1226 	    file->_flag &= ~_IOEOF; /* why doesn't fseek do this ??!!??! */
1227 	    Currline = saveln;
1228 	    Fseek (file, startline);
1229 	}
1230 	else {
1231 	    pr ("\nPattern not found\n");
1232 	    end_it ();
1233 	}
1234 	error ("Pattern not found");
1235     }
1236 }
1237 
1238 execute (filename, cmd, args)
1239 char *filename;
1240 char *cmd, *args;
1241 {
1242 	int id;
1243 
1244 	fflush (stdout);
1245 	reset_tty ();
1246 	while ((id = fork ()) < 0)
1247 	    sleep (5);
1248 	if (id == 0) {
1249 	    execv (cmd, &args);
1250 	    write (2, "exec failed\n", 12);
1251 	    exit (1);
1252 	}
1253 	signal (SIGINT, SIG_IGN);
1254 	signal (SIGQUIT, SIG_IGN);
1255 	if (catch_susp)
1256 	    signal(SIGTSTP, SIG_DFL);
1257 	wait (0);
1258 	signal (SIGINT, end_it);
1259 	signal (SIGQUIT, onquit);
1260 	if (catch_susp)
1261 	    signal(SIGTSTP, onsusp);
1262 	set_tty ();
1263 	pr ("------------------------\n");
1264 	prompt (filename);
1265 }
1266 /*
1267 ** Skip n lines in the file f
1268 */
1269 
1270 skiplns (n, f)
1271 register int n;
1272 register FILE *f;
1273 {
1274     register char c;
1275 
1276     while (n > 0) {
1277 	while ((c = Getc (f)) != '\n')
1278 	    if (c == EOF)
1279 		return;
1280 	    n--;
1281 	    Currline++;
1282     }
1283 }
1284 
1285 /*
1286 ** Skip nskip files in the file list (from the command line). Nskip may be
1287 ** negative.
1288 */
1289 
1290 skipf (nskip)
1291 register int nskip;
1292 {
1293     if (nskip == 0) return;
1294     if (nskip > 0) {
1295 	if (fnum + nskip > nfiles - 1)
1296 	    nskip = nfiles - fnum - 1;
1297     }
1298     else if (within)
1299 	++fnum;
1300     fnum += nskip;
1301     if (fnum < 0)
1302 	fnum = 0;
1303     pr ("\n...Skipping ");
1304     pr ("\n");
1305     if (clreol)
1306 	cleareol ();
1307     pr ("...Skipping ");
1308     pr (nskip > 0 ? "to file " : "back to file ");
1309     pr (fnames[fnum]);
1310     pr ("\n");
1311     if (clreol)
1312 	cleareol ();
1313     pr ("\n");
1314     --fnum;
1315 }
1316 
1317 /*----------------------------- Terminal I/O -------------------------------*/
1318 
1319 initterm ()
1320 {
1321     char	buf[TBUFSIZ];
1322     char	clearbuf[100];
1323     char	*clearptr, *padstr;
1324     int		ldisc;
1325     char	*term;
1326 
1327     setbuf(stdout, obuf);
1328     if (!(no_tty = gtty(1, &otty))) {
1329 	if ((term = getenv("TERM")) || tgetent(buf, term) <= 0) {
1330 	    dumb++; ul_opt = 0;
1331 	}
1332 	else {
1333 	    if (((Lpp = tgetnum("li")) < 0) || tgetflag("hc")) {
1334 		hard++;	/* Hard copy terminal */
1335 		Lpp = 24;
1336 	    }
1337 	    if (tailequ (fnames[0], "page") || !hard && tgetflag("ns"))
1338 		noscroll++;
1339 	    if ((Mcol = tgetnum("co")) < 0)
1340 		Mcol = 80;
1341 	    Wrap = tgetflag("am");
1342 	    bad_so = tgetflag ("xs");
1343 	    clearptr = clearbuf;
1344 	    eraseln = tgetstr("ce",&clearptr);
1345 	    Clear = tgetstr("cl", &clearptr);
1346 	    Senter = tgetstr("so", &clearptr);
1347 	    Sexit = tgetstr("se", &clearptr);
1348 
1349 	    /*
1350 	     *  Set up for underlining:  some terminals don't need it;
1351 	     *  others have start/stop sequences, still others have an
1352 	     *  underline char sequence which is assumed to move the
1353 	     *  cursor forward one character.  If underline sequence
1354 	     *  isn't available, settle for standout sequence.
1355 	     */
1356 
1357 	    if (tgetflag("ul") || tgetflag("os"))
1358 		ul_opt = 0;
1359 	    if ((chUL = tgetstr("uc", &clearptr)) == NULL )
1360 		chUL = "";
1361 	    if ((ULenter = tgetstr("us", &clearptr)) == NULL &&
1362 		(!*chUL) && (ULenter = tgetstr("so", &clearptr)) == NULL)
1363 		ULenter = "";
1364 	    if ((ULexit = tgetstr("ue", &clearptr)) == NULL &&
1365 		(!*chUL) && (ULexit = tgetstr("se", &clearptr)) == NULL)
1366 		ULexit = "";
1367 
1368 	    if (padstr = tgetstr("pc", &clearptr))
1369 		PC = *padstr;
1370 	    Home = tgetstr("ho",&clearptr);
1371 	    if (Home == 0 || *Home == '\0')
1372 	    {
1373 		if ((cursorm = tgetstr("cm", &clearptr)) != NULL) {
1374 		    strcpy(cursorhome, tgoto(cursorm, 0, 0));
1375 		    Home = cursorhome;
1376 	       }
1377 	    }
1378 	    EodClr = tgetstr("cd", &clearptr);
1379 	}
1380 	if ((shell = getenv("SHELL")) == NULL)
1381 	    shell = "/bin/sh";
1382     }
1383     no_intty = gtty(0, &otty);
1384     gtty(2, &otty);
1385     ospeed = otty.sg_ospeed;
1386     slow_tty = ospeed < B1200;
1387     hardtabs =  !(otty.sg_flags & XTABS);
1388     if (!no_tty) {
1389 	otty.sg_flags &= ~ECHO;
1390 	if (MBIT == CBREAK || !slow_tty)
1391 	    otty.sg_flags |= MBIT;
1392     }
1393 }
1394 
1395 readch ()
1396 {
1397 	char ch;
1398 	extern int errno;
1399 
1400 	if (read (2, &ch, 1) <= 0)
1401 		if (errno != EINTR)
1402 			exit(0);
1403 		else
1404 			ch = otty.sg_kill;
1405 	return (ch);
1406 }
1407 
1408 static char BS = '\b';
1409 static char CARAT = '^';
1410 
1411 ttyin (buf, nmax, pchar)
1412 char buf[];
1413 register int nmax;
1414 char pchar;
1415 {
1416     register char *sptr;
1417     register char ch;
1418     register int slash = 0;
1419     int	maxlen;
1420     char cbuf;
1421 
1422     sptr = buf;
1423     maxlen = 0;
1424     while (sptr - buf < nmax) {
1425 	if (promptlen > maxlen) maxlen = promptlen;
1426 	ch = readch ();
1427 	if (ch == '\\') {
1428 	    slash++;
1429 	}
1430 	else if ((ch == otty.sg_erase) && !slash) {
1431 	    if (sptr > buf) {
1432 		--promptlen;
1433 		write (2, &BS, 1);
1434 		--sptr;
1435 		if ((*sptr < ' ' && *sptr != '\n') || *sptr == RUBOUT) {
1436 		    --promptlen;
1437 		    write (2, &BS, 1);
1438 		}
1439 		continue;
1440 	    }
1441 	    else {
1442 		if (!eraseln) promptlen = maxlen;
1443 		longjmp (restore, 1);
1444 	    }
1445 	}
1446 	else if ((ch == otty.sg_kill) && !slash) {
1447 	    if (hard) {
1448 		show (ch);
1449 		putchar ('\n');
1450 		putchar (pchar);
1451 	    }
1452 	    else {
1453 		putchar ('\r');
1454 		putchar (pchar);
1455 		if (eraseln)
1456 		    erase (1);
1457 		promptlen = 1;
1458 	    }
1459 	    sptr = buf;
1460 	    fflush (stdout);
1461 	    continue;
1462 	}
1463 	if (slash && (ch == otty.sg_kill || ch == otty.sg_erase)) {
1464 	    write (2, &BS, 1);
1465 	    --sptr;
1466 	}
1467 	if (ch != '\\')
1468 	    slash = 0;
1469 	*sptr++ = ch;
1470 	if ((ch < ' ' && ch != '\n' && ch != ESC) || ch == RUBOUT) {
1471 	    ch += ch == RUBOUT ? -0100 : 0100;
1472 	    write (2, &CARAT, 1);
1473 	    promptlen++;
1474 	}
1475 	cbuf = ch;
1476 	if (ch != '\n' && ch != ESC) {
1477 	    write (2, &cbuf, 1);
1478 	    promptlen++;
1479 	}
1480 	else
1481 	    break;
1482     }
1483     *--sptr = '\0';
1484     if (!eraseln) promptlen = maxlen;
1485     if (sptr - buf >= nmax - 1)
1486 	error ("Line too long");
1487 }
1488 
1489 expand (outbuf, inbuf)
1490 char *outbuf;
1491 char *inbuf;
1492 {
1493     register char *instr;
1494     register char *outstr;
1495     register char ch;
1496     char temp[200];
1497     int changed = 0;
1498 
1499     instr = inbuf;
1500     outstr = temp;
1501     while ((ch = *instr++) != '\0')
1502 	switch (ch) {
1503 	case '%':
1504 	    if (!no_intty) {
1505 		strcpy (outstr, fnames[fnum]);
1506 		outstr += strlen (fnames[fnum]);
1507 		changed++;
1508 	    }
1509 	    else
1510 		*outstr++ = ch;
1511 	    break;
1512 	case '!':
1513 	    if (!shellp)
1514 		error ("No previous command to substitute for");
1515 	    strcpy (outstr, shell_line);
1516 	    outstr += strlen (shell_line);
1517 	    changed++;
1518 	    break;
1519 	case '\\':
1520 	    if (*instr == '%' || *instr == '!') {
1521 		*outstr++ = *instr++;
1522 		break;
1523 	    }
1524 	default:
1525 	    *outstr++ = ch;
1526 	}
1527     *outstr++ = '\0';
1528     strcpy (outbuf, temp);
1529     return (changed);
1530 }
1531 
1532 show (ch)
1533 register char ch;
1534 {
1535     char cbuf;
1536 
1537     if ((ch < ' ' && ch != '\n' && ch != ESC) || ch == RUBOUT) {
1538 	ch += ch == RUBOUT ? -0100 : 0100;
1539 	write (2, &CARAT, 1);
1540 	promptlen++;
1541     }
1542     cbuf = ch;
1543     write (2, &cbuf, 1);
1544     promptlen++;
1545 }
1546 
1547 error (mess)
1548 char *mess;
1549 {
1550     if (clreol)
1551 	cleareol ();
1552     else
1553 	kill_line ();
1554     promptlen += strlen (mess);
1555     if (Senter && Sexit) {
1556 	tputs (Senter, 1, putch);
1557 	pr(mess);
1558 	tputs (Sexit, 1, putch);
1559     }
1560     else
1561 	pr (mess);
1562     fflush(stdout);
1563     errors++;
1564     longjmp (restore, 1);
1565 }
1566 
1567 
1568 set_tty ()
1569 {
1570 	otty.sg_flags |= MBIT;
1571 	otty.sg_flags &= ~ECHO;
1572 	stty(2, &otty);
1573 }
1574 
1575 reset_tty ()
1576 {
1577     otty.sg_flags |= ECHO;
1578     otty.sg_flags &= ~MBIT;
1579     stty(2, &otty);
1580 }
1581 
1582 rdline (f)
1583 register FILE *f;
1584 {
1585     register char c;
1586     register char *p;
1587 
1588     p = Line;
1589     while ((c = Getc (f)) != '\n' && c != EOF && p - Line < LINSIZ - 1)
1590 	*p++ = c;
1591     if (c == '\n')
1592 	Currline++;
1593     *p = '\0';
1594 }
1595 
1596 /* Come here when we get a suspend signal from the terminal */
1597 
1598 onsusp ()
1599 {
1600     reset_tty ();
1601     fflush (stdout);
1602     /* Send the TSTP signal to suspend our process group */
1603     signal(SIGTSTP, SIG_DFL);
1604     sigsetmask(0);
1605     kill (0, SIGTSTP);
1606     /* Pause for station break */
1607 
1608     /* We're back */
1609     signal (SIGTSTP, onsusp);
1610     set_tty ();
1611     if (inwait)
1612 	    longjmp (restore);
1613 }
1614