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