4.4BSD snapshot (revision 8.1); add 1993 to copyright
[unix-history] / usr / src / usr.bin / more / prim.c
CommitLineData
bfe13c81
KB
1/*
2 * Copyright (c) 1988 Mark Nudleman
1e3ab5ee
KB
3 * Copyright (c) 1988, 1993
4 * The Regents of the University of California. All rights reserved.
bfe13c81 5 *
f15db449 6 * %sccs.include.redist.c%
bfe13c81
KB
7 */
8
9#ifndef lint
1e3ab5ee 10static char sccsid[] = "@(#)prim.c 8.1 (Berkeley) %G%";
bfe13c81
KB
11#endif /* not lint */
12
13/*
14 * Primitives for displaying the file on the screen.
15 */
16
966c6ec0
KB
17#include <sys/types.h>
18#include <stdio.h>
19#include <ctype.h>
20#include <less.h>
bfe13c81 21
966c6ec0
KB
22int back_scroll = -1;
23int hit_eof; /* keeps track of how many times we hit end of file */
24int screen_trashed;
bfe13c81
KB
25
26static int squished;
27
bfe13c81 28extern int sigs;
bfe13c81 29extern int top_scroll;
bfe13c81 30extern int sc_width, sc_height;
bfe13c81
KB
31extern int caseless;
32extern int linenums;
bfe13c81 33extern int tagoption;
966c6ec0 34extern char *line;
441de7b4 35extern int retain_below;
bfe13c81 36
966c6ec0
KB
37off_t position(), forw_line(), back_line(), forw_raw_line(), back_raw_line();
38off_t ch_length(), ch_tell();
bfe13c81
KB
39
40/*
41 * Check to see if the end of file is currently "displayed".
42 */
bfe13c81
KB
43eof_check()
44{
966c6ec0 45 off_t pos;
bfe13c81
KB
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 */
bfe13c81
KB
65squish_check()
66{
966c6ec0
KB
67 if (squished) {
68 squished = 0;
69 repaint();
70 }
bfe13c81
KB
71}
72
73/*
966c6ec0 74 * Display n lines, scrolling forward, starting at position pos in the
4a7551ea
KB
75 * input file. "only_last" means display only the last screenful if
76 * n > screen size.
bfe13c81 77 */
4a7551ea 78forw(n, pos, only_last)
bfe13c81 79 register int n;
966c6ec0 80 off_t pos;
bfe13c81
KB
81 int only_last;
82{
4a7551ea 83 extern int short_file;
bfe13c81 84 static int first_time = 1;
966c6ec0 85 int eof = 0, do_repaint;
bfe13c81
KB
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
966c6ec0
KB
95 if (!do_repaint) {
96 if (top_scroll && n >= sc_height - 1) {
bfe13c81
KB
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 */
966c6ec0 103 clear();
bfe13c81 104 home();
966c6ec0 105 } else {
bfe13c81
KB
106 lower_left();
107 clear_eol();
108 }
109
966c6ec0
KB
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)) {
bfe13c81
KB
116 pos_clear();
117 add_forw_pos(pos);
966c6ec0
KB
118 if (top_scroll) {
119 clear();
bfe13c81
KB
120 home();
121 } else if (!first_time)
bfe13c81 122 putstr("...skipping...\n");
bfe13c81
KB
123 }
124 }
125
4a7551ea 126 for (short_file = 0; --n >= 0;) {
bfe13c81
KB
127 /*
128 * Read the next line of input.
129 */
130 pos = forw_line(pos);
966c6ec0 131 if (pos == NULL_POSITION) {
bfe13c81 132 /*
4a7551ea
KB
133 * end of file; copy the table if the file was
134 * too small for an entire screen.
bfe13c81
KB
135 */
136 eof = 1;
4a7551ea
KB
137 if (position(TOP) == NULL_POSITION) {
138 copytable();
139 if (!position(TOP))
140 short_file = 1;
141 }
142 break;
bfe13c81
KB
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);
bfe13c81
KB
149 if (do_repaint)
150 continue;
151 /*
966c6ec0
KB
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,
bfe13c81
KB
157 * and it is not appropriate to squish in that case.
158 */
966c6ec0 159 if (first_time && line == NULL && !top_scroll && !tagoption) {
bfe13c81
KB
160 squished = 1;
161 continue;
162 }
bfe13c81
KB
163 put_line();
164 }
165
166 if (eof && !sigs)
167 hit_eof++;
168 else
169 eof_check();
966c6ec0 170 if (do_repaint)
bfe13c81
KB
171 repaint();
172 first_time = 0;
173 (void) currline(BOTTOM);
174}
175
176/*
177 * Display n lines, scrolling backward.
178 */
4a7551ea 179back(n, pos, only_last)
bfe13c81 180 register int n;
966c6ec0 181 off_t pos;
bfe13c81
KB
182 int only_last;
183{
bfe13c81
KB
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)
4a7551ea 196 break;
bfe13c81
KB
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);
bfe13c81
KB
202 if (!do_repaint)
203 {
441de7b4
EW
204 if (retain_below)
205 {
206 lower_left();
207 clear_eol();
208 }
bfe13c81
KB
209 home();
210 add_line();
211 put_line();
212 }
213 }
214
215 eof_check();
966c6ec0 216 if (do_repaint)
bfe13c81
KB
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 */
bfe13c81
KB
225forward(n, only_last)
226 int n;
227 int only_last;
228{
966c6ec0 229 off_t pos;
bfe13c81 230
966c6ec0 231 if (hit_eof) {
bfe13c81 232 /*
966c6ec0
KB
233 * If we're trying to go forward from end-of-file,
234 * go on to the next file.
bfe13c81
KB
235 */
236 next_file(1);
237 return;
238 }
239
240 pos = position(BOTTOM_PLUS_ONE);
241 if (pos == NULL_POSITION)
242 {
bfe13c81
KB
243 hit_eof++;
244 return;
245 }
4a7551ea 246 forw(n, pos, only_last);
bfe13c81
KB
247}
248
249/*
250 * Display n more lines, backward.
251 * Start just before the line currently displayed at the top of the screen.
252 */
bfe13c81
KB
253backward(n, only_last)
254 int n;
255 int only_last;
256{
966c6ec0 257 off_t pos;
bfe13c81
KB
258
259 pos = position(TOP);
966c6ec0
KB
260 /*
261 * This will almost never happen, because the top line is almost
262 * never empty.
263 */
bfe13c81 264 if (pos == NULL_POSITION)
bfe13c81 265 return;
4a7551ea 266 back(n, pos, only_last);
bfe13c81
KB
267}
268
269/*
270 * Repaint the screen, starting from a specified position.
271 */
966c6ec0
KB
272prepaint(pos)
273 off_t pos;
bfe13c81
KB
274{
275 hit_eof = 0;
4a7551ea 276 forw(sc_height-1, pos, 0);
bfe13c81
KB
277 screen_trashed = 0;
278}
279
280/*
281 * Repaint the screen.
282 */
bfe13c81
KB
283repaint()
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 */
bfe13c81
KB
297jump_forw()
298{
966c6ec0 299 off_t pos;
bfe13c81
KB
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);
4a7551ea 311 back(sc_height - 1, pos, 0);
bfe13c81
KB
312}
313
314/*
315 * Jump to line n in the file.
316 */
bfe13c81
KB
317jump_back(n)
318 register int n;
319{
966c6ec0 320 register int c, nlines;
bfe13c81
KB
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 */
966c6ec0 330 if (ch_seek((off_t)0)) {
bfe13c81
KB
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++)
bfe13c81 346 while ((c = ch_forw_get()) != '\n')
966c6ec0 347 if (c == EOI) {
bfe13c81 348 char message[40];
79a08ff0 349 (void)sprintf(message, "File has only %d lines",
966c6ec0 350 nlines - 1);
bfe13c81
KB
351 error(message);
352 return;
353 }
bfe13c81
KB
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 */
bfe13c81
KB
362jump_percent(percent)
363 int percent;
364{
966c6ec0 365 off_t pos, len, ch_length();
bfe13c81
KB
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 */
bfe13c81 396jump_loc(pos)
966c6ec0 397 off_t pos;
bfe13c81
KB
398{
399 register int nline;
966c6ec0 400 off_t tpos;
bfe13c81 401
966c6ec0 402 if ((nline = onscreen(pos)) >= 0) {
bfe13c81
KB
403 /*
404 * The line is currently displayed.
405 * Just scroll there.
406 */
4a7551ea 407 forw(nline, position(BOTTOM_PLUS_ONE), 0);
bfe13c81
KB
408 return;
409 }
410
411 /*
412 * Line is not on screen.
413 * Seek to the desired location.
414 */
966c6ec0 415 if (ch_seek(pos)) {
bfe13c81
KB
416 error("Cannot seek to that position");
417 return;
418 }
419
420 /*
966c6ec0
KB
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.
bfe13c81
KB
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);
966c6ec0
KB
431 if (tpos != NULL_POSITION && pos < tpos) {
432 off_t npos = pos;
bfe13c81
KB
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
966c6ec0 440 if (npos < tpos) {
bfe13c81
KB
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 */
4a7551ea 453 back(nline, npos, 0);
bfe13c81
KB
454 return;
455 }
456 /*
457 * Remember where we were; clear and paint the screen.
458 */
966c6ec0
KB
459 lastmark();
460 prepaint(pos);
bfe13c81
KB
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 */
966c6ec0 469static off_t marks[NMARKS];
bfe13c81
KB
470
471/*
472 * Initialize the mark table to show no marks are set.
473 */
bfe13c81
KB
474init_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
486badmark(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 */
bfe13c81
KB
500setmark(c)
501 int c;
502{
503 if (badmark(c))
504 return;
505 marks[c-'a'] = position(TOP);
506}
507
bfe13c81
KB
508lastmark()
509{
510 marks[LASTMARK] = position(TOP);
511}
512
513/*
514 * Go to a previously set mark.
515 */
bfe13c81
KB
516gomark(c)
517 int c;
518{
966c6ec0 519 off_t pos;
bfe13c81 520
148d5db7 521 if (c == '\'') {
bfe13c81 522 pos = marks[LASTMARK];
148d5db7
KB
523 if (pos == NULL_POSITION)
524 pos = 0;
525 }
526 else {
527 if (badmark(c))
528 return;
bfe13c81 529 pos = marks[c-'a'];
148d5db7
KB
530 if (pos == NULL_POSITION) {
531 error("mark not set");
532 return;
533 }
534 }
535 jump_loc(pos);
bfe13c81
KB
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 */
bfe13c81
KB
544get_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 */
bfe13c81
KB
557search(search_forward, pattern, n, wantmatch)
558 register int search_forward;
559 register char *pattern;
560 register int n;
561 int wantmatch;
562{
966c6ec0 563 off_t pos, linepos;
bfe13c81
KB
564 register char *p;
565 register char *q;
566 int linenum;
567 int linematch;
966c6ec0 568#ifdef RECOMP
bfe13c81
KB
569 char *re_comp();
570 char *errmsg;
571#else
966c6ec0 572#ifdef REGCMP
bfe13c81
KB
573 char *regcmp();
574 static char *cpattern = NULL;
575#else
576 static char lpbuf[100];
577 static char *last_pattern = NULL;
79a08ff0 578 char *strcpy();
bfe13c81
KB
579#endif
580#endif
581
966c6ec0
KB
582 /*
583 * For a caseless search, convert any uppercase in the pattern to
584 * lowercase.
585 */
bfe13c81 586 if (caseless && pattern != NULL)
966c6ec0
KB
587 for (p = pattern; *p; p++)
588 if (isupper(*p))
589 *p = tolower(*p);
590#ifdef RECOMP
bfe13c81
KB
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);
966c6ec0 599 return(0);
bfe13c81
KB
600 }
601#else
966c6ec0 602#ifdef REGCMP
bfe13c81
KB
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");
966c6ec0 612 return(0);
bfe13c81
KB
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");
966c6ec0 623 return(0);
bfe13c81
KB
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");
966c6ec0 638 return(0);
bfe13c81
KB
639 }
640 pattern = last_pattern;
641 } else
642 {
79a08ff0 643 (void)strcpy(lpbuf, pattern);
bfe13c81
KB
644 last_pattern = lpbuf;
645 }
646#endif
647#endif
648
649 /*
650 * Figure out where to start the search.
651 */
652
966c6ec0 653 if (position(TOP) == NULL_POSITION) {
bfe13c81 654 /*
966c6ec0
KB
655 * Nothing is currently displayed. Start at the beginning
656 * of the file. (This case is mainly for searches from the
657 * command line.
bfe13c81 658 */
966c6ec0
KB
659 pos = (off_t)0;
660 } else if (!search_forward) {
bfe13c81
KB
661 /*
662 * Backward search: start just before the top line
663 * displayed on the screen.
664 */
665 pos = position(TOP);
966c6ec0 666 } else {
bfe13c81
KB
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");
966c6ec0 679 return(0);
bfe13c81
KB
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 */
966c6ec0 694 return(0);
bfe13c81
KB
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");
966c6ec0 724 return(0);
bfe13c81
KB
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
966c6ec0
KB
735 /*
736 * If this is a caseless search, convert uppercase in the
737 * input line to lowercase.
738 */
bfe13c81 739 if (caseless)
966c6ec0
KB
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;
bfe13c81
KB
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 */
966c6ec0 761#ifdef REGCMP
bfe13c81
KB
762 linematch = (regex(cpattern, line) != NULL);
763#else
966c6ec0 764#ifdef RECOMP
bfe13c81
KB
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 }
bfe13c81 782 jump_loc(linepos);
966c6ec0 783 return(1);
bfe13c81
KB
784}
785
966c6ec0 786#if !defined(REGCMP) && !defined(RECOMP)
bfe13c81
KB
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 */
966c6ec0 792static
bfe13c81
KB
793match(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