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 * Routines to manipulate the "line buffer". 12 * The line buffer holds a line of output as it is being built 13 * in preparation for output to the screen. 14 */ 15 16 #include "less.h" 17 #include "charset.h" 18 #include "position.h" 19 20 #if MSDOS_COMPILER==WIN32C 21 #define WIN32_LEAN_AND_MEAN 22 #include <windows.h> 23 #endif 24 25 #define MAX_PFX_WIDTH (MAX_LINENUM_WIDTH + MAX_STATUSCOL_WIDTH + 1) 26 static struct { 27 char *buf; /* Buffer which holds the current output line */ 28 int *attr; /* Parallel to buf, to hold attributes */ 29 size_t print; /* Index in buf of first printable char */ 30 size_t end; /* Number of chars in buf */ 31 char pfx[MAX_PFX_WIDTH]; /* Holds status column and line number */ 32 int pfx_attr[MAX_PFX_WIDTH]; 33 size_t pfx_end; /* Number of chars in pfx */ 34 } linebuf; 35 36 /* 37 * Buffer of ansi sequences which have been shifted off the left edge 38 * of the screen. 39 */ 40 static struct xbuffer shifted_ansi; 41 42 /* 43 * Ring buffer of last ansi sequences sent. 44 * While sending a line, these will be resent at the end 45 * of any highlighted string, to restore text modes. 46 * {{ Not ideal, since we don't really know how many to resend. }} 47 */ 48 #define NUM_LAST_ANSIS 3 49 static struct xbuffer last_ansi; 50 static struct xbuffer last_ansis[NUM_LAST_ANSIS]; 51 static int curr_last_ansi; 52 53 public size_t size_linebuf = 0; /* Size of line buffer (and attr buffer) */ 54 static struct ansi_state *line_ansi = NULL; 55 static lbool ansi_in_line; 56 static int hlink_in_line; 57 static int line_mark_attr; 58 static int cshift; /* Current left-shift of output line buffer */ 59 public int hshift; /* Desired left-shift of output line buffer */ 60 public int tabstops[TABSTOP_MAX] = { 0 }; /* Custom tabstops */ 61 public int ntabstops = 1; /* Number of tabstops */ 62 public int tabdefault = 8; /* Default repeated tabstops */ 63 public POSITION highest_hilite; /* Pos of last hilite in file found so far */ 64 static POSITION line_pos; 65 66 static int end_column; /* Printable length, accounting for backspaces, etc. */ 67 static int right_curr; 68 static int right_column; 69 static int overstrike; /* Next char should overstrike previous char */ 70 static int last_overstrike = AT_NORMAL; 71 static lbool is_null_line; /* There is no current line */ 72 static LWCHAR pendc; 73 static POSITION pendpos; 74 static constant char *end_ansi_chars; 75 static constant char *mid_ansi_chars; 76 static int in_hilite; 77 78 static int attr_swidth(int a); 79 static int attr_ewidth(int a); 80 static int do_append(LWCHAR ch, constant char *rep, POSITION pos); 81 82 extern int sigs; 83 extern int bs_mode; 84 extern int proc_backspace; 85 extern int proc_tab; 86 extern int proc_return; 87 extern int linenums; 88 extern int ctldisp; 89 extern int twiddle; 90 extern int status_col; 91 extern int status_col_width; 92 extern int linenum_width; 93 extern int auto_wrap, ignaw; 94 extern int bo_s_width, bo_e_width; 95 extern int ul_s_width, ul_e_width; 96 extern int bl_s_width, bl_e_width; 97 extern int so_s_width, so_e_width; 98 extern int sc_width, sc_height; 99 extern int utf_mode; 100 extern POSITION start_attnpos; 101 extern POSITION end_attnpos; 102 extern LWCHAR rscroll_char; 103 extern int rscroll_attr; 104 extern int use_color; 105 extern int status_line; 106 107 static char mbc_buf[MAX_UTF_CHAR_LEN]; 108 static int mbc_buf_len = 0; 109 static int mbc_buf_index = 0; 110 static POSITION mbc_pos; 111 static size_t saved_line_end; 112 static int saved_end_column; 113 114 /* Configurable color map */ 115 struct color_map { int attr; char color[12]; }; 116 static struct color_map color_map[] = { 117 { AT_UNDERLINE, "" }, 118 { AT_BOLD, "" }, 119 { AT_BLINK, "" }, 120 { AT_STANDOUT, "" }, 121 { AT_COLOR_ATTN, "Wm" }, 122 { AT_COLOR_BIN, "kR" }, 123 { AT_COLOR_CTRL, "kR" }, 124 { AT_COLOR_ERROR, "kY" }, 125 { AT_COLOR_LINENUM, "c" }, 126 { AT_COLOR_MARK, "Wb" }, 127 { AT_COLOR_PROMPT, "kC" }, 128 { AT_COLOR_RSCROLL, "kc" }, 129 { AT_COLOR_HEADER, "" }, 130 { AT_COLOR_SEARCH, "kG" }, 131 { AT_COLOR_SUBSEARCH(1), "ky" }, 132 { AT_COLOR_SUBSEARCH(2), "wb" }, 133 { AT_COLOR_SUBSEARCH(3), "YM" }, 134 { AT_COLOR_SUBSEARCH(4), "Yr" }, 135 { AT_COLOR_SUBSEARCH(5), "Wc" }, 136 }; 137 138 /* State while processing an ANSI escape sequence */ 139 struct ansi_state { 140 int oindex; /* Index into OSC8 prefix */ 141 osc8_state ostate; /* State while processing OSC8 sequence */ 142 }; 143 144 /* 145 * Initialize from environment variables. 146 */ 147 public void init_line(void) 148 { 149 int ax; 150 151 end_ansi_chars = lgetenv("LESSANSIENDCHARS"); 152 if (isnullenv(end_ansi_chars)) 153 end_ansi_chars = "m"; 154 155 mid_ansi_chars = lgetenv("LESSANSIMIDCHARS"); 156 if (isnullenv(mid_ansi_chars)) 157 mid_ansi_chars = "0123456789:;[?!\"'#%()*+ "; 158 159 linebuf.buf = (char *) ecalloc(LINEBUF_SIZE, sizeof(char)); 160 linebuf.attr = (int *) ecalloc(LINEBUF_SIZE, sizeof(int)); 161 size_linebuf = LINEBUF_SIZE; 162 xbuf_init(&shifted_ansi); 163 xbuf_init(&last_ansi); 164 for (ax = 0; ax < NUM_LAST_ANSIS; ax++) 165 xbuf_init(&last_ansis[ax]); 166 curr_last_ansi = 0; 167 } 168 169 /* 170 * Expand the line buffer. 171 */ 172 static int expand_linebuf(void) 173 { 174 /* Double the size of the line buffer. */ 175 size_t new_size = size_linebuf * 2; 176 char *new_buf = (char *) calloc(new_size, sizeof(char)); 177 int *new_attr = (int *) calloc(new_size, sizeof(int)); 178 if (new_buf == NULL || new_attr == NULL) 179 { 180 if (new_attr != NULL) 181 free(new_attr); 182 if (new_buf != NULL) 183 free(new_buf); 184 return 1; 185 } 186 /* 187 * We just calloc'd the buffers; copy the old contents. 188 */ 189 memcpy(new_buf, linebuf.buf, size_linebuf * sizeof(char)); 190 memcpy(new_attr, linebuf.attr, size_linebuf * sizeof(int)); 191 free(linebuf.attr); 192 free(linebuf.buf); 193 linebuf.buf = new_buf; 194 linebuf.attr = new_attr; 195 size_linebuf = new_size; 196 return 0; 197 } 198 199 /* 200 * Is a character ASCII? 201 */ 202 public lbool is_ascii_char(LWCHAR ch) 203 { 204 return (ch <= 0x7F); 205 } 206 207 /* 208 */ 209 static void inc_end_column(int w) 210 { 211 if (end_column > right_column && w > 0) 212 { 213 right_column = end_column; 214 right_curr = (int) linebuf.end; 215 } 216 end_column += w; 217 } 218 219 public POSITION line_position(void) 220 { 221 return line_pos; 222 } 223 224 /* 225 * Rewind the line buffer. 226 */ 227 public void prewind(void) 228 { 229 int ax; 230 231 linebuf.print = 6; /* big enough for longest UTF-8 sequence */ 232 linebuf.pfx_end = 0; 233 for (linebuf.end = 0; linebuf.end < linebuf.print; linebuf.end++) 234 { 235 linebuf.buf[linebuf.end] = '\0'; 236 linebuf.attr[linebuf.end] = 0; 237 } 238 239 end_column = 0; 240 right_curr = 0; 241 right_column = 0; 242 cshift = 0; 243 overstrike = 0; 244 last_overstrike = AT_NORMAL; 245 mbc_buf_len = 0; 246 is_null_line = FALSE; 247 pendc = '\0'; 248 in_hilite = 0; 249 ansi_in_line = FALSE; 250 hlink_in_line = 0; 251 line_mark_attr = 0; 252 line_pos = NULL_POSITION; 253 xbuf_reset(&shifted_ansi); 254 xbuf_reset(&last_ansi); 255 for (ax = 0; ax < NUM_LAST_ANSIS; ax++) 256 xbuf_reset(&last_ansis[ax]); 257 curr_last_ansi = 0; 258 } 259 260 /* 261 * Set a character in the line buffer. 262 */ 263 static void set_linebuf(size_t n, char ch, int attr) 264 { 265 if (n >= size_linebuf) 266 { 267 /* 268 * Won't fit in line buffer. 269 * Try to expand it. 270 */ 271 if (expand_linebuf()) 272 return; 273 } 274 linebuf.buf[n] = ch; 275 linebuf.attr[n] = attr; 276 } 277 278 /* 279 * Append a character to the line buffer. 280 */ 281 static void add_linebuf(char ch, int attr, int w) 282 { 283 set_linebuf(linebuf.end++, ch, attr); 284 inc_end_column(w); 285 } 286 287 /* 288 * Append a string to the line buffer. 289 */ 290 static void addstr_linebuf(constant char *s, int attr, int cw) 291 { 292 for ( ; *s != '\0'; s++) 293 add_linebuf(*s, attr, cw); 294 } 295 296 /* 297 * Set a character in the line prefix buffer. 298 */ 299 static void set_pfx(size_t n, char ch, int attr) 300 { 301 linebuf.pfx[n] = ch; 302 linebuf.pfx_attr[n] = attr; 303 } 304 305 /* 306 * Append a character to the line prefix buffer. 307 */ 308 static void add_pfx(char ch, int attr) 309 { 310 set_pfx(linebuf.pfx_end++, ch, attr); 311 } 312 313 /* 314 * Insert the status column and line number into the line buffer. 315 */ 316 public void plinestart(POSITION pos) 317 { 318 LINENUM linenum = 0; 319 320 if (linenums == OPT_ONPLUS) 321 { 322 /* 323 * Get the line number and put it in the current line. 324 * {{ Note: since find_linenum calls forw_raw_line, 325 * it may seek in the input file, requiring the caller 326 * of plinestart to re-seek if necessary. }} 327 * {{ Since forw_raw_line modifies linebuf, we must 328 * do this first, before storing anything in linebuf. }} 329 */ 330 linenum = find_linenum(pos); 331 } 332 333 /* 334 * Display a status column if the -J option is set. 335 */ 336 if (status_col || status_line) 337 { 338 char c = posmark(pos); 339 if (c != 0) 340 line_mark_attr = AT_HILITE|AT_COLOR_MARK; 341 else if (start_attnpos != NULL_POSITION && 342 pos >= start_attnpos && pos <= end_attnpos) 343 line_mark_attr = AT_HILITE|AT_COLOR_ATTN; 344 if (status_col) 345 { 346 add_pfx(c ? c : ' ', line_mark_attr); /* column 0: status */ 347 while (linebuf.pfx_end < (size_t) status_col_width) /*{{type-issue}}*/ 348 add_pfx(' ', AT_NORMAL); 349 } 350 } 351 352 /* 353 * Display the line number at the start of each line 354 * if the -N option is set. 355 */ 356 if (linenums == OPT_ONPLUS) 357 { 358 char buf[INT_STRLEN_BOUND(linenum) + 2]; 359 size_t len; 360 size_t i; 361 362 linenum = vlinenum(linenum); 363 if (linenum == 0) 364 len = 0; 365 else 366 { 367 linenumtoa(linenum, buf, 10); 368 len = strlen(buf); 369 } 370 for (i = 0; i + len < (size_t) linenum_width; i++) 371 add_pfx(' ', AT_NORMAL); 372 for (i = 0; i < len; i++) 373 add_pfx(buf[i], AT_BOLD|AT_COLOR_LINENUM); 374 add_pfx(' ', AT_NORMAL); 375 } 376 end_column = (int) linebuf.pfx_end; /*{{type-issue}}*/ 377 } 378 379 /* 380 * Return the width of the line prefix (status column and line number). 381 * {{ Actual line number can be wider than linenum_width. }} 382 */ 383 public int line_pfx_width(void) 384 { 385 int width = 0; 386 if (status_col) 387 width += status_col_width; 388 if (linenums == OPT_ONPLUS) 389 width += linenum_width + 1; 390 return width; 391 } 392 393 /* 394 * Shift line left so that the last char is just to the left 395 * of the first visible column. 396 */ 397 public void pshift_all(void) 398 { 399 size_t i; 400 for (i = linebuf.print; i < linebuf.end; i++) 401 if (linebuf.attr[i] == AT_ANSI) 402 xbuf_add_char(&shifted_ansi, linebuf.buf[i]); 403 linebuf.end = linebuf.print; 404 end_column = (int) linebuf.pfx_end; /*{{type-issue}}*/ 405 line_pos = NULL_POSITION; 406 } 407 408 /* 409 * Return the printing width of the start (enter) sequence 410 * for a given character attribute. 411 */ 412 static int attr_swidth(int a) 413 { 414 int w = 0; 415 416 a = apply_at_specials(a); 417 418 if (a & AT_UNDERLINE) 419 w += ul_s_width; 420 if (a & AT_BOLD) 421 w += bo_s_width; 422 if (a & AT_BLINK) 423 w += bl_s_width; 424 if (a & AT_STANDOUT) 425 w += so_s_width; 426 427 return w; 428 } 429 430 /* 431 * Return the printing width of the end (exit) sequence 432 * for a given character attribute. 433 */ 434 static int attr_ewidth(int a) 435 { 436 int w = 0; 437 438 a = apply_at_specials(a); 439 440 if (a & AT_UNDERLINE) 441 w += ul_e_width; 442 if (a & AT_BOLD) 443 w += bo_e_width; 444 if (a & AT_BLINK) 445 w += bl_e_width; 446 if (a & AT_STANDOUT) 447 w += so_e_width; 448 449 return w; 450 } 451 452 /* 453 * Return the printing width of a given character and attribute, 454 * if the character were added after prev_ch. 455 * Adding a character with a given attribute may cause an enter or exit 456 * attribute sequence to be inserted, so this must be taken into account. 457 */ 458 public int pwidth(LWCHAR ch, int a, LWCHAR prev_ch, int prev_a) 459 { 460 int w; 461 462 if (ch == '\b') 463 { 464 /* 465 * Backspace moves backwards one or two positions. 466 */ 467 if (prev_a & (AT_ANSI|AT_BINARY)) 468 return (int) strlen(prchar('\b')); /*{{type-issue}}*/ 469 return (utf_mode && is_wide_char(prev_ch)) ? -2 : -1; 470 } 471 472 if (!utf_mode || is_ascii_char(ch)) 473 { 474 if (control_char(ch)) 475 { 476 /* 477 * Control characters do unpredictable things, 478 * so we don't even try to guess; say it doesn't move. 479 * This can only happen if the -r flag is in effect. 480 */ 481 return (0); 482 } 483 } else 484 { 485 if (is_composing_char(ch) || is_combining_char(prev_ch, ch)) 486 { 487 /* 488 * Composing and combining chars take up no space. 489 * 490 * Some terminals, upon failure to compose a 491 * composing character with the character(s) that 492 * precede(s) it will actually take up one end_column 493 * for the composing character; there isn't much 494 * we could do short of testing the (complex) 495 * composition process ourselves and printing 496 * a binary representation when it fails. 497 */ 498 return (0); 499 } 500 } 501 502 /* 503 * Other characters take one or two columns, 504 * plus the width of any attribute enter/exit sequence. 505 */ 506 w = 1; 507 if (is_wide_char(ch)) 508 w++; 509 if (linebuf.end > 0 && !is_at_equiv(linebuf.attr[linebuf.end-1], a)) 510 w += attr_ewidth(linebuf.attr[linebuf.end-1]); 511 if (apply_at_specials(a) != AT_NORMAL && 512 (linebuf.end == 0 || !is_at_equiv(linebuf.attr[linebuf.end-1], a))) 513 w += attr_swidth(a); 514 return (w); 515 } 516 517 /* 518 * Delete to the previous base character in the line buffer. 519 */ 520 static int backc(void) 521 { 522 LWCHAR ch; 523 char *p; 524 525 if (linebuf.end == 0) 526 return (0); 527 p = &linebuf.buf[linebuf.end]; 528 ch = step_char(&p, -1, linebuf.buf); 529 /* Skip back to the next nonzero-width char. */ 530 while (p > linebuf.buf) 531 { 532 LWCHAR prev_ch; 533 int width; 534 linebuf.end = ptr_diff(p, linebuf.buf); 535 prev_ch = step_char(&p, -1, linebuf.buf); 536 width = pwidth(ch, linebuf.attr[linebuf.end], prev_ch, linebuf.attr[linebuf.end-1]); 537 end_column -= width; 538 /* {{ right_column? }} */ 539 if (width > 0) 540 break; 541 ch = prev_ch; 542 } 543 return (1); 544 } 545 546 /* 547 * Preserve the current position in the line buffer (for word wrapping). 548 */ 549 public void savec(void) 550 { 551 saved_line_end = linebuf.end; 552 saved_end_column = end_column; 553 } 554 555 /* 556 * Restore the position in the line buffer (start of line for word wrapping). 557 */ 558 public void loadc(void) 559 { 560 linebuf.end = saved_line_end; 561 end_column = saved_end_column; 562 } 563 564 /* 565 * Is a character the end of an ANSI escape sequence? 566 */ 567 public lbool is_ansi_end(LWCHAR ch) 568 { 569 if (!is_ascii_char(ch)) 570 return (FALSE); 571 return (strchr(end_ansi_chars, (char) ch) != NULL); 572 } 573 574 /* 575 * Can a char appear in an ANSI escape sequence, before the end char? 576 */ 577 public lbool is_ansi_middle(LWCHAR ch) 578 { 579 if (!is_ascii_char(ch)) 580 return (FALSE); 581 if (is_ansi_end(ch)) 582 return (FALSE); 583 return (strchr(mid_ansi_chars, (char) ch) != NULL); 584 } 585 586 /* 587 * Skip past an ANSI escape sequence. 588 * pp is initially positioned just after the CSI_START char. 589 */ 590 public void skip_ansi(struct ansi_state *pansi, constant char **pp, constant char *limit) 591 { 592 LWCHAR c; 593 do { 594 c = step_charc(pp, +1, limit); 595 } while (*pp < limit && ansi_step(pansi, c) == ANSI_MID); 596 /* Note that we discard final char, for which is_ansi_end is true. */ 597 } 598 599 /* 600 * Determine if a character starts an ANSI escape sequence. 601 * If so, return an ansi_state struct; otherwise return NULL. 602 */ 603 public struct ansi_state * ansi_start(LWCHAR ch) 604 { 605 struct ansi_state *pansi; 606 607 if (!IS_CSI_START(ch)) 608 return NULL; 609 pansi = ecalloc(1, sizeof(struct ansi_state)); 610 pansi->oindex = 0; 611 pansi->ostate = OSC8_PREFIX; 612 return pansi; 613 } 614 615 /* 616 * Determine whether the next char in an ANSI escape sequence 617 * ends the sequence. 618 */ 619 public ansi_state ansi_step(struct ansi_state *pansi, LWCHAR ch) 620 { 621 static constant char osc8_prefix[] = ESCS "]8;"; 622 623 switch (pansi->ostate) 624 { 625 case OSC8_PREFIX: 626 if (ch != (LWCHAR) osc8_prefix[pansi->oindex] && 627 !(pansi->oindex == 0 && IS_CSI_START(ch))) 628 { 629 pansi->ostate = OSC8_NOT; /* not an OSC8 sequence */ 630 break; 631 } 632 pansi->oindex++; 633 if (osc8_prefix[pansi->oindex] == '\0') /* end of prefix */ 634 pansi->ostate = OSC8_PARAMS; 635 return ANSI_MID; 636 case OSC8_PARAMS: 637 if (ch == ';') 638 pansi->ostate = OSC8_URI; 639 return ANSI_MID; 640 case OSC8_URI: 641 /* URI ends with \7 or ESC-backslash. */ 642 if (ch == '\7') 643 { 644 pansi->ostate = OSC8_END; 645 return ANSI_END; 646 } 647 if (ch == ESC) 648 pansi->ostate = OSC8_ST_ESC; 649 return ANSI_MID; 650 case OSC8_ST_ESC: 651 if (ch != '\\') 652 { 653 return ANSI_ERR; 654 } 655 pansi->ostate = OSC8_END; 656 return ANSI_END; 657 case OSC8_END: 658 return ANSI_END; 659 case OSC8_NOT: 660 break; 661 } 662 /* Check for SGR sequences */ 663 if (is_ansi_middle(ch)) 664 return ANSI_MID; 665 if (is_ansi_end(ch)) 666 return ANSI_END; 667 return ANSI_ERR; 668 } 669 670 /* 671 * Return the current OSC8 parsing state. 672 */ 673 public osc8_state ansi_osc8_state(struct ansi_state *pansi) 674 { 675 return pansi->ostate; 676 } 677 678 /* 679 * Free an ansi_state structure. 680 */ 681 public void ansi_done(struct ansi_state *pansi) 682 { 683 free(pansi); 684 } 685 686 /* 687 * Will w characters in attribute a fit on the screen? 688 */ 689 static int fits_on_screen(int w, int a) 690 { 691 if (ctldisp == OPT_ON) 692 /* We're not counting, so say that everything fits. */ 693 return 1; 694 return (end_column - cshift + w + attr_ewidth(a) <= sc_width); 695 } 696 697 /* 698 * Append a character and attribute to the line buffer. 699 */ 700 #define STORE_CHAR(ch,a,rep,pos) \ 701 do { \ 702 if (store_char((ch),(a),(rep),(pos))) return (1); \ 703 } while (0) 704 705 static int store_char(LWCHAR ch, int a, constant char *rep, POSITION pos) 706 { 707 int w; 708 size_t i; 709 size_t replen; 710 char cs; 711 int ov; 712 713 ov = (a & (AT_UNDERLINE|AT_BOLD)); 714 if (ov != AT_NORMAL) 715 last_overstrike = ov; 716 717 #if HILITE_SEARCH 718 { 719 int matches; 720 int resend_last = 0; 721 int hl_attr = 0; 722 723 if (pos == NULL_POSITION) 724 { 725 /* Color the prompt unless it has ansi sequences in it. */ 726 hl_attr = ansi_in_line ? 0 : AT_STANDOUT|AT_COLOR_PROMPT; 727 } else if (a != AT_ANSI) 728 { 729 hl_attr = is_hilited_attr(pos, pos+1, 0, &matches); 730 if (hl_attr == 0 && status_line) 731 hl_attr = line_mark_attr; 732 } 733 if (hl_attr) 734 { 735 /* 736 * This character should be highlighted. 737 * Override the attribute passed in. 738 */ 739 a |= hl_attr; 740 if (highest_hilite != NULL_POSITION && pos != NULL_POSITION && pos > highest_hilite) 741 highest_hilite = pos; 742 in_hilite = 1; 743 } else 744 { 745 if (in_hilite) 746 { 747 /* 748 * This is the first non-hilited char after a hilite. 749 * Resend the last ANSI seq to restore color. 750 */ 751 resend_last = 1; 752 } 753 in_hilite = 0; 754 } 755 if (resend_last) 756 { 757 int ai; 758 for (ai = 0; ai < NUM_LAST_ANSIS; ai++) 759 { 760 int ax = (curr_last_ansi + ai) % NUM_LAST_ANSIS; 761 for (i = 0; i < last_ansis[ax].end; i++) 762 STORE_CHAR(last_ansis[ax].data[i], AT_ANSI, NULL, pos); 763 } 764 } 765 } 766 #endif 767 768 if (a == AT_ANSI) { 769 w = 0; 770 } else { 771 char *p = &linebuf.buf[linebuf.end]; 772 LWCHAR prev_ch = (linebuf.end > 0) ? step_char(&p, -1, linebuf.buf) : 0; 773 int prev_a = (linebuf.end > 0) ? linebuf.attr[linebuf.end-1] : 0; 774 w = pwidth(ch, a, prev_ch, prev_a); 775 } 776 777 if (!fits_on_screen(w, a)) 778 return (1); 779 780 if (rep == NULL) 781 { 782 cs = (char) ch; 783 rep = &cs; 784 replen = 1; 785 } else 786 { 787 replen = (size_t) utf_len(rep[0]); /*{{type-issue}}*/ 788 } 789 790 if (cshift == hshift) 791 { 792 if (line_pos == NULL_POSITION) 793 line_pos = pos; 794 if (shifted_ansi.end > 0) 795 { 796 /* Copy shifted ANSI sequences to beginning of line. */ 797 for (i = 0; i < shifted_ansi.end; i++) 798 add_linebuf((char) shifted_ansi.data[i], AT_ANSI, 0); 799 xbuf_reset(&shifted_ansi); 800 } 801 } 802 803 /* Add the char to the buf, even if we will left-shift it next. */ 804 inc_end_column(w); 805 for (i = 0; i < replen; i++) 806 add_linebuf(*rep++, a, 0); 807 808 if (cshift < hshift) 809 { 810 /* We haven't left-shifted enough yet. */ 811 if (a == AT_ANSI) 812 xbuf_add_char(&shifted_ansi, (char) ch); /* Save ANSI attributes */ 813 if (linebuf.end > linebuf.print) 814 { 815 /* Shift left enough to put last byte of this char at print-1. */ 816 size_t i; 817 for (i = 0; i < linebuf.print; i++) 818 { 819 linebuf.buf[i] = linebuf.buf[i+replen]; 820 linebuf.attr[i] = linebuf.attr[i+replen]; 821 } 822 linebuf.end -= replen; 823 cshift += w; 824 /* 825 * If the char we just left-shifted was double width, 826 * the 2 spaces we shifted may be too much. 827 * Represent the "half char" at start of line with a highlighted space. 828 */ 829 while (cshift > hshift) 830 { 831 add_linebuf(' ', rscroll_attr, 0); 832 cshift--; 833 } 834 } 835 } 836 return (0); 837 } 838 839 #define STORE_STRING(s,a,pos) \ 840 do { if (store_string((s),(a),(pos))) return (1); } while (0) 841 842 static int store_string(constant char *s, int a, POSITION pos) 843 { 844 if (!fits_on_screen((int) strlen(s), a)) 845 return 1; 846 for ( ; *s != 0; s++) 847 STORE_CHAR((LWCHAR)*s, a, NULL, pos); 848 return 0; 849 } 850 851 /* 852 * Return number of spaces from col to the next tab stop. 853 */ 854 static int tab_spaces(int col) 855 { 856 int to_tab = col - (int) linebuf.pfx_end; /*{{type-issue}}*/ 857 858 if (ntabstops < 2 || to_tab >= tabstops[ntabstops-1]) 859 to_tab = tabdefault - 860 ((to_tab - tabstops[ntabstops-1]) % tabdefault); 861 else 862 { 863 int i; 864 for (i = ntabstops - 2; i >= 0; i--) 865 if (to_tab >= tabstops[i]) 866 break; 867 to_tab = tabstops[i+1] - to_tab; 868 } 869 return to_tab; 870 } 871 872 /* 873 * Append a tab to the line buffer. 874 * Store spaces to represent the tab. 875 */ 876 #define STORE_TAB(a,pos) \ 877 do { if (store_tab((a),(pos))) return (1); } while (0) 878 879 static int store_tab(int attr, POSITION pos) 880 { 881 int to_tab = tab_spaces(end_column); 882 do { 883 STORE_CHAR(' ', attr, " ", pos); 884 } while (--to_tab > 0); 885 return 0; 886 } 887 888 #define STORE_PRCHAR(c, pos) \ 889 do { if (store_prchar((c), (pos))) return 1; } while (0) 890 891 static int store_prchar(LWCHAR c, POSITION pos) 892 { 893 /* 894 * Convert to printable representation. 895 */ 896 STORE_STRING(prchar(c), AT_BINARY|AT_COLOR_CTRL, pos); 897 return 0; 898 } 899 900 static int flush_mbc_buf(POSITION pos) 901 { 902 int i; 903 904 for (i = 0; i < mbc_buf_index; i++) 905 if (store_prchar((LWCHAR) mbc_buf[i], pos)) 906 return mbc_buf_index - i; 907 return 0; 908 } 909 910 /* 911 * Append a character to the line buffer. 912 * Expand tabs into spaces, handle underlining, boldfacing, etc. 913 * Returns 0 if ok, 1 if couldn't fit in buffer. 914 */ 915 public int pappend_b(char c, POSITION pos, lbool before_pendc) 916 { 917 LWCHAR ch = c & 0377; 918 int r; 919 920 if (pendc && !before_pendc) 921 { 922 if (ch == '\r' && pendc == '\r') 923 return (0); 924 if (do_append(pendc, NULL, pendpos)) 925 /* 926 * Oops. We've probably lost the char which 927 * was in pendc, since caller won't back up. 928 */ 929 return (1); 930 pendc = '\0'; 931 } 932 933 if (ch == '\r' && (proc_return == OPT_ON || (bs_mode == BS_SPECIAL && proc_return == OPT_OFF))) 934 { 935 if (mbc_buf_len > 0) /* utf_mode must be on. */ 936 { 937 /* Flush incomplete (truncated) sequence. */ 938 r = flush_mbc_buf(mbc_pos); 939 mbc_buf_index = r + 1; 940 mbc_buf_len = 0; 941 if (r) 942 return (mbc_buf_index); 943 } 944 945 /* 946 * Don't put the CR into the buffer until we see 947 * the next char. If the next char is a newline, 948 * discard the CR. 949 */ 950 pendc = ch; 951 pendpos = pos; 952 return (0); 953 } 954 955 if (!utf_mode) 956 { 957 r = do_append(ch, NULL, pos); 958 } else 959 { 960 /* Perform strict validation in all possible cases. */ 961 if (mbc_buf_len == 0) 962 { 963 retry: 964 mbc_buf_index = 1; 965 *mbc_buf = c; 966 if (IS_ASCII_OCTET(c)) 967 r = do_append(ch, NULL, pos); 968 else if (IS_UTF8_LEAD(c)) 969 { 970 mbc_buf_len = utf_len(c); 971 mbc_pos = pos; 972 return (0); 973 } else 974 /* UTF8_INVALID or stray UTF8_TRAIL */ 975 r = flush_mbc_buf(pos); 976 } else if (IS_UTF8_TRAIL(c)) 977 { 978 mbc_buf[mbc_buf_index++] = c; 979 if (mbc_buf_index < mbc_buf_len) 980 return (0); 981 if (is_utf8_well_formed(mbc_buf, mbc_buf_index)) 982 r = do_append(get_wchar(mbc_buf), mbc_buf, mbc_pos); 983 else 984 /* Complete, but not shortest form, sequence. */ 985 mbc_buf_index = r = flush_mbc_buf(mbc_pos); 986 mbc_buf_len = 0; 987 } else 988 { 989 /* Flush incomplete (truncated) sequence. */ 990 r = flush_mbc_buf(mbc_pos); 991 mbc_buf_index = r + 1; 992 mbc_buf_len = 0; 993 /* Handle new char. */ 994 if (!r) 995 goto retry; 996 } 997 } 998 if (r) 999 { 1000 /* How many chars should caller back up? */ 1001 r = (!utf_mode) ? 1 : mbc_buf_index; 1002 } 1003 return (r); 1004 } 1005 1006 public int pappend(char c, POSITION pos) 1007 { 1008 return pappend_b(c, pos, FALSE); 1009 } 1010 1011 static int store_control_char(LWCHAR ch, constant char *rep, POSITION pos) 1012 { 1013 if (ctldisp == OPT_ON) 1014 { 1015 /* Output the character itself. */ 1016 STORE_CHAR(ch, AT_NORMAL, rep, pos); 1017 } else 1018 { 1019 /* Output a printable representation of the character. */ 1020 STORE_PRCHAR(ch, pos); 1021 } 1022 return (0); 1023 } 1024 1025 static int store_ansi(LWCHAR ch, constant char *rep, POSITION pos) 1026 { 1027 switch (ansi_step(line_ansi, ch)) 1028 { 1029 case ANSI_MID: 1030 STORE_CHAR(ch, AT_ANSI, rep, pos); 1031 if (ansi_osc8_state(line_ansi) == OSC8_PARAMS) 1032 hlink_in_line = 1; 1033 xbuf_add_char(&last_ansi, (char) ch); 1034 break; 1035 case ANSI_END: 1036 STORE_CHAR(ch, AT_ANSI, rep, pos); 1037 ansi_done(line_ansi); 1038 line_ansi = NULL; 1039 xbuf_add_char(&last_ansi, (char) ch); 1040 xbuf_set(&last_ansis[curr_last_ansi], &last_ansi); 1041 xbuf_reset(&last_ansi); 1042 curr_last_ansi = (curr_last_ansi + 1) % NUM_LAST_ANSIS; 1043 break; 1044 case ANSI_ERR: 1045 { 1046 /* Remove whole unrecognized sequence. */ 1047 constant char *start = (cshift < hshift) ? xbuf_char_data(&shifted_ansi): linebuf.buf; 1048 size_t *end = (cshift < hshift) ? &shifted_ansi.end : &linebuf.end; 1049 constant char *p = start + *end; 1050 LWCHAR bch; 1051 do { 1052 bch = step_charc(&p, -1, start); 1053 } while (p > start && !IS_CSI_START(bch)); 1054 *end = ptr_diff(p, start); 1055 } 1056 xbuf_reset(&last_ansi); 1057 ansi_done(line_ansi); 1058 line_ansi = NULL; 1059 break; 1060 default: 1061 break; 1062 } 1063 return (0); 1064 } 1065 1066 static int store_bs(LWCHAR ch, constant char *rep, POSITION pos) 1067 { 1068 if (proc_backspace == OPT_ONPLUS || (bs_mode == BS_CONTROL && proc_backspace == OPT_OFF)) 1069 return store_control_char(ch, rep, pos); 1070 if (linebuf.end > 0 && 1071 ((linebuf.end <= linebuf.print && linebuf.buf[linebuf.end-1] == '\0') || 1072 (linebuf.end > 0 && linebuf.attr[linebuf.end - 1] & (AT_ANSI|AT_BINARY)))) 1073 STORE_PRCHAR('\b', pos); 1074 else if (proc_backspace == OPT_OFF && bs_mode == BS_NORMAL) 1075 STORE_CHAR(ch, AT_NORMAL, NULL, pos); 1076 else if (proc_backspace == OPT_ON || (bs_mode == BS_SPECIAL && proc_backspace == OPT_OFF)) 1077 overstrike = backc(); 1078 return 0; 1079 } 1080 1081 static int do_append(LWCHAR ch, constant char *rep, POSITION pos) 1082 { 1083 int a = AT_NORMAL; 1084 int in_overstrike = overstrike; 1085 1086 if (ctldisp == OPT_ONPLUS && line_ansi == NULL) 1087 { 1088 line_ansi = ansi_start(ch); 1089 if (line_ansi != NULL) 1090 ansi_in_line = TRUE; 1091 } 1092 1093 overstrike = 0; 1094 if (line_ansi != NULL) 1095 return store_ansi(ch, rep, pos); 1096 1097 if (ch == '\b') 1098 return store_bs(ch, rep, pos); 1099 1100 if (in_overstrike > 0) 1101 { 1102 /* 1103 * Overstrike the character at the current position 1104 * in the line buffer. This will cause either 1105 * underline (if a "_" is overstruck), 1106 * bold (if an identical character is overstruck), 1107 * or just replacing the character in the buffer. 1108 */ 1109 LWCHAR prev_ch; 1110 overstrike = utf_mode ? -1 : 0; 1111 if (utf_mode) 1112 { 1113 /* To be correct, this must be a base character. */ 1114 prev_ch = get_wchar(&linebuf.buf[linebuf.end]); 1115 } else 1116 { 1117 prev_ch = (unsigned char) linebuf.buf[linebuf.end]; 1118 } 1119 a = linebuf.attr[linebuf.end]; 1120 if (ch == prev_ch) 1121 { 1122 /* 1123 * Overstriking a char with itself means make it bold. 1124 * But overstriking an underscore with itself is 1125 * ambiguous. It could mean make it bold, or 1126 * it could mean make it underlined. 1127 * Use the previous overstrike to resolve it. 1128 */ 1129 if (ch == '_') 1130 { 1131 if ((a & (AT_BOLD|AT_UNDERLINE)) != AT_NORMAL) 1132 a |= (AT_BOLD|AT_UNDERLINE); 1133 else if (last_overstrike != AT_NORMAL) 1134 a |= last_overstrike; 1135 else 1136 a |= AT_BOLD; 1137 } else 1138 a |= AT_BOLD; 1139 } else if (ch == '_') 1140 { 1141 a |= AT_UNDERLINE; 1142 ch = prev_ch; 1143 rep = &linebuf.buf[linebuf.end]; 1144 } else if (prev_ch == '_') 1145 { 1146 a |= AT_UNDERLINE; 1147 } 1148 /* Else we replace prev_ch, but we keep its attributes. */ 1149 } else if (in_overstrike < 0) 1150 { 1151 if ( is_composing_char(ch) 1152 || is_combining_char(get_wchar(&linebuf.buf[linebuf.end]), ch)) 1153 /* Continuation of the same overstrike. */ 1154 a = last_overstrike; 1155 else 1156 overstrike = 0; 1157 } 1158 1159 if (ch == '\t') 1160 { 1161 /* 1162 * Expand a tab into spaces. 1163 */ 1164 if (proc_tab == OPT_ONPLUS || (bs_mode == BS_CONTROL && proc_tab == OPT_OFF)) 1165 return store_control_char(ch, rep, pos); 1166 STORE_TAB(a, pos); 1167 return (0); 1168 } 1169 if ((!utf_mode || is_ascii_char(ch)) && control_char(ch)) 1170 { 1171 return store_control_char(ch, rep, pos); 1172 } else if (utf_mode && ctldisp != OPT_ON && is_ubin_char(ch)) 1173 { 1174 STORE_STRING(prutfchar(ch), AT_BINARY, pos); 1175 } else 1176 { 1177 STORE_CHAR(ch, a, rep, pos); 1178 } 1179 return (0); 1180 } 1181 1182 /* 1183 * 1184 */ 1185 public int pflushmbc(void) 1186 { 1187 int r = 0; 1188 1189 if (mbc_buf_len > 0) 1190 { 1191 /* Flush incomplete (truncated) sequence. */ 1192 r = flush_mbc_buf(mbc_pos); 1193 mbc_buf_len = 0; 1194 } 1195 return r; 1196 } 1197 1198 /* 1199 * Switch to normal attribute at end of line. 1200 */ 1201 static void add_attr_normal(void) 1202 { 1203 if (ctldisp != OPT_ONPLUS || !is_ansi_end('m')) 1204 return; 1205 addstr_linebuf("\033[m", AT_ANSI, 0); 1206 if (hlink_in_line) /* Don't send hyperlink clear if we know we don't need to. */ 1207 addstr_linebuf("\033]8;;\033\\", AT_ANSI, 0); 1208 } 1209 1210 /* 1211 * Terminate the line in the line buffer. 1212 */ 1213 public void pdone(int endline, int chopped, int forw) 1214 { 1215 (void) pflushmbc(); 1216 1217 if (pendc && (pendc != '\r' || !endline)) 1218 /* 1219 * If we had a pending character, put it in the buffer. 1220 * But discard a pending CR if we are at end of line 1221 * (that is, discard the CR in a CR/LF sequence). 1222 */ 1223 (void) do_append(pendc, NULL, pendpos); 1224 1225 if (chopped && rscroll_char) 1226 { 1227 char rscroll_utf8[MAX_UTF_CHAR_LEN+1]; 1228 char *up = rscroll_utf8; 1229 1230 /* 1231 * Display the right scrolling char. 1232 * If we've already filled the rightmost screen char 1233 * (in the buffer), overwrite it. 1234 */ 1235 if (end_column >= sc_width + cshift) 1236 { 1237 /* We've already written in the rightmost char. */ 1238 end_column = right_column; 1239 linebuf.end = (size_t) right_curr; 1240 } 1241 add_attr_normal(); 1242 while (end_column < sc_width-1 + cshift) 1243 { 1244 /* 1245 * Space to last (rightmost) char on screen. 1246 * This may be necessary if the char we overwrote 1247 * was double-width. 1248 */ 1249 add_linebuf(' ', 0, 1); 1250 } 1251 /* Print rscroll char. */ 1252 put_wchar(&up, rscroll_char); 1253 *up = '\0'; 1254 addstr_linebuf(rscroll_utf8, rscroll_attr, 0); 1255 inc_end_column(1); /* assume rscroll_char is single-width */ 1256 } else 1257 { 1258 add_attr_normal(); 1259 } 1260 1261 /* 1262 * If we're coloring a status line, fill out the line with spaces. 1263 */ 1264 if (status_line && line_mark_attr != 0) { 1265 while (end_column +1 < sc_width + cshift) 1266 add_linebuf(' ', line_mark_attr, 1); 1267 } 1268 1269 /* 1270 * Add a newline if necessary, 1271 * and append a '\0' to the end of the line. 1272 * We output a newline if we're not at the right edge of the screen, 1273 * or if the terminal doesn't auto wrap, 1274 * or if this is really the end of the line AND the terminal ignores 1275 * a newline at the right edge. 1276 * (In the last case we don't want to output a newline if the terminal 1277 * doesn't ignore it since that would produce an extra blank line. 1278 * But we do want to output a newline if the terminal ignores it in case 1279 * the next line is blank. In that case the single newline output for 1280 * that blank line would be ignored!) 1281 */ 1282 if (end_column < sc_width + cshift || !auto_wrap || (endline && ignaw) || ctldisp == OPT_ON) 1283 { 1284 add_linebuf('\n', AT_NORMAL, 0); 1285 } 1286 else if (ignaw && end_column >= sc_width + cshift && forw) 1287 { 1288 /* 1289 * Terminals with "ignaw" don't wrap until they *really* need 1290 * to, i.e. when the character *after* the last one to fit on a 1291 * line is output. But they are too hard to deal with when they 1292 * get in the state where a full screen width of characters 1293 * have been output but the cursor is sitting on the right edge 1294 * instead of at the start of the next line. 1295 * So we nudge them into wrapping by outputting a space 1296 * character plus a backspace. But do this only if moving 1297 * forward; if we're moving backward and drawing this line at 1298 * the top of the screen, the space would overwrite the first 1299 * char on the next line. We don't need to do this "nudge" 1300 * at the top of the screen anyway. 1301 */ 1302 add_linebuf(' ', AT_NORMAL, 1); 1303 add_linebuf('\b', AT_NORMAL, -1); 1304 } 1305 set_linebuf(linebuf.end, '\0', AT_NORMAL); 1306 } 1307 1308 /* 1309 * Return the column number (screen position) of a given file position in its line. 1310 * linepos = position of first char in line 1311 * spos = position of char being queried 1312 * saved_pos = position of a known column, or NULL_POSITION if no known column 1313 * saved_col = column number of a known column, or -1 if no known column 1314 * 1315 * This attempts to mimic the logic in pappend() and the store_*() functions. 1316 * Duplicating this complicated logic is not a good design. 1317 */ 1318 1319 struct col_pos { int col; POSITION pos; }; 1320 1321 static void col_vs_pos(POSITION linepos, mutable struct col_pos *cp, POSITION saved_pos, int saved_col) 1322 { 1323 int col = (saved_col < 0) ? 0 : saved_col; 1324 LWCHAR prev_ch = 0; 1325 struct ansi_state *pansi = NULL; 1326 char utf8_buf[MAX_UTF_CHAR_LEN]; 1327 int utf8_len = 0; 1328 POSITION chpos; 1329 1330 if (ch_seek(saved_pos != NULL_POSITION ? saved_pos : linepos)) 1331 return; 1332 for (;;) 1333 { 1334 int ich; 1335 char ch; 1336 int cw = 0; 1337 1338 chpos = ch_tell(); 1339 ich = ch_forw_get(); 1340 ch = (char) ich; 1341 if (ich == EOI || ch == '\n') 1342 break; 1343 if (pansi != NULL) 1344 { 1345 if (ansi_step(pansi, ch) != ANSI_MID) 1346 { 1347 ansi_done(pansi); 1348 pansi = NULL; 1349 } 1350 } else if (ctldisp == OPT_ONPLUS && (pansi = ansi_start(ch)) != NULL) 1351 { 1352 /* start of ansi sequence */ 1353 (void) ansi_step(pansi, ch); 1354 } else if (ch == '\b') 1355 { 1356 if (proc_backspace == OPT_ONPLUS || (bs_mode == BS_CONTROL && proc_backspace == OPT_OFF)) 1357 cw = strlen(prchar(ch)); 1358 else 1359 cw = (utf_mode && is_wide_char(prev_ch)) ? -2 : -1; 1360 } else if (ch == '\t') 1361 { 1362 if (proc_tab == OPT_ONPLUS || (bs_mode == BS_CONTROL && proc_tab == OPT_OFF)) 1363 cw = strlen(prchar(ch)); 1364 else 1365 cw = tab_spaces(col); 1366 } else if ((!utf_mode || is_ascii_char(ch)) && control_char(ch)) 1367 { 1368 cw = strlen(prchar(ch)); 1369 } else if (utf8_len < MAX_UTF_CHAR_LEN) 1370 { 1371 utf8_buf[utf8_len++] = ch; 1372 if (is_utf8_well_formed(utf8_buf, utf8_len)) 1373 { 1374 LWCHAR wch = get_wchar(utf8_buf); 1375 utf8_len = 0; 1376 int attr = 0; /* {{ ignoring attribute is not correct for magic cookie terminals }} */ 1377 if (utf_mode && ctldisp != OPT_ON && is_ubin_char(wch)) 1378 cw = strlen(prutfchar(wch)); 1379 else 1380 cw = pwidth(wch, attr, prev_ch, attr); 1381 prev_ch = wch; 1382 } 1383 } else 1384 { 1385 utf8_len = 0; /* flush invalid UTF-8 */ 1386 } 1387 1388 if (cp->pos != NULL_POSITION && chpos == cp->pos) /* found the position we want */ 1389 break; 1390 if (cp->col >= 0 && col >= cp->col && cw > 0) /* found the column we want */ 1391 break; 1392 col += cw; 1393 prev_ch = ch; 1394 } 1395 cp->col = col; 1396 cp->pos = chpos; 1397 } 1398 1399 public int col_from_pos(POSITION linepos, POSITION spos, POSITION saved_pos, int saved_col) 1400 { 1401 struct col_pos cp; 1402 cp.pos = spos; 1403 cp.col = -1; 1404 col_vs_pos(linepos, &cp, saved_pos, saved_col); 1405 return cp.col; 1406 } 1407 1408 public POSITION pos_from_col(POSITION linepos, int col, POSITION saved_pos, int saved_col) 1409 { 1410 struct col_pos cp; 1411 cp.col = col + hshift - line_pfx_width(); 1412 cp.pos = NULL_POSITION; 1413 col_vs_pos(linepos, &cp, saved_pos, saved_col); 1414 return cp.pos; 1415 } 1416 1417 /* 1418 * Set an attribute on each char of the line in the line buffer. 1419 */ 1420 public void set_attr_line(int a) 1421 { 1422 size_t i; 1423 1424 for (i = linebuf.print; i < linebuf.end; i++) 1425 if ((linebuf.attr[i] & AT_COLOR) == 0 || (a & AT_COLOR) == 0) 1426 linebuf.attr[i] |= a; 1427 } 1428 1429 /* 1430 * Set the char to be displayed in the status column. 1431 */ 1432 public void set_status_col(char c, int attr) 1433 { 1434 set_pfx(0, c, attr); 1435 } 1436 1437 /* 1438 * Get a character from the current line. 1439 * Return the character as the function return value, 1440 * and the character attribute in *ap. 1441 */ 1442 public int gline(size_t i, int *ap) 1443 { 1444 if (is_null_line) 1445 { 1446 /* 1447 * If there is no current line, we pretend the line is 1448 * either "~" or "", depending on the "twiddle" flag. 1449 */ 1450 if (twiddle) 1451 { 1452 if (i == 0) 1453 { 1454 *ap = AT_BOLD; 1455 return '~'; 1456 } 1457 --i; 1458 } 1459 /* Make sure we're back to AT_NORMAL before the '\n'. */ 1460 *ap = AT_NORMAL; 1461 return i ? '\0' : '\n'; 1462 } 1463 1464 if (i < linebuf.pfx_end) 1465 { 1466 *ap = linebuf.pfx_attr[i]; 1467 return linebuf.pfx[i]; 1468 } 1469 i += linebuf.print - linebuf.pfx_end; 1470 *ap = linebuf.attr[i]; 1471 return (linebuf.buf[i] & 0xFF); 1472 } 1473 1474 /* 1475 * Indicate that there is no current line. 1476 */ 1477 public void null_line(void) 1478 { 1479 is_null_line = TRUE; 1480 cshift = 0; 1481 } 1482 1483 /* 1484 * Analogous to forw_line(), but deals with "raw lines": 1485 * lines which are not split for screen width. 1486 * {{ This is supposed to be more efficient than forw_line(). }} 1487 */ 1488 public POSITION forw_raw_line_len(POSITION curr_pos, size_t read_len, constant char **linep, size_t *line_lenp) 1489 { 1490 size_t n; 1491 int c; 1492 POSITION new_pos; 1493 1494 if (curr_pos == NULL_POSITION || ch_seek(curr_pos) || 1495 (c = ch_forw_get()) == EOI) 1496 return (NULL_POSITION); 1497 1498 n = 0; 1499 for (;;) 1500 { 1501 if (c == '\n' || c == EOI || ABORT_SIGS()) 1502 { 1503 new_pos = ch_tell(); 1504 break; 1505 } 1506 if (n >= size_linebuf-1) 1507 { 1508 if (expand_linebuf()) 1509 { 1510 /* 1511 * Overflowed the input buffer. 1512 * Pretend the line ended here. 1513 */ 1514 new_pos = ch_tell() - 1; 1515 break; 1516 } 1517 } 1518 linebuf.buf[n++] = (char) c; 1519 if (read_len != size_t_null && read_len > 0 && n >= read_len) 1520 { 1521 new_pos = ch_tell(); 1522 break; 1523 } 1524 c = ch_forw_get(); 1525 } 1526 linebuf.buf[n] = '\0'; 1527 if (linep != NULL) 1528 *linep = linebuf.buf; 1529 if (line_lenp != NULL) 1530 *line_lenp = n; 1531 return (new_pos); 1532 } 1533 1534 public POSITION forw_raw_line(POSITION curr_pos, constant char **linep, size_t *line_lenp) 1535 { 1536 return forw_raw_line_len(curr_pos, size_t_null, linep, line_lenp); 1537 } 1538 1539 /* 1540 * Analogous to back_line(), but deals with "raw lines". 1541 * {{ This is supposed to be more efficient than back_line(). }} 1542 */ 1543 public POSITION back_raw_line(POSITION curr_pos, constant char **linep, size_t *line_lenp) 1544 { 1545 size_t n; 1546 int c; 1547 POSITION new_pos; 1548 1549 if (curr_pos == NULL_POSITION || curr_pos <= ch_zero() || 1550 ch_seek(curr_pos-1)) 1551 return (NULL_POSITION); 1552 1553 n = size_linebuf; 1554 linebuf.buf[--n] = '\0'; 1555 for (;;) 1556 { 1557 c = ch_back_get(); 1558 if (c == '\n' || ABORT_SIGS()) 1559 { 1560 /* 1561 * This is the newline ending the previous line. 1562 * We have hit the beginning of the line. 1563 */ 1564 new_pos = ch_tell() + 1; 1565 break; 1566 } 1567 if (c == EOI) 1568 { 1569 /* 1570 * We have hit the beginning of the file. 1571 * This must be the first line in the file. 1572 * This must, of course, be the beginning of the line. 1573 */ 1574 new_pos = ch_zero(); 1575 break; 1576 } 1577 if (n <= 0) 1578 { 1579 size_t old_size_linebuf = size_linebuf; 1580 char *fm; 1581 char *to; 1582 if (expand_linebuf()) 1583 { 1584 /* 1585 * Overflowed the input buffer. 1586 * Pretend the line ended here. 1587 */ 1588 new_pos = ch_tell() + 1; 1589 break; 1590 } 1591 /* 1592 * Shift the data to the end of the new linebuf. 1593 */ 1594 for (fm = linebuf.buf + old_size_linebuf - 1, 1595 to = linebuf.buf + size_linebuf - 1; 1596 fm >= linebuf.buf; fm--, to--) 1597 *to = *fm; 1598 n = size_linebuf - old_size_linebuf; 1599 } 1600 linebuf.buf[--n] = (char) c; 1601 } 1602 if (linep != NULL) 1603 *linep = &linebuf.buf[n]; 1604 if (line_lenp != NULL) 1605 *line_lenp = size_linebuf - 1 - n; 1606 return (new_pos); 1607 } 1608 1609 /* 1610 * Skip cols printable columns at the start of line. 1611 * Return number of bytes skipped. 1612 */ 1613 public int skip_columns(int cols, constant char **linep, size_t *line_lenp) 1614 { 1615 constant char *line = *linep; 1616 constant char *eline = line + *line_lenp; 1617 LWCHAR pch = 0; 1618 size_t bytes; 1619 1620 while (cols > 0 && line < eline) 1621 { 1622 LWCHAR ch = step_charc(&line, +1, eline); 1623 struct ansi_state *pansi = ansi_start(ch); 1624 if (pansi != NULL) 1625 { 1626 skip_ansi(pansi, &line, eline); 1627 ansi_done(pansi); 1628 pch = 0; 1629 } else 1630 { 1631 int w = pwidth(ch, 0, pch, 0); 1632 cols -= w; 1633 pch = ch; 1634 } 1635 } 1636 bytes = ptr_diff(line, *linep); 1637 *linep = line; 1638 *line_lenp -= bytes; 1639 return (int) bytes; /*{{type-issue}}*/ 1640 } 1641 1642 /* 1643 * Append a string to the line buffer. 1644 */ 1645 static int pappstr(constant char *str) 1646 { 1647 while (*str != '\0') 1648 { 1649 if (pappend(*str++, NULL_POSITION)) 1650 /* Doesn't fit on screen. */ 1651 return 1; 1652 } 1653 return 0; 1654 } 1655 1656 /* 1657 * Load a string into the line buffer. 1658 * If the string is too long to fit on the screen, 1659 * truncate the beginning of the string to fit. 1660 */ 1661 public void load_line(constant char *str) 1662 { 1663 int save_hshift = hshift; 1664 1665 hshift = 0; 1666 for (;;) 1667 { 1668 prewind(); 1669 if (pappstr(str) == 0) 1670 break; 1671 /* 1672 * Didn't fit on screen; increase left shift by one. 1673 * {{ This gets very inefficient if the string 1674 * is much longer than the screen width. }} 1675 */ 1676 hshift += 1; 1677 } 1678 set_linebuf(linebuf.end, '\0', AT_NORMAL); 1679 hshift = save_hshift; 1680 } 1681 1682 /* 1683 * Find the shift necessary to show the end of the longest displayed line. 1684 */ 1685 public int rrshift(void) 1686 { 1687 POSITION pos; 1688 int save_width; 1689 int sline; 1690 int longest = 0; 1691 1692 save_width = sc_width; 1693 sc_width = INT_MAX; /* so forw_line() won't chop */ 1694 for (sline = TOP; sline < sc_height; sline++) 1695 if ((pos = position(sline)) != NULL_POSITION) 1696 break; 1697 for (; sline < sc_height && pos != NULL_POSITION; sline++) 1698 { 1699 pos = forw_line(pos); 1700 if (end_column > longest) 1701 longest = end_column; 1702 } 1703 sc_width = save_width; 1704 if (longest < sc_width) 1705 return 0; 1706 return longest - sc_width; 1707 } 1708 1709 /* 1710 * Get the color_map index associated with a given attribute. 1711 */ 1712 static int lookup_color_index(int attr) 1713 { 1714 int cx; 1715 for (cx = 0; cx < countof(color_map); cx++) 1716 if (color_map[cx].attr == attr) 1717 return cx; 1718 return -1; 1719 } 1720 1721 static int color_index(int attr) 1722 { 1723 if (use_color && (attr & AT_COLOR)) 1724 return lookup_color_index(attr & AT_COLOR); 1725 if (attr & AT_UNDERLINE) 1726 return lookup_color_index(AT_UNDERLINE); 1727 if (attr & AT_BOLD) 1728 return lookup_color_index(AT_BOLD); 1729 if (attr & AT_BLINK) 1730 return lookup_color_index(AT_BLINK); 1731 if (attr & AT_STANDOUT) 1732 return lookup_color_index(AT_STANDOUT); 1733 return -1; 1734 } 1735 1736 /* 1737 * Set the color string to use for a given attribute. 1738 */ 1739 public int set_color_map(int attr, constant char *colorstr) 1740 { 1741 int cx = color_index(attr); 1742 if (cx < 0) 1743 return -1; 1744 if (strlen(colorstr)+1 > sizeof(color_map[cx].color)) 1745 return -1; 1746 if (*colorstr != '\0' && parse_color(colorstr, NULL, NULL, NULL) == CT_NULL) 1747 return -1; 1748 strcpy(color_map[cx].color, colorstr); 1749 return 0; 1750 } 1751 1752 /* 1753 * Get the color string to use for a given attribute. 1754 */ 1755 public constant char * get_color_map(int attr) 1756 { 1757 int cx = color_index(attr); 1758 if (cx < 0) 1759 return NULL; 1760 return color_map[cx].color; 1761 } 1762