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 * High level routines dealing with getting lines of input 12 * from the file being viewed. 13 * 14 * When we speak of "lines" here, we mean PRINTABLE lines; 15 * lines processed with respect to the screen width. 16 * We use the term "raw line" to refer to lines simply 17 * delimited by newlines; not processed with respect to screen width. 18 */ 19 20 #include "less.h" 21 22 extern int squeeze; 23 extern int hshift; 24 extern int quit_if_one_screen; 25 extern int sigs; 26 extern int ignore_eoi; 27 extern int status_col; 28 extern int wordwrap; 29 extern POSITION start_attnpos; 30 extern POSITION end_attnpos; 31 #if HILITE_SEARCH 32 extern int hilite_search; 33 extern int size_linebuf; 34 extern int show_attn; 35 #endif 36 37 /* 38 * Set the status column. 39 * base Position of first char in line. 40 * disp First visible char. 41 * Different than base_pos if line is shifted. 42 * edisp Last visible char. 43 * eol End of line. Normally the newline. 44 * Different than edisp if line is chopped. 45 */ 46 static void init_status_col(POSITION base_pos, POSITION disp_pos, POSITION edisp_pos, POSITION eol_pos) 47 { 48 int hl_before = (chop_line() && disp_pos != NULL_POSITION) ? 49 is_hilited_attr(base_pos, disp_pos, TRUE, NULL) : 0; 50 int hl_after = (chop_line()) ? 51 is_hilited_attr(edisp_pos, eol_pos, TRUE, NULL) : 0; 52 int attr; 53 char ch; 54 55 if (hl_before && hl_after) 56 { 57 attr = hl_after; 58 ch = '='; 59 } else if (hl_before) 60 { 61 attr = hl_before; 62 ch = '<'; 63 } else if (hl_after) 64 { 65 attr = hl_after; 66 ch = '>'; 67 } else 68 { 69 attr = is_hilited_attr(base_pos, eol_pos, TRUE, NULL); 70 ch = '*'; 71 } 72 if (attr) 73 set_status_col(ch, attr); 74 } 75 76 /* 77 * Get the next line. 78 * A "current" position is passed and a "new" position is returned. 79 * The current position is the position of the first character of 80 * a line. The new position is the position of the first character 81 * of the NEXT line. The line obtained is the line starting at curr_pos. 82 */ 83 public POSITION forw_line_seg(POSITION curr_pos, int skipeol, int rscroll, int nochop) 84 { 85 POSITION base_pos; 86 POSITION new_pos; 87 POSITION edisp_pos; 88 int c; 89 int blankline; 90 int endline; 91 int chopped; 92 int backchars; 93 POSITION wrap_pos; 94 int skipped_leading; 95 96 get_forw_line: 97 if (curr_pos == NULL_POSITION) 98 { 99 null_line(); 100 return (NULL_POSITION); 101 } 102 #if HILITE_SEARCH 103 if (hilite_search == OPT_ONPLUS || is_filtering() || status_col) 104 { 105 /* 106 * If we are ignoring EOI (command F), only prepare 107 * one line ahead, to avoid getting stuck waiting for 108 * slow data without displaying the data we already have. 109 * If we're not ignoring EOI, we *could* do the same, but 110 * for efficiency we prepare several lines ahead at once. 111 */ 112 prep_hilite(curr_pos, curr_pos + 3*size_linebuf, 113 ignore_eoi ? 1 : -1); 114 curr_pos = next_unfiltered(curr_pos); 115 } 116 #endif 117 if (ch_seek(curr_pos)) 118 { 119 null_line(); 120 return (NULL_POSITION); 121 } 122 123 /* 124 * Step back to the beginning of the line. 125 */ 126 base_pos = curr_pos; 127 for (;;) 128 { 129 if (ABORT_SIGS()) 130 { 131 null_line(); 132 return (NULL_POSITION); 133 } 134 c = ch_back_get(); 135 if (c == EOI) 136 break; 137 if (c == '\n') 138 { 139 (void) ch_forw_get(); 140 break; 141 } 142 --base_pos; 143 } 144 145 /* 146 * Read forward again to the position we should start at. 147 */ 148 prewind(); 149 plinestart(base_pos); 150 (void) ch_seek(base_pos); 151 new_pos = base_pos; 152 while (new_pos < curr_pos) 153 { 154 if (ABORT_SIGS()) 155 { 156 null_line(); 157 return (NULL_POSITION); 158 } 159 c = ch_forw_get(); 160 backchars = pappend(c, new_pos); 161 new_pos++; 162 if (backchars > 0) 163 { 164 pshift_all(); 165 if (wordwrap && (c == ' ' || c == '\t')) 166 { 167 do 168 { 169 new_pos++; 170 c = ch_forw_get(); 171 } while (c == ' ' || c == '\t'); 172 backchars = 1; 173 } 174 new_pos -= backchars; 175 while (--backchars >= 0) 176 (void) ch_back_get(); 177 } 178 } 179 (void) pflushmbc(); 180 pshift_all(); 181 182 /* 183 * Read the first character to display. 184 */ 185 c = ch_forw_get(); 186 if (c == EOI) 187 { 188 null_line(); 189 return (NULL_POSITION); 190 } 191 blankline = (c == '\n' || c == '\r'); 192 wrap_pos = NULL_POSITION; 193 skipped_leading = FALSE; 194 195 /* 196 * Read each character in the line and append to the line buffer. 197 */ 198 chopped = FALSE; 199 for (;;) 200 { 201 if (ABORT_SIGS()) 202 { 203 null_line(); 204 return (NULL_POSITION); 205 } 206 if (c == '\n' || c == EOI) 207 { 208 /* 209 * End of the line. 210 */ 211 backchars = pflushmbc(); 212 new_pos = ch_tell(); 213 if (backchars > 0 && (nochop || !chop_line()) && hshift == 0) 214 { 215 new_pos -= backchars + 1; 216 endline = FALSE; 217 } else 218 endline = TRUE; 219 edisp_pos = new_pos; 220 break; 221 } 222 if (c != '\r') 223 blankline = 0; 224 225 /* 226 * Append the char to the line and get the next char. 227 */ 228 backchars = pappend(c, ch_tell()-1); 229 if (backchars > 0) 230 { 231 /* 232 * The char won't fit in the line; the line 233 * is too long to print in the screen width. 234 * End the line here. 235 */ 236 if (skipeol) 237 { 238 /* Read to end of line. */ 239 edisp_pos = ch_tell(); 240 do 241 { 242 if (ABORT_SIGS()) 243 { 244 null_line(); 245 return (NULL_POSITION); 246 } 247 c = ch_forw_get(); 248 } while (c != '\n' && c != EOI); 249 new_pos = ch_tell(); 250 endline = TRUE; 251 quit_if_one_screen = FALSE; 252 chopped = TRUE; 253 } else 254 { 255 if (!wordwrap) 256 new_pos = ch_tell() - backchars; 257 else 258 { 259 /* 260 * We're word-wrapping, so go back to the last space. 261 * However, if it's the space itself that couldn't fit, 262 * simply ignore it and any subsequent spaces. 263 */ 264 if (c == ' ' || c == '\t') 265 { 266 do 267 { 268 new_pos = ch_tell(); 269 c = ch_forw_get(); 270 } while (c == ' ' || c == '\t'); 271 if (c == '\r') 272 c = ch_forw_get(); 273 if (c == '\n') 274 new_pos = ch_tell(); 275 } else if (wrap_pos == NULL_POSITION) 276 new_pos = ch_tell() - backchars; 277 else 278 { 279 new_pos = wrap_pos; 280 loadc(); 281 } 282 } 283 endline = FALSE; 284 } 285 break; 286 } 287 if (wordwrap) 288 { 289 if (c == ' ' || c == '\t') 290 { 291 if (skipped_leading) 292 { 293 wrap_pos = ch_tell(); 294 savec(); 295 } 296 } else 297 skipped_leading = TRUE; 298 } 299 c = ch_forw_get(); 300 } 301 302 #if HILITE_SEARCH 303 if (blankline && show_attn) 304 { 305 /* Add spurious space to carry possible attn hilite. */ 306 pappend(' ', ch_tell()-1); 307 } 308 #endif 309 pdone(endline, rscroll && chopped, 1); 310 311 #if HILITE_SEARCH 312 if (is_filtered(base_pos)) 313 { 314 /* 315 * We don't want to display this line. 316 * Get the next line. 317 */ 318 curr_pos = new_pos; 319 goto get_forw_line; 320 } 321 if (status_col) 322 init_status_col(base_pos, line_position(), edisp_pos, new_pos); 323 #endif 324 325 if (squeeze && blankline) 326 { 327 /* 328 * This line is blank. 329 * Skip down to the last contiguous blank line 330 * and pretend it is the one which we are returning. 331 */ 332 while ((c = ch_forw_get()) == '\n' || c == '\r') 333 if (ABORT_SIGS()) 334 { 335 null_line(); 336 return (NULL_POSITION); 337 } 338 if (c != EOI) 339 (void) ch_back_get(); 340 new_pos = ch_tell(); 341 } 342 343 return (new_pos); 344 } 345 346 public POSITION forw_line(POSITION curr_pos) 347 { 348 349 return forw_line_seg(curr_pos, (chop_line() || hshift > 0), TRUE, FALSE); 350 } 351 352 /* 353 * Get the previous line. 354 * A "current" position is passed and a "new" position is returned. 355 * The current position is the position of the first character of 356 * a line. The new position is the position of the first character 357 * of the PREVIOUS line. The line obtained is the one starting at new_pos. 358 */ 359 public POSITION back_line(POSITION curr_pos) 360 { 361 POSITION base_pos; 362 POSITION new_pos; 363 POSITION edisp_pos; 364 POSITION begin_new_pos; 365 int c; 366 int endline; 367 int chopped; 368 int backchars; 369 POSITION wrap_pos; 370 int skipped_leading; 371 372 get_back_line: 373 if (curr_pos == NULL_POSITION || curr_pos <= ch_zero()) 374 { 375 null_line(); 376 return (NULL_POSITION); 377 } 378 #if HILITE_SEARCH 379 if (hilite_search == OPT_ONPLUS || is_filtering() || status_col) 380 prep_hilite((curr_pos < 3*size_linebuf) ? 381 0 : curr_pos - 3*size_linebuf, curr_pos, -1); 382 #endif 383 if (ch_seek(curr_pos-1)) 384 { 385 null_line(); 386 return (NULL_POSITION); 387 } 388 389 if (squeeze) 390 { 391 /* 392 * Find out if the "current" line was blank. 393 */ 394 (void) ch_forw_get(); /* Skip the newline */ 395 c = ch_forw_get(); /* First char of "current" line */ 396 (void) ch_back_get(); /* Restore our position */ 397 (void) ch_back_get(); 398 399 if (c == '\n' || c == '\r') 400 { 401 /* 402 * The "current" line was blank. 403 * Skip over any preceding blank lines, 404 * since we skipped them in forw_line(). 405 */ 406 while ((c = ch_back_get()) == '\n' || c == '\r') 407 if (ABORT_SIGS()) 408 { 409 null_line(); 410 return (NULL_POSITION); 411 } 412 if (c == EOI) 413 { 414 null_line(); 415 return (NULL_POSITION); 416 } 417 (void) ch_forw_get(); 418 } 419 } 420 421 /* 422 * Scan backwards until we hit the beginning of the line. 423 */ 424 for (;;) 425 { 426 if (ABORT_SIGS()) 427 { 428 null_line(); 429 return (NULL_POSITION); 430 } 431 c = ch_back_get(); 432 if (c == '\n') 433 { 434 /* 435 * This is the newline ending the previous line. 436 * We have hit the beginning of the line. 437 */ 438 base_pos = ch_tell() + 1; 439 break; 440 } 441 if (c == EOI) 442 { 443 /* 444 * We have hit the beginning of the file. 445 * This must be the first line in the file. 446 * This must, of course, be the beginning of the line. 447 */ 448 base_pos = ch_tell(); 449 break; 450 } 451 } 452 453 /* 454 * Now scan forwards from the beginning of this line. 455 * We keep discarding "printable lines" (based on screen width) 456 * until we reach the curr_pos. 457 * 458 * {{ This algorithm is pretty inefficient if the lines 459 * are much longer than the screen width, 460 * but I don't know of any better way. }} 461 */ 462 new_pos = base_pos; 463 if (ch_seek(new_pos)) 464 { 465 null_line(); 466 return (NULL_POSITION); 467 } 468 endline = FALSE; 469 prewind(); 470 plinestart(new_pos); 471 loop: 472 wrap_pos = NULL_POSITION; 473 skipped_leading = FALSE; 474 begin_new_pos = new_pos; 475 (void) ch_seek(new_pos); 476 chopped = FALSE; 477 478 for (;;) 479 { 480 c = ch_forw_get(); 481 if (c == EOI || ABORT_SIGS()) 482 { 483 null_line(); 484 return (NULL_POSITION); 485 } 486 new_pos++; 487 if (c == '\n') 488 { 489 backchars = pflushmbc(); 490 if (backchars > 0 && !chop_line() && hshift == 0) 491 { 492 backchars++; 493 goto shift; 494 } 495 endline = TRUE; 496 edisp_pos = new_pos; 497 break; 498 } 499 backchars = pappend(c, ch_tell()-1); 500 if (backchars > 0) 501 { 502 /* 503 * Got a full printable line, but we haven't 504 * reached our curr_pos yet. Discard the line 505 * and start a new one. 506 */ 507 if (chop_line() || hshift > 0) 508 { 509 endline = TRUE; 510 chopped = TRUE; 511 quit_if_one_screen = FALSE; 512 edisp_pos = new_pos; 513 break; 514 } 515 shift: 516 if (!wordwrap) 517 { 518 pshift_all(); 519 new_pos -= backchars; 520 } else 521 { 522 if (c == ' ' || c == '\t') 523 { 524 for (;;) 525 { 526 c = ch_forw_get(); 527 if (c == ' ' || c == '\t') 528 new_pos++; 529 else 530 { 531 if (c == '\r') 532 { 533 c = ch_forw_get(); 534 if (c == '\n') 535 new_pos++; 536 } 537 if (c == '\n') 538 new_pos++; 539 break; 540 } 541 } 542 if (new_pos >= curr_pos) 543 break; 544 pshift_all(); 545 } else 546 { 547 pshift_all(); 548 if (wrap_pos == NULL_POSITION) 549 new_pos -= backchars; 550 else 551 new_pos = wrap_pos; 552 } 553 } 554 goto loop; 555 } 556 if (wordwrap) 557 { 558 if (c == ' ' || c == '\t') 559 { 560 if (skipped_leading) 561 wrap_pos = new_pos; 562 } else 563 skipped_leading = TRUE; 564 } 565 if (new_pos >= curr_pos) 566 { 567 edisp_pos = new_pos; 568 break; 569 } 570 } 571 572 pdone(endline, chopped, 0); 573 574 #if HILITE_SEARCH 575 if (is_filtered(base_pos)) 576 { 577 /* 578 * We don't want to display this line. 579 * Get the previous line. 580 */ 581 curr_pos = begin_new_pos; 582 goto get_back_line; 583 } 584 if (status_col) 585 init_status_col(base_pos, line_position(), edisp_pos, new_pos); 586 #endif 587 588 return (begin_new_pos); 589 } 590 591 /* 592 * Set attnpos. 593 */ 594 public void set_attnpos(POSITION pos) 595 { 596 int c; 597 598 if (pos != NULL_POSITION) 599 { 600 if (ch_seek(pos)) 601 return; 602 for (;;) 603 { 604 c = ch_forw_get(); 605 if (c == EOI) 606 break; 607 if (c == '\n' || c == '\r') 608 { 609 (void) ch_back_get(); 610 break; 611 } 612 pos++; 613 } 614 end_attnpos = pos; 615 for (;;) 616 { 617 c = ch_back_get(); 618 if (c == EOI || c == '\n' || c == '\r') 619 break; 620 pos--; 621 } 622 } 623 start_attnpos = pos; 624 } 625