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