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