1/*	$NetBSD: prim.c,v 1.9 2003/10/13 14:34:25 agc 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
36static char sccsid[] = "@(#)prim.c	8.1 (Berkeley) 6/6/93";
37#else
38__RCSID("$NetBSD: prim.c,v 1.9 2003/10/13 14:34:25 agc 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
54int back_scroll = -1;
55int hit_eof;		/* keeps track of how many times we hit end of file */
56int screen_trashed;
57
58static int squished;
59
60
61static int match(char *, char *);
62static int badmark __P((int));
63
64/*
65 * Check to see if the end of file is currently "displayed".
66 */
67void
68eof_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 */
91void
92squish_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 */
106void
107forw(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 */
205void
206back(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 */
253void
254forward(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 */
283void
284backward(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 */
304void
305prepaint(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 */
317void
318repaint()
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 */
333void
334jump_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 */
355void
356jump_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 */
402void
403jump_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 */
438void
439jump_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 */
513static off_t marks[NMARKS];
514
515/*
516 * Initialize the mark table to show no marks are set.
517 */
518void
519init_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 */
531static int
532badmark(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 */
546void
547setmark(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
556void
557lastmark()
558/*###547 [cc] conflicting types for `lastmark'%%%*/
559{
560	marks[LASTMARK] = position(TOP);
561}
562
563/*
564 * Go to a previously set mark.
565 */
566void
567gomark(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 */
596int
597get_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 occurence of a specified pattern,
608 * either forward or backward.
609 */
610int
611search(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 */
846static int
847match(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