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