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