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