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