1/* $FreeBSD$ */
2/*
3 * Copyright (C) 1984-2021  Mark Nudelman
4 *
5 * You may distribute under the terms of either the GNU General Public
6 * License or the Less License, as specified in the README file.
7 *
8 * For more information, see the README file.
9 */
10
11
12/*
13 * Primitives for displaying the file on the screen,
14 * scrolling either forward or backward.
15 */
16
17#include "less.h"
18#include "position.h"
19
20public int screen_trashed;
21public int squished;
22public int no_back_scroll = 0;
23public int forw_prompt;
24
25extern int sigs;
26extern int top_scroll;
27extern int quiet;
28extern int sc_width, sc_height;
29extern int less_is_more;
30extern int plusoption;
31extern int forw_scroll;
32extern int back_scroll;
33extern int ignore_eoi;
34extern int clear_bg;
35extern int final_attr;
36extern int oldbot;
37#if HILITE_SEARCH
38extern int size_linebuf;
39extern int hilite_search;
40extern int status_col;
41#endif
42#if TAGS
43extern char *tagoption;
44#endif
45
46/*
47 * Sound the bell to indicate user is trying to move past end of file.
48 */
49	static void
50eof_bell(VOID_PARAM)
51{
52#if HAVE_TIME
53	static time_type last_eof_bell = 0;
54	time_type now = get_time();
55	if (now == last_eof_bell) /* max once per second */
56		return;
57	last_eof_bell = now;
58#endif
59	if (quiet == NOT_QUIET)
60		bell();
61	else
62		vbell();
63}
64
65/*
66 * Check to see if the end of file is currently displayed.
67 */
68	public int
69eof_displayed(VOID_PARAM)
70{
71	POSITION pos;
72
73	if (ignore_eoi)
74		return (0);
75
76	if (ch_length() == NULL_POSITION)
77		/*
78		 * If the file length is not known,
79		 * we can't possibly be displaying EOF.
80		 */
81		return (0);
82
83	/*
84	 * If the bottom line is empty, we are at EOF.
85	 * If the bottom line ends at the file length,
86	 * we must be just at EOF.
87	 */
88	pos = position(BOTTOM_PLUS_ONE);
89	return (pos == NULL_POSITION || pos == ch_length());
90}
91
92/*
93 * Check to see if the entire file is currently displayed.
94 */
95	public int
96entire_file_displayed(VOID_PARAM)
97{
98	POSITION pos;
99
100	/* Make sure last line of file is displayed. */
101	if (!eof_displayed())
102		return (0);
103
104	/* Make sure first line of file is displayed. */
105	pos = position(0);
106	return (pos == NULL_POSITION || pos == 0);
107}
108
109/*
110 * If the screen is "squished", repaint it.
111 * "Squished" means the first displayed line is not at the top
112 * of the screen; this can happen when we display a short file
113 * for the first time.
114 */
115	public void
116squish_check(VOID_PARAM)
117{
118	if (!squished)
119		return;
120	squished = 0;
121	repaint();
122}
123
124/*
125 * Display n lines, scrolling forward,
126 * starting at position pos in the input file.
127 * "force" means display the n lines even if we hit end of file.
128 * "only_last" means display only the last screenful if n > screen size.
129 * "nblank" is the number of blank lines to draw before the first
130 *   real line.  If nblank > 0, the pos must be NULL_POSITION.
131 *   The first real line after the blanks will start at ch_zero().
132 */
133	public void
134forw(n, pos, force, only_last, nblank)
135	int n;
136	POSITION pos;
137	int force;
138	int only_last;
139	int nblank;
140{
141	int nlines = 0;
142	int do_repaint;
143	static int first_time = 1;
144
145	squish_check();
146
147	/*
148	 * do_repaint tells us not to display anything till the end,
149	 * then just repaint the entire screen.
150	 * We repaint if we are supposed to display only the last
151	 * screenful and the request is for more than a screenful.
152	 * Also if the request exceeds the forward scroll limit
153	 * (but not if the request is for exactly a screenful, since
154	 * repainting itself involves scrolling forward a screenful).
155	 */
156	do_repaint = (only_last && n > sc_height-1) ||
157		(forw_scroll >= 0 && n > forw_scroll && n != sc_height-1);
158
159#if HILITE_SEARCH
160	if (hilite_search == OPT_ONPLUS || is_filtering() || status_col) {
161		prep_hilite(pos, pos + 4*size_linebuf, ignore_eoi ? 1 : -1);
162		pos = next_unfiltered(pos);
163	}
164#endif
165
166	if (!do_repaint)
167	{
168		if (top_scroll && n >= sc_height - 1 && pos != ch_length())
169		{
170			/*
171			 * Start a new screen.
172			 * {{ This is not really desirable if we happen
173			 *    to hit eof in the middle of this screen,
174			 *    but we don't yet know if that will happen. }}
175			 */
176			pos_clear();
177			add_forw_pos(pos);
178			force = 1;
179			if (less_is_more == 0) {
180				clear();
181				home();
182			}
183		}
184
185		if (pos != position(BOTTOM_PLUS_ONE) || empty_screen())
186		{
187			/*
188			 * This is not contiguous with what is
189			 * currently displayed.  Clear the screen image
190			 * (position table) and start a new screen.
191			 */
192			pos_clear();
193			add_forw_pos(pos);
194			force = 1;
195			if (top_scroll)
196			{
197				clear();
198				home();
199			} else if (!first_time && !is_filtering())
200			{
201				putstr("...skipping...\n");
202			}
203		}
204	}
205
206	while (--n >= 0)
207	{
208		/*
209		 * Read the next line of input.
210		 */
211		if (nblank > 0)
212		{
213			/*
214			 * Still drawing blanks; don't get a line
215			 * from the file yet.
216			 * If this is the last blank line, get ready to
217			 * read a line starting at ch_zero() next time.
218			 */
219			if (--nblank == 0)
220				pos = ch_zero();
221		} else
222		{
223			/*
224			 * Get the next line from the file.
225			 */
226			pos = forw_line(pos);
227#if HILITE_SEARCH
228			pos = next_unfiltered(pos);
229#endif
230			if (pos == NULL_POSITION)
231			{
232				/*
233				 * End of file: stop here unless the top line
234				 * is still empty, or "force" is true.
235				 * Even if force is true, stop when the last
236				 * line in the file reaches the top of screen.
237				 */
238				if (!force && position(TOP) != NULL_POSITION)
239					break;
240				if (!empty_lines(0, 0) &&
241				    !empty_lines(1, 1) &&
242				     empty_lines(2, sc_height-1))
243					break;
244			}
245		}
246		/*
247		 * Add the position of the next line to the position table.
248		 * Display the current line on the screen.
249		 */
250		add_forw_pos(pos);
251		nlines++;
252		if (do_repaint)
253			continue;
254		/*
255		 * If this is the first screen displayed and
256		 * we hit an early EOF (i.e. before the requested
257		 * number of lines), we "squish" the display down
258		 * at the bottom of the screen.
259		 * But don't do this if a + option or a -t option
260		 * was given.  These options can cause us to
261		 * start the display after the beginning of the file,
262		 * and it is not appropriate to squish in that case.
263		 */
264		if ((first_time || less_is_more) &&
265		    pos == NULL_POSITION && !top_scroll &&
266#if TAGS
267		    tagoption == NULL &&
268#endif
269		    !plusoption)
270		{
271			squished = 1;
272			continue;
273		}
274		put_line();
275#if 0
276		/* {{
277		 * Can't call clear_eol here.  The cursor might be at end of line
278		 * on an ignaw terminal, so clear_eol would clear the last char
279		 * of the current line instead of all of the next line.
280		 * If we really need to do this on clear_bg terminals, we need
281		 * to find a better way.
282		 * }}
283		 */
284		if (clear_bg && apply_at_specials(final_attr) != AT_NORMAL)
285		{
286			/*
287			 * Writing the last character on the last line
288			 * of the display may have scrolled the screen.
289			 * If we were in standout mode, clear_bg terminals
290			 * will fill the new line with the standout color.
291			 * Now we're in normal mode again, so clear the line.
292			 */
293			clear_eol();
294		}
295#endif
296		forw_prompt = 1;
297	}
298
299	if (nlines == 0 && !ignore_eoi)
300		eof_bell();
301	else if (do_repaint)
302		repaint();
303	first_time = 0;
304	(void) currline(BOTTOM);
305}
306
307/*
308 * Display n lines, scrolling backward.
309 */
310	public void
311back(n, pos, force, only_last)
312	int n;
313	POSITION pos;
314	int force;
315	int only_last;
316{
317	int nlines = 0;
318	int do_repaint;
319
320	squish_check();
321	do_repaint = (n > get_back_scroll() || (only_last && n > sc_height-1));
322#if HILITE_SEARCH
323	if (hilite_search == OPT_ONPLUS || is_filtering() || status_col) {
324		prep_hilite((pos < 3*size_linebuf) ?  0 : pos - 3*size_linebuf, pos, -1);
325	}
326#endif
327	while (--n >= 0)
328	{
329		/*
330		 * Get the previous line of input.
331		 */
332#if HILITE_SEARCH
333		pos = prev_unfiltered(pos);
334#endif
335
336		pos = back_line(pos);
337		if (pos == NULL_POSITION)
338		{
339			/*
340			 * Beginning of file: stop here unless "force" is true.
341			 */
342			if (!force)
343				break;
344		}
345		/*
346		 * Add the position of the previous line to the position table.
347		 * Display the line on the screen.
348		 */
349		add_back_pos(pos);
350		nlines++;
351		if (!do_repaint)
352		{
353			home();
354			add_line();
355			put_line();
356		}
357	}
358
359	if (nlines == 0)
360		eof_bell();
361	else if (do_repaint)
362		repaint();
363	else if (!oldbot)
364		lower_left();
365	(void) currline(BOTTOM);
366}
367
368/*
369 * Display n more lines, forward.
370 * Start just after the line currently displayed at the bottom of the screen.
371 */
372	public void
373forward(n, force, only_last)
374	int n;
375	int force;
376	int only_last;
377{
378	POSITION pos;
379
380	if (get_quit_at_eof() && eof_displayed() && !(ch_getflags() & CH_HELPFILE))
381	{
382		/*
383		 * If the -e flag is set and we're trying to go
384		 * forward from end-of-file, go on to the next file.
385		 */
386		if (edit_next(1))
387			quit(QUIT_OK);
388		return;
389	}
390
391	pos = position(BOTTOM_PLUS_ONE);
392	if (pos == NULL_POSITION && (!force || empty_lines(2, sc_height-1)))
393	{
394		if (ignore_eoi)
395		{
396			/*
397			 * ignore_eoi is to support A_F_FOREVER.
398			 * Back up until there is a line at the bottom
399			 * of the screen.
400			 */
401			if (empty_screen())
402				pos = ch_zero();
403			else
404			{
405				do
406				{
407					back(1, position(TOP), 1, 0);
408					pos = position(BOTTOM_PLUS_ONE);
409				} while (pos == NULL_POSITION);
410			}
411		} else
412		{
413			eof_bell();
414			return;
415		}
416	}
417	forw(n, pos, force, only_last, 0);
418}
419
420/*
421 * Display n more lines, backward.
422 * Start just before the line currently displayed at the top of the screen.
423 */
424	public void
425backward(n, force, only_last)
426	int n;
427	int force;
428	int only_last;
429{
430	POSITION pos;
431
432	pos = position(TOP);
433	if (pos == NULL_POSITION && (!force || position(BOTTOM) == 0))
434	{
435		eof_bell();
436		return;
437	}
438	back(n, pos, force, only_last);
439}
440
441/*
442 * Get the backwards scroll limit.
443 * Must call this function instead of just using the value of
444 * back_scroll, because the default case depends on sc_height and
445 * top_scroll, as well as back_scroll.
446 */
447	public int
448get_back_scroll(VOID_PARAM)
449{
450	if (no_back_scroll)
451		return (0);
452	if (back_scroll >= 0)
453		return (back_scroll);
454	if (top_scroll)
455		return (sc_height - 2);
456	return (10000); /* infinity */
457}
458
459/*
460 * Will the entire file fit on one screen?
461 */
462	public int
463get_one_screen(VOID_PARAM)
464{
465	int nlines;
466	POSITION pos = ch_zero();
467
468	for (nlines = 0;  nlines < sc_height;  nlines++)
469	{
470		pos = forw_line(pos);
471		if (pos == NULL_POSITION) break;
472	}
473	return (nlines < sc_height);
474}
475