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