xref: /original-bsd/usr.bin/more/prim.c (revision 76f06662)
1 /*
2  * Copyright (c) 1988 Mark Nudleman
3  * Copyright (c) 1988, 1993
4  *	The Regents of the University of California.  All rights reserved.
5  *
6  * %sccs.include.redist.c%
7  */
8 
9 #ifndef lint
10 static char sccsid[] = "@(#)prim.c	8.1 (Berkeley) 06/06/93";
11 #endif /* not lint */
12 
13 /*
14  * Primitives for displaying the file on the screen.
15  */
16 
17 #include <sys/types.h>
18 #include <stdio.h>
19 #include <ctype.h>
20 #include <less.h>
21 
22 int back_scroll = -1;
23 int hit_eof;		/* keeps track of how many times we hit end of file */
24 int screen_trashed;
25 
26 static int squished;
27 
28 extern int sigs;
29 extern int top_scroll;
30 extern int sc_width, sc_height;
31 extern int caseless;
32 extern int linenums;
33 extern int tagoption;
34 extern char *line;
35 extern int retain_below;
36 
37 off_t position(), forw_line(), back_line(), forw_raw_line(), back_raw_line();
38 off_t ch_length(), ch_tell();
39 
40 /*
41  * Check to see if the end of file is currently "displayed".
42  */
eof_check()43 eof_check()
44 {
45 	off_t pos;
46 
47 	if (sigs)
48 		return;
49 	/*
50 	 * If the bottom line is empty, we are at EOF.
51 	 * If the bottom line ends at the file length,
52 	 * we must be just at EOF.
53 	 */
54 	pos = position(BOTTOM_PLUS_ONE);
55 	if (pos == NULL_POSITION || pos == ch_length())
56 		hit_eof++;
57 }
58 
59 /*
60  * If the screen is "squished", repaint it.
61  * "Squished" means the first displayed line is not at the top
62  * of the screen; this can happen when we display a short file
63  * for the first time.
64  */
squish_check()65 squish_check()
66 {
67 	if (squished) {
68 		squished = 0;
69 		repaint();
70 	}
71 }
72 
73 /*
74  * Display n lines, scrolling forward, starting at position pos in the
75  * input file.  "only_last" means display only the last screenful if
76  * n > screen size.
77  */
forw(n,pos,only_last)78 forw(n, pos, only_last)
79 	register int n;
80 	off_t pos;
81 	int only_last;
82 {
83 	extern int short_file;
84 	static int first_time = 1;
85 	int eof = 0, do_repaint;
86 
87 	squish_check();
88 
89 	/*
90 	 * do_repaint tells us not to display anything till the end,
91 	 * then just repaint the entire screen.
92 	 */
93 	do_repaint = (only_last && n > sc_height-1);
94 
95 	if (!do_repaint) {
96 		if (top_scroll && n >= sc_height - 1) {
97 			/*
98 			 * Start a new screen.
99 			 * {{ This is not really desirable if we happen
100 			 *    to hit eof in the middle of this screen,
101 			 *    but we don't yet know if that will happen. }}
102 			 */
103 			clear();
104 			home();
105 		} else {
106 			lower_left();
107 			clear_eol();
108 		}
109 
110 		/*
111 		 * This is not contiguous with what is currently displayed.
112 		 * Clear the screen image (position table) and start a new
113 		 * screen.
114 		 */
115 		if (pos != position(BOTTOM_PLUS_ONE)) {
116 			pos_clear();
117 			add_forw_pos(pos);
118 			if (top_scroll) {
119 				clear();
120 				home();
121 			} else if (!first_time)
122 				putstr("...skipping...\n");
123 		}
124 	}
125 
126 	for (short_file = 0; --n >= 0;) {
127 		/*
128 		 * Read the next line of input.
129 		 */
130 		pos = forw_line(pos);
131 		if (pos == NULL_POSITION) {
132 			/*
133 			 * end of file; copy the table if the file was
134 			 * too small for an entire screen.
135 			 */
136 			eof = 1;
137 			if (position(TOP) == NULL_POSITION) {
138 				copytable();
139 				if (!position(TOP))
140 					short_file = 1;
141 			}
142 			break;
143 		}
144 		/*
145 		 * Add the position of the next line to the position table.
146 		 * Display the current line on the screen.
147 		 */
148 		add_forw_pos(pos);
149 		if (do_repaint)
150 			continue;
151 		/*
152 		 * If this is the first screen displayed and we hit an early
153 		 * EOF (i.e. before the requested number of lines), we
154 		 * "squish" the display down at the bottom of the screen.
155 		 * But don't do this if a -t option was given; it can cause
156 		 * us to start the display after the beginning of the file,
157 		 * and it is not appropriate to squish in that case.
158 		 */
159 		if (first_time && line == NULL && !top_scroll && !tagoption) {
160 			squished = 1;
161 			continue;
162 		}
163 		put_line();
164 	}
165 
166 	if (eof && !sigs)
167 		hit_eof++;
168 	else
169 		eof_check();
170 	if (do_repaint)
171 		repaint();
172 	first_time = 0;
173 	(void) currline(BOTTOM);
174 }
175 
176 /*
177  * Display n lines, scrolling backward.
178  */
back(n,pos,only_last)179 back(n, pos, only_last)
180 	register int n;
181 	off_t pos;
182 	int only_last;
183 {
184 	int do_repaint;
185 
186 	squish_check();
187 	do_repaint = (n > get_back_scroll() || (only_last && n > sc_height-1));
188 	hit_eof = 0;
189 	while (--n >= 0)
190 	{
191 		/*
192 		 * Get the previous line of input.
193 		 */
194 		pos = back_line(pos);
195 		if (pos == NULL_POSITION)
196 			break;
197 		/*
198 		 * Add the position of the previous line to the position table.
199 		 * Display the line on the screen.
200 		 */
201 		add_back_pos(pos);
202 		if (!do_repaint)
203 		{
204 			if (retain_below)
205 			{
206 				lower_left();
207 				clear_eol();
208 			}
209 			home();
210 			add_line();
211 			put_line();
212 		}
213 	}
214 
215 	eof_check();
216 	if (do_repaint)
217 		repaint();
218 	(void) currline(BOTTOM);
219 }
220 
221 /*
222  * Display n more lines, forward.
223  * Start just after the line currently displayed at the bottom of the screen.
224  */
forward(n,only_last)225 forward(n, only_last)
226 	int n;
227 	int only_last;
228 {
229 	off_t pos;
230 
231 	if (hit_eof) {
232 		/*
233 		 * If we're trying to go forward from end-of-file,
234 		 * go on to the next file.
235 		 */
236 		next_file(1);
237 		return;
238 	}
239 
240 	pos = position(BOTTOM_PLUS_ONE);
241 	if (pos == NULL_POSITION)
242 	{
243 		hit_eof++;
244 		return;
245 	}
246 	forw(n, pos, only_last);
247 }
248 
249 /*
250  * Display n more lines, backward.
251  * Start just before the line currently displayed at the top of the screen.
252  */
backward(n,only_last)253 backward(n, only_last)
254 	int n;
255 	int only_last;
256 {
257 	off_t pos;
258 
259 	pos = position(TOP);
260 	/*
261 	 * This will almost never happen, because the top line is almost
262 	 * never empty.
263 	 */
264 	if (pos == NULL_POSITION)
265 		return;
266 	back(n, pos, only_last);
267 }
268 
269 /*
270  * Repaint the screen, starting from a specified position.
271  */
prepaint(pos)272 prepaint(pos)
273 	off_t pos;
274 {
275 	hit_eof = 0;
276 	forw(sc_height-1, pos, 0);
277 	screen_trashed = 0;
278 }
279 
280 /*
281  * Repaint the screen.
282  */
repaint()283 repaint()
284 {
285 	/*
286 	 * Start at the line currently at the top of the screen
287 	 * and redisplay the screen.
288 	 */
289 	prepaint(position(TOP));
290 }
291 
292 /*
293  * Jump to the end of the file.
294  * It is more convenient to paint the screen backward,
295  * from the end of the file toward the beginning.
296  */
jump_forw()297 jump_forw()
298 {
299 	off_t pos;
300 
301 	if (ch_end_seek())
302 	{
303 		error("Cannot seek to end of file");
304 		return;
305 	}
306 	lastmark();
307 	pos = ch_tell();
308 	clear();
309 	pos_clear();
310 	add_back_pos(pos);
311 	back(sc_height - 1, pos, 0);
312 }
313 
314 /*
315  * Jump to line n in the file.
316  */
jump_back(n)317 jump_back(n)
318 	register int n;
319 {
320 	register int c, nlines;
321 
322 	/*
323 	 * This is done the slow way, by starting at the beginning
324 	 * of the file and counting newlines.
325 	 *
326 	 * {{ Now that we have line numbering (in linenum.c),
327 	 *    we could improve on this by starting at the
328 	 *    nearest known line rather than at the beginning. }}
329 	 */
330 	if (ch_seek((off_t)0)) {
331 		/*
332 		 * Probably a pipe with beginning of file no longer buffered.
333 		 * If he wants to go to line 1, we do the best we can,
334 		 * by going to the first line which is still buffered.
335 		 */
336 		if (n <= 1 && ch_beg_seek() == 0)
337 			jump_loc(ch_tell());
338 		error("Cannot get to beginning of file");
339 		return;
340 	}
341 
342 	/*
343 	 * Start counting lines.
344 	 */
345 	for (nlines = 1;  nlines < n;  nlines++)
346 		while ((c = ch_forw_get()) != '\n')
347 			if (c == EOI) {
348 				char message[40];
349 				(void)sprintf(message, "File has only %d lines",
350 				    nlines - 1);
351 				error(message);
352 				return;
353 			}
354 	jump_loc(ch_tell());
355 }
356 
357 /*
358  * Jump to a specified percentage into the file.
359  * This is a poor compensation for not being able to
360  * quickly jump to a specific line number.
361  */
jump_percent(percent)362 jump_percent(percent)
363 	int percent;
364 {
365 	off_t pos, len, ch_length();
366 	register int c;
367 
368 	/*
369 	 * Determine the position in the file
370 	 * (the specified percentage of the file's length).
371 	 */
372 	if ((len = ch_length()) == NULL_POSITION)
373 	{
374 		error("Don't know length of file");
375 		return;
376 	}
377 	pos = (percent * len) / 100;
378 
379 	/*
380 	 * Back up to the beginning of the line.
381 	 */
382 	if (ch_seek(pos) == 0)
383 	{
384 		while ((c = ch_back_get()) != '\n' && c != EOI)
385 			;
386 		if (c == '\n')
387 			(void) ch_forw_get();
388 		pos = ch_tell();
389 	}
390 	jump_loc(pos);
391 }
392 
393 /*
394  * Jump to a specified position in the file.
395  */
jump_loc(pos)396 jump_loc(pos)
397 	off_t pos;
398 {
399 	register int nline;
400 	off_t tpos;
401 
402 	if ((nline = onscreen(pos)) >= 0) {
403 		/*
404 		 * The line is currently displayed.
405 		 * Just scroll there.
406 		 */
407 		forw(nline, position(BOTTOM_PLUS_ONE), 0);
408 		return;
409 	}
410 
411 	/*
412 	 * Line is not on screen.
413 	 * Seek to the desired location.
414 	 */
415 	if (ch_seek(pos)) {
416 		error("Cannot seek to that position");
417 		return;
418 	}
419 
420 	/*
421 	 * See if the desired line is BEFORE the currently displayed screen.
422 	 * If so, then move forward far enough so the line we're on will be
423 	 * at the bottom of the screen, in order to be able to call back()
424 	 * to make the screen scroll backwards & put the line at the top of
425 	 * the screen.
426 	 * {{ This seems inefficient, but it's not so bad,
427 	 *    since we can never move forward more than a
428 	 *    screenful before we stop to redraw the screen. }}
429 	 */
430 	tpos = position(TOP);
431 	if (tpos != NULL_POSITION && pos < tpos) {
432 		off_t npos = pos;
433 		/*
434 		 * Note that we can't forw_line() past tpos here,
435 		 * so there should be no EOI at this stage.
436 		 */
437 		for (nline = 0;  npos < tpos && nline < sc_height - 1;  nline++)
438 			npos = forw_line(npos);
439 
440 		if (npos < tpos) {
441 			/*
442 			 * More than a screenful back.
443 			 */
444 			lastmark();
445 			clear();
446 			pos_clear();
447 			add_back_pos(npos);
448 		}
449 
450 		/*
451 		 * Note that back() will repaint() if nline > back_scroll.
452 		 */
453 		back(nline, npos, 0);
454 		return;
455 	}
456 	/*
457 	 * Remember where we were; clear and paint the screen.
458 	 */
459 	lastmark();
460 	prepaint(pos);
461 }
462 
463 /*
464  * The table of marks.
465  * A mark is simply a position in the file.
466  */
467 #define	NMARKS		(27)		/* 26 for a-z plus one for quote */
468 #define	LASTMARK	(NMARKS-1)	/* For quote */
469 static off_t marks[NMARKS];
470 
471 /*
472  * Initialize the mark table to show no marks are set.
473  */
init_mark()474 init_mark()
475 {
476 	int i;
477 
478 	for (i = 0;  i < NMARKS;  i++)
479 		marks[i] = NULL_POSITION;
480 }
481 
482 /*
483  * See if a mark letter is valid (between a and z).
484  */
485 	static int
badmark(c)486 badmark(c)
487 	int c;
488 {
489 	if (c < 'a' || c > 'z')
490 	{
491 		error("Choose a letter between 'a' and 'z'");
492 		return (1);
493 	}
494 	return (0);
495 }
496 
497 /*
498  * Set a mark.
499  */
setmark(c)500 setmark(c)
501 	int c;
502 {
503 	if (badmark(c))
504 		return;
505 	marks[c-'a'] = position(TOP);
506 }
507 
lastmark()508 lastmark()
509 {
510 	marks[LASTMARK] = position(TOP);
511 }
512 
513 /*
514  * Go to a previously set mark.
515  */
gomark(c)516 gomark(c)
517 	int c;
518 {
519 	off_t pos;
520 
521 	if (c == '\'') {
522 		pos = marks[LASTMARK];
523 		if (pos == NULL_POSITION)
524 			pos = 0;
525 	}
526 	else {
527 		if (badmark(c))
528 			return;
529 		pos = marks[c-'a'];
530 		if (pos == NULL_POSITION) {
531 			error("mark not set");
532 			return;
533 		}
534 	}
535 	jump_loc(pos);
536 }
537 
538 /*
539  * Get the backwards scroll limit.
540  * Must call this function instead of just using the value of
541  * back_scroll, because the default case depends on sc_height and
542  * top_scroll, as well as back_scroll.
543  */
get_back_scroll()544 get_back_scroll()
545 {
546 	if (back_scroll >= 0)
547 		return (back_scroll);
548 	if (top_scroll)
549 		return (sc_height - 2);
550 	return (sc_height - 1);
551 }
552 
553 /*
554  * Search for the n-th occurence of a specified pattern,
555  * either forward or backward.
556  */
search(search_forward,pattern,n,wantmatch)557 search(search_forward, pattern, n, wantmatch)
558 	register int search_forward;
559 	register char *pattern;
560 	register int n;
561 	int wantmatch;
562 {
563 	off_t pos, linepos;
564 	register char *p;
565 	register char *q;
566 	int linenum;
567 	int linematch;
568 #ifdef RECOMP
569 	char *re_comp();
570 	char *errmsg;
571 #else
572 #ifdef REGCMP
573 	char *regcmp();
574 	static char *cpattern = NULL;
575 #else
576 	static char lpbuf[100];
577 	static char *last_pattern = NULL;
578 	char *strcpy();
579 #endif
580 #endif
581 
582 	/*
583 	 * For a caseless search, convert any uppercase in the pattern to
584 	 * lowercase.
585 	 */
586 	if (caseless && pattern != NULL)
587 		for (p = pattern;  *p;  p++)
588 			if (isupper(*p))
589 				*p = tolower(*p);
590 #ifdef RECOMP
591 
592 	/*
593 	 * (re_comp handles a null pattern internally,
594 	 *  so there is no need to check for a null pattern here.)
595 	 */
596 	if ((errmsg = re_comp(pattern)) != NULL)
597 	{
598 		error(errmsg);
599 		return(0);
600 	}
601 #else
602 #ifdef REGCMP
603 	if (pattern == NULL || *pattern == '\0')
604 	{
605 		/*
606 		 * A null pattern means use the previous pattern.
607 		 * The compiled previous pattern is in cpattern, so just use it.
608 		 */
609 		if (cpattern == NULL)
610 		{
611 			error("No previous regular expression");
612 			return(0);
613 		}
614 	} else
615 	{
616 		/*
617 		 * Otherwise compile the given pattern.
618 		 */
619 		char *s;
620 		if ((s = regcmp(pattern, 0)) == NULL)
621 		{
622 			error("Invalid pattern");
623 			return(0);
624 		}
625 		if (cpattern != NULL)
626 			free(cpattern);
627 		cpattern = s;
628 	}
629 #else
630 	if (pattern == NULL || *pattern == '\0')
631 	{
632 		/*
633 		 * Null pattern means use the previous pattern.
634 		 */
635 		if (last_pattern == NULL)
636 		{
637 			error("No previous regular expression");
638 			return(0);
639 		}
640 		pattern = last_pattern;
641 	} else
642 	{
643 		(void)strcpy(lpbuf, pattern);
644 		last_pattern = lpbuf;
645 	}
646 #endif
647 #endif
648 
649 	/*
650 	 * Figure out where to start the search.
651 	 */
652 
653 	if (position(TOP) == NULL_POSITION) {
654 		/*
655 		 * Nothing is currently displayed.  Start at the beginning
656 		 * of the file.  (This case is mainly for searches from the
657 		 * command line.
658 		 */
659 		pos = (off_t)0;
660 	} else if (!search_forward) {
661 		/*
662 		 * Backward search: start just before the top line
663 		 * displayed on the screen.
664 		 */
665 		pos = position(TOP);
666 	} else {
667 		/*
668 		 * Start at the second screen line displayed on the screen.
669 		 */
670 		pos = position(TOP_PLUS_ONE);
671 	}
672 
673 	if (pos == NULL_POSITION)
674 	{
675 		/*
676 		 * Can't find anyplace to start searching from.
677 		 */
678 		error("Nothing to search");
679 		return(0);
680 	}
681 
682 	linenum = find_linenum(pos);
683 	for (;;)
684 	{
685 		/*
686 		 * Get lines until we find a matching one or
687 		 * until we hit end-of-file (or beginning-of-file
688 		 * if we're going backwards).
689 		 */
690 		if (sigs)
691 			/*
692 			 * A signal aborts the search.
693 			 */
694 			return(0);
695 
696 		if (search_forward)
697 		{
698 			/*
699 			 * Read the next line, and save the
700 			 * starting position of that line in linepos.
701 			 */
702 			linepos = pos;
703 			pos = forw_raw_line(pos);
704 			if (linenum != 0)
705 				linenum++;
706 		} else
707 		{
708 			/*
709 			 * Read the previous line and save the
710 			 * starting position of that line in linepos.
711 			 */
712 			pos = back_raw_line(pos);
713 			linepos = pos;
714 			if (linenum != 0)
715 				linenum--;
716 		}
717 
718 		if (pos == NULL_POSITION)
719 		{
720 			/*
721 			 * We hit EOF/BOF without a match.
722 			 */
723 			error("Pattern not found");
724 			return(0);
725 		}
726 
727 		/*
728 		 * If we're using line numbers, we might as well
729 		 * remember the information we have now (the position
730 		 * and line number of the current line).
731 		 */
732 		if (linenums)
733 			add_lnum(linenum, pos);
734 
735 		/*
736 		 * If this is a caseless search, convert uppercase in the
737 		 * input line to lowercase.
738 		 */
739 		if (caseless)
740 			for (p = q = line;  *p;  p++, q++)
741 				*q = isupper(*p) ? tolower(*p) : *p;
742 
743 		/*
744 		 * Remove any backspaces along with the preceeding char.
745 		 * This allows us to match text which is underlined or
746 		 * overstruck.
747 		 */
748 		for (p = q = line;  *p;  p++, q++)
749 			if (q > line && *p == '\b')
750 				/* Delete BS and preceeding char. */
751 				q -= 2;
752 			else
753 				/* Otherwise, just copy. */
754 				*q = *p;
755 
756 		/*
757 		 * Test the next line to see if we have a match.
758 		 * This is done in a variety of ways, depending
759 		 * on what pattern matching functions are available.
760 		 */
761 #ifdef REGCMP
762 		linematch = (regex(cpattern, line) != NULL);
763 #else
764 #ifdef RECOMP
765 		linematch = (re_exec(line) == 1);
766 #else
767 		linematch = match(pattern, line);
768 #endif
769 #endif
770 		/*
771 		 * We are successful if wantmatch and linematch are
772 		 * both true (want a match and got it),
773 		 * or both false (want a non-match and got it).
774 		 */
775 		if (((wantmatch && linematch) || (!wantmatch && !linematch)) &&
776 		      --n <= 0)
777 			/*
778 			 * Found the line.
779 			 */
780 			break;
781 	}
782 	jump_loc(linepos);
783 	return(1);
784 }
785 
786 #if !defined(REGCMP) && !defined(RECOMP)
787 /*
788  * We have neither regcmp() nor re_comp().
789  * We use this function to do simple pattern matching.
790  * It supports no metacharacters like *, etc.
791  */
792 static
match(pattern,buf)793 match(pattern, buf)
794 	char *pattern, *buf;
795 {
796 	register char *pp, *lp;
797 
798 	for ( ;  *buf != '\0';  buf++)
799 	{
800 		for (pp = pattern, lp = buf;  *pp == *lp;  pp++, lp++)
801 			if (*pp == '\0' || *lp == '\0')
802 				break;
803 		if (*pp == '\0')
804 			return (1);
805 	}
806 	return (0);
807 }
808 #endif
809