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