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