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