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