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