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