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