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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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