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