xref: /freebsd/contrib/less/forwback.c (revision dafba19e42e78cd3d7c9264ece49ddd3d7d70da5)
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 lbool 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 		lbell();
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,lbool do_stop_on_form_feed,int nblank)221 public void forw(int n, POSITION pos, lbool force, lbool only_last, lbool to_newline, lbool do_stop_on_form_feed, int nblank)
222 {
223 	int nlines = 0;
224 	lbool do_repaint;
225 	lbool newline;
226 	lbool first_line = TRUE;
227 	lbool need_home = FALSE;
228 
229 	if (pos != NULL_POSITION)
230 		pos = after_header_pos(pos);
231 	squish_check();
232 
233 	/*
234 	 * do_repaint tells us not to display anything till the end,
235 	 * then just repaint the entire screen.
236 	 * We repaint if we are supposed to display only the last
237 	 * screenful and the request is for more than a screenful.
238 	 * Also if the request exceeds the forward scroll limit
239 	 * (but not if the request is for exactly a screenful, since
240 	 * repainting itself involves scrolling forward a screenful).
241 	 */
242 	do_repaint = (only_last && n > sc_height-1) ||
243 		(forw_scroll >= 0 && n > forw_scroll && n != sc_height-1);
244 	if (!do_repaint)
245 	{
246 		if (top_scroll && n >= sc_height - 1 && pos != ch_length())
247 		{
248 			/*
249 			 * Start a new screen.
250 			 * {{ This is not really desirable if we happen
251 			 *    to hit eof in the middle of this screen,
252 			 *    but we don't yet know if that will happen. }}
253 			 */
254 			pos_clear();
255 			force = TRUE;
256 			need_home = (less_is_more == 0) ? TRUE : FALSE;
257 		}
258 
259 		if (pos != position(BOTTOM_PLUS_ONE) || empty_screen())
260 		{
261 			/*
262 			 * This is not contiguous with what is
263 			 * currently displayed.  Clear the screen image
264 			 * (position table) and start a new screen.
265 			 */
266 			pos_clear();
267 			force = TRUE;
268 			if (top_scroll)
269 			{
270 				need_home = TRUE;
271 			} else if (!first_time && !is_filtering() && full_screen)
272 			{
273 				putstr("...skipping...\n");
274 			}
275 		}
276 	}
277 
278 	while (--n >= 0)
279 	{
280 		POSITION linepos = NULL_POSITION;
281 		/*
282 		 * Read the next line of input.
283 		 */
284 		if (nblank > 0)
285 		{
286 			/*
287 			 * Still drawing blanks; don't get a line
288 			 * from the file yet.
289 			 * If this is the last blank line, get ready to
290 			 * read a line starting at ch_zero() next time.
291 			 */
292 			if (--nblank == 0)
293 				pos = ch_zero();
294 		} else
295 		{
296 			/*
297 			 * Get the next line from the file.
298 			 */
299 			POSITION opos = pos;
300 			pos = forw_line(pos, &linepos, &newline);
301 			if (to_newline && !newline)
302 				++n;
303 			if (pos == NULL_POSITION)
304 			{
305 				/*
306 				 * End of file: stop here unless the top line
307 				 * is still empty, or "force" is true.
308 				 * Even if force is true, stop when the last
309 				 * line in the file reaches the top of screen.
310 				 */
311 				soft_eof = opos;
312 				linepos = opos;
313 				if (ABORT_SIGS() ||
314 				   (!force && position(TOP) != NULL_POSITION) ||
315 				   (!empty_lines(0, 0) && !empty_lines(1, 1) && empty_lines(2, sc_height-1)))
316 				{
317 					pos = opos;
318 					break;
319 				}
320 			}
321 		}
322 		/*
323 		 * Add the position of the next line to the position table.
324 		 * Display the current line on the screen.
325 		 */
326 		add_forw_pos(linepos, first_line);
327 		first_line = FALSE;
328 		nlines++;
329 		if (do_repaint)
330 			continue;
331 		if (need_home)
332 		{
333 			lclear();
334 			home();
335 			need_home = FALSE;
336 		}
337 		/*
338 		 * If this is the first screen displayed and
339 		 * we hit an early EOF (i.e. before the requested
340 		 * number of lines), we "squish" the display down
341 		 * at the bottom of the screen.
342 		 * But don't do this if a + option or a -t option
343 		 * was given.  These options can cause us to
344 		 * start the display after the beginning of the file,
345 		 * and it is not appropriate to squish in that case.
346 		 */
347 		if ((first_time || less_is_more) &&
348 		    pos == NULL_POSITION && !top_scroll &&
349 		    header_lines == 0 && header_cols == 0 &&
350 #if TAGS
351 		    tagoption == NULL &&
352 #endif
353 		    !plusoption)
354 		{
355 			squished = TRUE;
356 			continue;
357 		}
358 		put_line(TRUE);
359 		if (do_stop_on_form_feed && !do_repaint && line_is_ff() && position(TOP) != NULL_POSITION)
360 			break;
361 		forw_prompt = 1;
362 	}
363 	if (!first_line)
364 		add_forw_pos(pos, FALSE);
365 	if (nlines == 0 && !ignore_eoi)
366 		eof_bell();
367 	else if (do_repaint)
368 		repaint();
369 	else
370 	{
371 		overlay_header();
372 		/* lower_left(); {{ considered harmful? }} */
373 	}
374 	first_time = FALSE;
375 	(void) currline(BOTTOM);
376 }
377 
378 /*
379  * Display n lines, scrolling backward.
380  */
back(int n,POSITION pos,lbool force,lbool only_last,lbool to_newline,lbool do_stop_on_form_feed)381 public void back(int n, POSITION pos, lbool force, lbool only_last, lbool to_newline, lbool do_stop_on_form_feed)
382 {
383 	int nlines = 0;
384 	lbool do_repaint;
385 	lbool newline;
386 
387 	squish_check();
388 	do_repaint = (n > get_back_scroll() || (only_last && n > sc_height-1) || header_lines > 0);
389 
390 	while (--n >= 0)
391 	{
392 		/*
393 		 * Get the previous line of input.
394 		 */
395 		pos = back_line(pos, &newline);
396 		if (to_newline && !newline)
397 			++n;
398 		if (pos == NULL_POSITION)
399 		{
400 			/*
401 			 * Beginning of file: stop here unless "force" is true.
402 			 */
403 			if (!force)
404 				break;
405 		}
406 		if (pos != after_header_pos(pos))
407 		{
408 			/*
409 			 * Don't allow scrolling back to before the current header line.
410 			 */
411 			break;
412 		}
413 		/*
414 		 * Add the position of the previous line to the position table.
415 		 * Display the line on the screen.
416 		 */
417 		add_back_pos(pos);
418 		nlines++;
419 		if (!do_repaint)
420 		{
421 			home();
422 			add_line();
423 			put_line(FALSE);
424 			if (do_stop_on_form_feed && line_is_ff())
425 				break;
426 		}
427 	}
428 	if (nlines == 0)
429 		eof_bell();
430 	else if (do_repaint)
431 		repaint();
432 	else
433 	{
434 		overlay_header();
435 		lower_left();
436 	}
437 	(void) currline(BOTTOM);
438 }
439 
440 /*
441  * Display n more lines, forward.
442  * Start just after the line currently displayed at the bottom of the screen.
443  */
forward(int n,lbool force,lbool only_last,lbool to_newline)444 public void forward(int n, lbool force, lbool only_last, lbool to_newline)
445 {
446 	POSITION pos;
447 
448 	if (get_quit_at_eof() && eof_displayed(FALSE) && !(ch_getflags() & CH_HELPFILE))
449 	{
450 		/*
451 		 * If the -e flag is set and we're trying to go
452 		 * forward from end-of-file, go on to the next file.
453 		 */
454 		if (edit_next(1))
455 			quit(QUIT_OK);
456 		return;
457 	}
458 
459 	pos = position(BOTTOM_PLUS_ONE);
460 	if (pos == NULL_POSITION && (!force || empty_lines(2, sc_height-1)))
461 	{
462 		if (ignore_eoi)
463 		{
464 			/*
465 			 * ignore_eoi is to support A_F_FOREVER.
466 			 * Back up until there is a line at the bottom
467 			 * of the screen.
468 			 */
469 			if (empty_screen())
470 				pos = ch_zero();
471 			else
472 			{
473 				do
474 				{
475 					back(1, position(TOP), TRUE, FALSE, FALSE, stop_on_form_feed);
476 					pos = position(BOTTOM_PLUS_ONE);
477 				} while (pos == NULL_POSITION && !ABORT_SIGS());
478 			}
479 		} else
480 		{
481 			eof_bell();
482 			return;
483 		}
484 	}
485 	forw(n, pos, force, only_last, to_newline, stop_on_form_feed, 0);
486 }
487 
488 /*
489  * Display n more lines, backward.
490  * Start just before the line currently displayed at the top of the screen.
491  */
backward(int n,lbool force,lbool only_last,lbool to_newline)492 public void backward(int n, lbool force, lbool only_last, lbool to_newline)
493 {
494 	POSITION pos;
495 
496 	pos = position(TOP);
497 	if (pos == NULL_POSITION && (!force || position(BOTTOM) == 0))
498 	{
499 		eof_bell();
500 		return;
501 	}
502 	back(n, pos, force, only_last, to_newline, stop_on_form_feed);
503 }
504 
505 /*
506  * Get the backwards scroll limit.
507  * Must call this function instead of just using the value of
508  * back_scroll, because the default case depends on sc_height and
509  * top_scroll, as well as back_scroll.
510  */
get_back_scroll(void)511 public int get_back_scroll(void)
512 {
513 	if (no_back_scroll)
514 		return (0);
515 	if (back_scroll >= 0)
516 		return (back_scroll);
517 	if (top_scroll)
518 		return (sc_height - 2);
519 	return (10000); /* infinity */
520 }
521 
522 /*
523  * Will the entire file fit on one screen?
524  */
get_one_screen(void)525 public lbool get_one_screen(void)
526 {
527 	int nlines;
528 	POSITION pos = ch_zero();
529 	lbool ret = FALSE;
530 
531 	/* Disable polling until we know whether we will exit early due to -F. */
532 	getting_one_screen = TRUE;
533 	for (nlines = 0;  nlines + shell_lines <= sc_height;  nlines++)
534 	{
535 		pos = forw_line(pos, NULL, NULL);
536 		if (ABORT_SIGS())
537 			break;
538 		if (pos == NULL_POSITION)
539 		{
540 			ret = TRUE;
541 			break;
542 		}
543 	}
544 	getting_one_screen = FALSE;
545 	return ret;
546 }
547