stdio.h defines BUFSIZ
[unix-history] / usr / src / usr.bin / more / prim.c
CommitLineData
bfe13c81
KB
1/*
2 * Copyright (c) 1988 Mark Nudleman
3 * Copyright (c) 1988 Regents of the University of California.
4 * All rights reserved.
5 *
f15db449 6 * %sccs.include.redist.c%
bfe13c81
KB
7 */
8
9#ifndef lint
f15db449 10static char sccsid[] = "@(#)prim.c 5.8 (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;
bfe13c81 35
966c6ec0
KB
36off_t position(), forw_line(), back_line(), forw_raw_line(), back_raw_line();
37off_t ch_length(), ch_tell();
bfe13c81
KB
38
39/*
40 * Check to see if the end of file is currently "displayed".
41 */
bfe13c81
KB
42eof_check()
43{
966c6ec0 44 off_t pos;
bfe13c81
KB
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 */
bfe13c81
KB
64squish_check()
65{
966c6ec0
KB
66 if (squished) {
67 squished = 0;
68 repaint();
69 }
bfe13c81
KB
70}
71
72/*
966c6ec0 73 * Display n lines, scrolling forward, starting at position pos in the
4a7551ea
KB
74 * input file. "only_last" means display only the last screenful if
75 * n > screen size.
bfe13c81 76 */
4a7551ea 77forw(n, pos, only_last)
bfe13c81 78 register int n;
966c6ec0 79 off_t pos;
bfe13c81
KB
80 int only_last;
81{
4a7551ea 82 extern int short_file;
bfe13c81 83 static int first_time = 1;
966c6ec0 84 int eof = 0, do_repaint;
bfe13c81
KB
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
966c6ec0
KB
94 if (!do_repaint) {
95 if (top_scroll && n >= sc_height - 1) {
bfe13c81
KB
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 */
966c6ec0 102 clear();
bfe13c81 103 home();
966c6ec0 104 } else {
bfe13c81
KB
105 lower_left();
106 clear_eol();
107 }
108
966c6ec0
KB
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)) {
bfe13c81
KB
115 pos_clear();
116 add_forw_pos(pos);
966c6ec0
KB
117 if (top_scroll) {
118 clear();
bfe13c81
KB
119 home();
120 } else if (!first_time)
bfe13c81 121 putstr("...skipping...\n");
bfe13c81
KB
122 }
123 }
124
4a7551ea 125 for (short_file = 0; --n >= 0;) {
bfe13c81
KB
126 /*
127 * Read the next line of input.
128 */
129 pos = forw_line(pos);
966c6ec0 130 if (pos == NULL_POSITION) {
bfe13c81 131 /*
4a7551ea
KB
132 * end of file; copy the table if the file was
133 * too small for an entire screen.
bfe13c81
KB
134 */
135 eof = 1;
4a7551ea
KB
136 if (position(TOP) == NULL_POSITION) {
137 copytable();
138 if (!position(TOP))
139 short_file = 1;
140 }
141 break;
bfe13c81
KB
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);
bfe13c81
KB
148 if (do_repaint)
149 continue;
150 /*
966c6ec0
KB
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,
bfe13c81
KB
156 * and it is not appropriate to squish in that case.
157 */
966c6ec0 158 if (first_time && line == NULL && !top_scroll && !tagoption) {
bfe13c81
KB
159 squished = 1;
160 continue;
161 }
bfe13c81
KB
162 put_line();
163 }
164
165 if (eof && !sigs)
166 hit_eof++;
167 else
168 eof_check();
966c6ec0 169 if (do_repaint)
bfe13c81
KB
170 repaint();
171 first_time = 0;
172 (void) currline(BOTTOM);
173}
174
175/*
176 * Display n lines, scrolling backward.
177 */
4a7551ea 178back(n, pos, only_last)
bfe13c81 179 register int n;
966c6ec0 180 off_t pos;
bfe13c81
KB
181 int only_last;
182{
bfe13c81
KB
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)
4a7551ea 195 break;
bfe13c81
KB
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);
bfe13c81
KB
201 if (!do_repaint)
202 {
203 home();
204 add_line();
205 put_line();
206 }
207 }
208
209 eof_check();
966c6ec0 210 if (do_repaint)
bfe13c81
KB
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 */
bfe13c81
KB
219forward(n, only_last)
220 int n;
221 int only_last;
222{
966c6ec0 223 off_t pos;
bfe13c81 224
966c6ec0 225 if (hit_eof) {
bfe13c81 226 /*
966c6ec0
KB
227 * If we're trying to go forward from end-of-file,
228 * go on to the next file.
bfe13c81
KB
229 */
230 next_file(1);
231 return;
232 }
233
234 pos = position(BOTTOM_PLUS_ONE);
235 if (pos == NULL_POSITION)
236 {
bfe13c81
KB
237 hit_eof++;
238 return;
239 }
4a7551ea 240 forw(n, pos, only_last);
bfe13c81
KB
241}
242
243/*
244 * Display n more lines, backward.
245 * Start just before the line currently displayed at the top of the screen.
246 */
bfe13c81
KB
247backward(n, only_last)
248 int n;
249 int only_last;
250{
966c6ec0 251 off_t pos;
bfe13c81
KB
252
253 pos = position(TOP);
966c6ec0
KB
254 /*
255 * This will almost never happen, because the top line is almost
256 * never empty.
257 */
bfe13c81 258 if (pos == NULL_POSITION)
bfe13c81 259 return;
4a7551ea 260 back(n, pos, only_last);
bfe13c81
KB
261}
262
263/*
264 * Repaint the screen, starting from a specified position.
265 */
966c6ec0
KB
266prepaint(pos)
267 off_t pos;
bfe13c81
KB
268{
269 hit_eof = 0;
4a7551ea 270 forw(sc_height-1, pos, 0);
bfe13c81
KB
271 screen_trashed = 0;
272}
273
274/*
275 * Repaint the screen.
276 */
bfe13c81
KB
277repaint()
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 */
bfe13c81
KB
291jump_forw()
292{
966c6ec0 293 off_t pos;
bfe13c81
KB
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);
4a7551ea 305 back(sc_height - 1, pos, 0);
bfe13c81
KB
306}
307
308/*
309 * Jump to line n in the file.
310 */
bfe13c81
KB
311jump_back(n)
312 register int n;
313{
966c6ec0 314 register int c, nlines;
bfe13c81
KB
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 */
966c6ec0 324 if (ch_seek((off_t)0)) {
bfe13c81
KB
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++)
bfe13c81 340 while ((c = ch_forw_get()) != '\n')
966c6ec0 341 if (c == EOI) {
bfe13c81 342 char message[40];
79a08ff0 343 (void)sprintf(message, "File has only %d lines",
966c6ec0 344 nlines - 1);
bfe13c81
KB
345 error(message);
346 return;
347 }
bfe13c81
KB
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 */
bfe13c81
KB
356jump_percent(percent)
357 int percent;
358{
966c6ec0 359 off_t pos, len, ch_length();
bfe13c81
KB
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 */
bfe13c81 390jump_loc(pos)
966c6ec0 391 off_t pos;
bfe13c81
KB
392{
393 register int nline;
966c6ec0 394 off_t tpos;
bfe13c81 395
966c6ec0 396 if ((nline = onscreen(pos)) >= 0) {
bfe13c81
KB
397 /*
398 * The line is currently displayed.
399 * Just scroll there.
400 */
4a7551ea 401 forw(nline, position(BOTTOM_PLUS_ONE), 0);
bfe13c81
KB
402 return;
403 }
404
405 /*
406 * Line is not on screen.
407 * Seek to the desired location.
408 */
966c6ec0 409 if (ch_seek(pos)) {
bfe13c81
KB
410 error("Cannot seek to that position");
411 return;
412 }
413
414 /*
966c6ec0
KB
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.
bfe13c81
KB
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);
966c6ec0
KB
425 if (tpos != NULL_POSITION && pos < tpos) {
426 off_t npos = pos;
bfe13c81
KB
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
966c6ec0 434 if (npos < tpos) {
bfe13c81
KB
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 */
4a7551ea 447 back(nline, npos, 0);
bfe13c81
KB
448 return;
449 }
450 /*
451 * Remember where we were; clear and paint the screen.
452 */
966c6ec0
KB
453 lastmark();
454 prepaint(pos);
bfe13c81
KB
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 */
966c6ec0 463static off_t marks[NMARKS];
bfe13c81
KB
464
465/*
466 * Initialize the mark table to show no marks are set.
467 */
bfe13c81
KB
468init_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
480badmark(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 */
bfe13c81
KB
494setmark(c)
495 int c;
496{
497 if (badmark(c))
498 return;
499 marks[c-'a'] = position(TOP);
500}
501
bfe13c81
KB
502lastmark()
503{
504 marks[LASTMARK] = position(TOP);
505}
506
507/*
508 * Go to a previously set mark.
509 */
bfe13c81
KB
510gomark(c)
511 int c;
512{
966c6ec0 513 off_t pos;
bfe13c81 514
148d5db7 515 if (c == '\'') {
bfe13c81 516 pos = marks[LASTMARK];
148d5db7
KB
517 if (pos == NULL_POSITION)
518 pos = 0;
519 }
520 else {
521 if (badmark(c))
522 return;
bfe13c81 523 pos = marks[c-'a'];
148d5db7
KB
524 if (pos == NULL_POSITION) {
525 error("mark not set");
526 return;
527 }
528 }
529 jump_loc(pos);
bfe13c81
KB
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 */
bfe13c81
KB
538get_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 */
bfe13c81
KB
551search(search_forward, pattern, n, wantmatch)
552 register int search_forward;
553 register char *pattern;
554 register int n;
555 int wantmatch;
556{
966c6ec0 557 off_t pos, linepos;
bfe13c81
KB
558 register char *p;
559 register char *q;
560 int linenum;
561 int linematch;
966c6ec0 562#ifdef RECOMP
bfe13c81
KB
563 char *re_comp();
564 char *errmsg;
565#else
966c6ec0 566#ifdef REGCMP
bfe13c81
KB
567 char *regcmp();
568 static char *cpattern = NULL;
569#else
570 static char lpbuf[100];
571 static char *last_pattern = NULL;
79a08ff0 572 char *strcpy();
bfe13c81
KB
573#endif
574#endif
575
966c6ec0
KB
576 /*
577 * For a caseless search, convert any uppercase in the pattern to
578 * lowercase.
579 */
bfe13c81 580 if (caseless && pattern != NULL)
966c6ec0
KB
581 for (p = pattern; *p; p++)
582 if (isupper(*p))
583 *p = tolower(*p);
584#ifdef RECOMP
bfe13c81
KB
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);
966c6ec0 593 return(0);
bfe13c81
KB
594 }
595#else
966c6ec0 596#ifdef REGCMP
bfe13c81
KB
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");
966c6ec0 606 return(0);
bfe13c81
KB
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");
966c6ec0 617 return(0);
bfe13c81
KB
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");
966c6ec0 632 return(0);
bfe13c81
KB
633 }
634 pattern = last_pattern;
635 } else
636 {
79a08ff0 637 (void)strcpy(lpbuf, pattern);
bfe13c81
KB
638 last_pattern = lpbuf;
639 }
640#endif
641#endif
642
643 /*
644 * Figure out where to start the search.
645 */
646
966c6ec0 647 if (position(TOP) == NULL_POSITION) {
bfe13c81 648 /*
966c6ec0
KB
649 * Nothing is currently displayed. Start at the beginning
650 * of the file. (This case is mainly for searches from the
651 * command line.
bfe13c81 652 */
966c6ec0
KB
653 pos = (off_t)0;
654 } else if (!search_forward) {
bfe13c81
KB
655 /*
656 * Backward search: start just before the top line
657 * displayed on the screen.
658 */
659 pos = position(TOP);
966c6ec0 660 } else {
bfe13c81
KB
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");
966c6ec0 673 return(0);
bfe13c81
KB
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 */
966c6ec0 688 return(0);
bfe13c81
KB
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");
966c6ec0 718 return(0);
bfe13c81
KB
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
966c6ec0
KB
729 /*
730 * If this is a caseless search, convert uppercase in the
731 * input line to lowercase.
732 */
bfe13c81 733 if (caseless)
966c6ec0
KB
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;
bfe13c81
KB
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 */
966c6ec0 755#ifdef REGCMP
bfe13c81
KB
756 linematch = (regex(cpattern, line) != NULL);
757#else
966c6ec0 758#ifdef RECOMP
bfe13c81
KB
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 }
bfe13c81 776 jump_loc(linepos);
966c6ec0 777 return(1);
bfe13c81
KB
778}
779
966c6ec0 780#if !defined(REGCMP) && !defined(RECOMP)
bfe13c81
KB
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 */
966c6ec0 786static
bfe13c81
KB
787match(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