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