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