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