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