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