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