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