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