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