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