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