Annotation of src/usr.bin/less/line.c, Revision 1.10
1.1 etheisen 1: /*
1.10 ! shadchin 2: * Copyright (C) 1984-2011 Mark Nudelman
1.1 etheisen 3: *
1.5 millert 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.
1.1 etheisen 6: *
1.5 millert 7: * For more information about less, or for information on how to
8: * contact the author, see the README file.
1.1 etheisen 9: */
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"
1.10 ! shadchin 19: #include "charset.h"
1.1 etheisen 20:
1.10 ! shadchin 21: #include <err.h>
1.5 millert 22:
1.10 ! shadchin 23: static char *linebuf = NULL; /* Buffer which holds the current output line */
1.5 millert 24: static char *attr = NULL; /* Extension of linebuf to hold attributes */
25: public int size_linebuf = 0; /* Size of line buffer (and attr buffer) */
26:
1.10 ! shadchin 27: static int cshift; /* Current left-shift of output line buffer */
1.5 millert 28: public int hshift; /* Desired left-shift of output line buffer */
29: public int tabstops[TABSTOP_MAX] = { 0 }; /* Custom tabstops */
30: public int ntabstops = 1; /* Number of tabstops */
31: public int tabdefault = 8; /* Default repeated tabstops */
1.1 etheisen 32:
33: static int curr; /* Index into linebuf */
34: static int column; /* Printable length, accounting for
35: backspaces, etc. */
36: static int overstrike; /* Next char should overstrike previous char */
1.5 millert 37: static int last_overstrike = AT_NORMAL;
1.1 etheisen 38: static int is_null_line; /* There is no current line */
1.5 millert 39: static int lmargin; /* Left margin */
1.1 etheisen 40: static char pendc;
41: static POSITION pendpos;
1.5 millert 42: static char *end_ansi_chars;
1.10 ! shadchin 43: static char *mid_ansi_chars;
1.1 etheisen 44:
1.10 ! shadchin 45: static int attr_swidth();
! 46: static int attr_ewidth();
1.1 etheisen 47: static int do_append();
48:
1.10 ! shadchin 49: extern int sigs;
1.1 etheisen 50: extern int bs_mode;
51: extern int linenums;
52: extern int ctldisp;
53: extern int twiddle;
54: extern int binattr;
1.5 millert 55: extern int status_col;
1.1 etheisen 56: extern int auto_wrap, ignaw;
57: extern int bo_s_width, bo_e_width;
58: extern int ul_s_width, ul_e_width;
59: extern int bl_s_width, bl_e_width;
60: extern int so_s_width, so_e_width;
61: extern int sc_width, sc_height;
1.5 millert 62: extern int utf_mode;
63: extern POSITION start_attnpos;
64: extern POSITION end_attnpos;
65:
1.10 ! shadchin 66: static char mbc_buf[MAX_UTF_CHAR_LEN];
! 67: static int mbc_buf_len = 0;
! 68: static int mbc_buf_index = 0;
! 69: static POSITION mbc_pos;
! 70:
1.5 millert 71: /*
72: * Initialize from environment variables.
73: */
74: public void
75: init_line()
76: {
77: end_ansi_chars = lgetenv("LESSANSIENDCHARS");
78: if (end_ansi_chars == NULL || *end_ansi_chars == '\0')
79: end_ansi_chars = "m";
1.10 ! shadchin 80:
! 81: mid_ansi_chars = lgetenv("LESSANSIMIDCHARS");
! 82: if (mid_ansi_chars == NULL || *mid_ansi_chars == '\0')
! 83: mid_ansi_chars = "0123456789;[?!\"'#%()*+ ";
! 84:
1.5 millert 85: linebuf = (char *) ecalloc(LINEBUF_SIZE, sizeof(char));
86: attr = (char *) ecalloc(LINEBUF_SIZE, sizeof(char));
87: size_linebuf = LINEBUF_SIZE;
88: }
89:
90: /*
91: * Expand the line buffer.
92: */
1.10 ! shadchin 93: static int
1.5 millert 94: expand_linebuf()
95: {
1.10 ! shadchin 96: /* Double the size of the line buffer. */
1.7 millert 97: int new_size = size_linebuf * 2;
1.10 ! shadchin 98:
! 99: /* Just realloc to expand the buffer, if we can. */
1.9 otto 100: char *new_buf;
101: char *new_attr;
102:
103: new_buf = realloc(linebuf, new_size);
104: if (new_buf == NULL)
105: return 1;
106: new_attr = realloc(attr, new_size);
107: if (new_attr == NULL) {
108: /* realloc linebuf back to original size */
109: linebuf = realloc(new_buf, size_linebuf);
110: if (linebuf == NULL)
111: err(1, NULL);
1.5 millert 112: return 1;
113: }
1.10 ! shadchin 114: /*
! 115: * We realloc'd the buffers; they already have the old contents.
! 116: */
1.7 millert 117: memset(new_buf + size_linebuf, 0, new_size - size_linebuf);
118: memset(new_attr + size_linebuf, 0, new_size - size_linebuf);
1.5 millert 119: linebuf = new_buf;
120: attr = new_attr;
121: size_linebuf = new_size;
122: return 0;
123: }
1.1 etheisen 124:
125: /*
1.10 ! shadchin 126: * Is a character ASCII?
! 127: */
! 128: public int
! 129: is_ascii_char(ch)
! 130: LWCHAR ch;
! 131: {
! 132: return (ch <= 0x7F);
! 133: }
! 134:
! 135: /*
1.1 etheisen 136: * Rewind the line buffer.
137: */
138: public void
139: prewind()
140: {
141: curr = 0;
142: column = 0;
1.10 ! shadchin 143: cshift = 0;
1.1 etheisen 144: overstrike = 0;
1.10 ! shadchin 145: last_overstrike = AT_NORMAL;
! 146: mbc_buf_len = 0;
1.1 etheisen 147: is_null_line = 0;
148: pendc = '\0';
1.5 millert 149: lmargin = 0;
150: if (status_col)
151: lmargin += 1;
1.1 etheisen 152: }
153:
154: /*
155: * Insert the line number (of the given position) into the line buffer.
156: */
157: public void
158: plinenum(pos)
159: POSITION pos;
160: {
1.5 millert 161: register LINENUM linenum = 0;
162: register int i;
163:
164: if (linenums == OPT_ONPLUS)
165: {
166: /*
167: * Get the line number and put it in the current line.
168: * {{ Note: since find_linenum calls forw_raw_line,
169: * it may seek in the input file, requiring the caller
170: * of plinenum to re-seek if necessary. }}
171: * {{ Since forw_raw_line modifies linebuf, we must
172: * do this first, before storing anything in linebuf. }}
173: */
174: linenum = find_linenum(pos);
175: }
1.1 etheisen 176:
177: /*
1.5 millert 178: * Display a status column if the -J option is set.
1.1 etheisen 179: */
1.5 millert 180: if (status_col)
181: {
182: linebuf[curr] = ' ';
183: if (start_attnpos != NULL_POSITION &&
184: pos >= start_attnpos && pos < end_attnpos)
1.10 ! shadchin 185: attr[curr] = AT_NORMAL|AT_HILITE;
1.5 millert 186: else
1.10 ! shadchin 187: attr[curr] = AT_NORMAL;
1.5 millert 188: curr++;
189: column++;
190: }
1.1 etheisen 191: /*
1.5 millert 192: * Display the line number at the start of each line
193: * if the -N option is set.
1.1 etheisen 194: */
1.5 millert 195: if (linenums == OPT_ONPLUS)
196: {
197: char buf[INT_STRLEN_BOUND(pos) + 2];
198: int n;
1.1 etheisen 199:
1.5 millert 200: linenumtoa(linenum, buf, sizeof(buf));
201: n = strlen(buf);
202: if (n < MIN_LINENUM_WIDTH)
203: n = MIN_LINENUM_WIDTH;
1.6 millert 204: snprintf(linebuf+curr, size_linebuf-curr, "%*s ", n, buf);
1.5 millert 205: n++; /* One space after the line number. */
206: for (i = 0; i < n; i++)
207: attr[curr+i] = AT_NORMAL;
208: curr += n;
209: column += n;
210: lmargin += n;
211: }
1.1 etheisen 212:
213: /*
1.5 millert 214: * Append enough spaces to bring us to the lmargin.
1.1 etheisen 215: */
1.5 millert 216: while (column < lmargin)
1.1 etheisen 217: {
218: linebuf[curr] = ' ';
219: attr[curr++] = AT_NORMAL;
220: column++;
1.5 millert 221: }
222: }
223:
224: /*
1.10 ! shadchin 225: * Shift the input line left.
! 226: * This means discarding N printable chars at the start of the buffer.
1.5 millert 227: */
1.10 ! shadchin 228: static void
! 229: pshift(shift)
! 230: int shift;
! 231: {
! 232: LWCHAR prev_ch = 0;
! 233: unsigned char c;
! 234: int shifted = 0;
! 235: int to;
! 236: int from;
1.5 millert 237: int len;
1.10 ! shadchin 238: int width;
! 239: int prev_attr;
! 240: int next_attr;
! 241:
! 242: if (shift > column - lmargin)
! 243: shift = column - lmargin;
! 244: if (shift > curr - lmargin)
! 245: shift = curr - lmargin;
1.5 millert 246:
1.10 ! shadchin 247: to = from = lmargin;
1.5 millert 248: /*
1.10 ! shadchin 249: * We keep on going when shifted == shift
! 250: * to get all combining chars.
1.5 millert 251: */
1.10 ! shadchin 252: while (shifted <= shift && from < curr)
1.5 millert 253: {
1.10 ! shadchin 254: c = linebuf[from];
! 255: if (ctldisp == OPT_ONPLUS && IS_CSI_START(c))
! 256: {
! 257: /* Keep cumulative effect. */
! 258: linebuf[to] = c;
! 259: attr[to++] = attr[from++];
! 260: while (from < curr && linebuf[from])
! 261: {
! 262: linebuf[to] = linebuf[from];
! 263: attr[to++] = attr[from];
! 264: if (!is_ansi_middle(linebuf[from++]))
! 265: break;
! 266: }
! 267: continue;
! 268: }
! 269:
! 270: width = 0;
! 271:
! 272: #if !SMALL
! 273: if (!IS_ASCII_OCTET(c) && utf_mode)
1.5 millert 274: {
1.10 ! shadchin 275: /* Assumes well-formedness validation already done. */
! 276: LWCHAR ch;
! 277:
! 278: len = utf_len(c);
! 279: if (from + len > curr)
! 280: break;
! 281: ch = get_wchar(linebuf + from);
! 282: if (!is_composing_char(ch) && !is_combining_char(prev_ch, ch))
! 283: width = is_wide_char(ch) ? 2 : 1;
! 284: prev_ch = ch;
1.5 millert 285: } else
1.10 ! shadchin 286: #endif /* !SMALL */
! 287: {
! 288: len = 1;
! 289: if (c == '\b')
! 290: /* XXX - Incorrect if several '\b' in a row. */
! 291: #if !SMALL
! 292: width = (utf_mode && is_wide_char(prev_ch)) ? -2 : -1;
! 293: #else
! 294: width = -1;
! 295: #endif /* !SMALL */
! 296: else if (!control_char(c))
! 297: width = 1;
! 298: prev_ch = 0;
! 299: }
! 300:
! 301: if (width == 2 && shift - shifted == 1) {
! 302: /* Should never happen when called by pshift_all(). */
! 303: attr[to] = attr[from];
! 304: /*
! 305: * Assume a wide_char will never be the first half of a
! 306: * combining_char pair, so reset prev_ch in case we're
! 307: * followed by a '\b'.
! 308: */
! 309: prev_ch = linebuf[to++] = ' ';
! 310: from += len;
! 311: shifted++;
! 312: continue;
! 313: }
! 314:
! 315: /* Adjust width for magic cookies. */
! 316: prev_attr = (to > 0) ? attr[to-1] : AT_NORMAL;
! 317: next_attr = (from + len < curr) ? attr[from + len] : prev_attr;
! 318: if (!is_at_equiv(attr[from], prev_attr) &&
! 319: !is_at_equiv(attr[from], next_attr))
1.5 millert 320: {
1.10 ! shadchin 321: width += attr_swidth(attr[from]);
! 322: if (from + len < curr)
! 323: width += attr_ewidth(attr[from]);
! 324: if (is_at_equiv(prev_attr, next_attr))
1.5 millert 325: {
1.10 ! shadchin 326: width += attr_ewidth(prev_attr);
! 327: if (from + len < curr)
! 328: width += attr_swidth(next_attr);
1.5 millert 329: }
330: }
331:
1.10 ! shadchin 332: if (shift - shifted < width)
! 333: break;
! 334: from += len;
! 335: shifted += width;
! 336: if (shifted < 0)
! 337: shifted = 0;
! 338: }
! 339: while (from < curr)
! 340: {
! 341: linebuf[to] = linebuf[from];
! 342: attr[to++] = attr[from++];
! 343: }
! 344: curr = to;
! 345: column -= shifted;
! 346: cshift += shifted;
1.5 millert 347: }
348:
349: /*
1.10 ! shadchin 350: *
1.5 millert 351: */
1.10 ! shadchin 352: public void
! 353: pshift_all()
1.5 millert 354: {
1.10 ! shadchin 355: pshift(column);
1.1 etheisen 356: }
357:
358: /*
359: * Return the printing width of the start (enter) sequence
360: * for a given character attribute.
361: */
1.5 millert 362: static int
1.1 etheisen 363: attr_swidth(a)
364: int a;
365: {
1.10 ! shadchin 366: int w = 0;
! 367:
! 368: a = apply_at_specials(a);
! 369:
! 370: if (a & AT_UNDERLINE)
! 371: w += ul_s_width;
! 372: if (a & AT_BOLD)
! 373: w += bo_s_width;
! 374: if (a & AT_BLINK)
! 375: w += bl_s_width;
! 376: if (a & AT_STANDOUT)
! 377: w += so_s_width;
! 378:
! 379: return w;
1.1 etheisen 380: }
381:
382: /*
383: * Return the printing width of the end (exit) sequence
384: * for a given character attribute.
385: */
1.5 millert 386: static int
1.1 etheisen 387: attr_ewidth(a)
388: int a;
389: {
1.10 ! shadchin 390: int w = 0;
! 391:
! 392: a = apply_at_specials(a);
! 393:
! 394: if (a & AT_UNDERLINE)
! 395: w += ul_e_width;
! 396: if (a & AT_BOLD)
! 397: w += bo_e_width;
! 398: if (a & AT_BLINK)
! 399: w += bl_e_width;
! 400: if (a & AT_STANDOUT)
! 401: w += so_e_width;
! 402:
! 403: return w;
1.1 etheisen 404: }
405:
406: /*
407: * Return the printing width of a given character and attribute,
408: * if the character were added to the current position in the line buffer.
409: * Adding a character with a given attribute may cause an enter or exit
410: * attribute sequence to be inserted, so this must be taken into account.
411: */
412: static int
1.10 ! shadchin 413: pwidth(ch, a, prev_ch)
! 414: LWCHAR ch;
1.1 etheisen 415: int a;
1.10 ! shadchin 416: LWCHAR prev_ch;
1.1 etheisen 417: {
1.10 ! shadchin 418: int w;
1.1 etheisen 419:
1.10 ! shadchin 420: if (ch == '\b')
1.1 etheisen 421: /*
1.10 ! shadchin 422: * Backspace moves backwards one or two positions.
! 423: * XXX - Incorrect if several '\b' in a row.
1.1 etheisen 424: */
1.10 ! shadchin 425: #if !SMALL
! 426: return (utf_mode && is_wide_char(prev_ch)) ? -2 : -1;
! 427: #else
! 428: return -1;
! 429: #endif /* !SMALL */
1.1 etheisen 430:
1.10 ! shadchin 431: if (!utf_mode || is_ascii_char(ch))
! 432: {
! 433: if (control_char((char)ch))
! 434: {
! 435: /*
! 436: * Control characters do unpredictable things,
! 437: * so we don't even try to guess; say it doesn't move.
! 438: * This can only happen if the -r flag is in effect.
! 439: */
! 440: return (0);
! 441: }
! 442: }
! 443: #if !SMALL
! 444: else
! 445: {
! 446: if (is_composing_char(ch) || is_combining_char(prev_ch, ch))
! 447: {
! 448: /*
! 449: * Composing and combining chars take up no space.
! 450: *
! 451: * Some terminals, upon failure to compose a
! 452: * composing character with the character(s) that
! 453: * precede(s) it will actually take up one column
! 454: * for the composing character; there isn't much
! 455: * we could do short of testing the (complex)
! 456: * composition process ourselves and printing
! 457: * a binary representation when it fails.
! 458: */
! 459: return (0);
! 460: }
! 461: }
! 462: #endif /* !SMALL */
1.1 etheisen 463:
464: /*
1.10 ! shadchin 465: * Other characters take one or two columns,
1.1 etheisen 466: * plus the width of any attribute enter/exit sequence.
467: */
468: w = 1;
1.10 ! shadchin 469: #if !SMALL
! 470: if (is_wide_char(ch))
! 471: w++;
! 472: #endif /* !SMALL */
! 473: if (curr > 0 && !is_at_equiv(attr[curr-1], a))
1.1 etheisen 474: w += attr_ewidth(attr[curr-1]);
1.10 ! shadchin 475: if ((apply_at_specials(a) != AT_NORMAL) &&
! 476: (curr == 0 || !is_at_equiv(attr[curr-1], a)))
1.1 etheisen 477: w += attr_swidth(a);
478: return (w);
479: }
480:
481: /*
1.10 ! shadchin 482: * Delete to the previous base character in the line buffer.
! 483: * Return 1 if one is found.
1.1 etheisen 484: */
1.10 ! shadchin 485: static int
1.1 etheisen 486: backc()
487: {
1.10 ! shadchin 488: LWCHAR prev_ch;
! 489: char *p = linebuf + curr;
! 490: LWCHAR ch = step_char(&p, -1, linebuf + lmargin);
! 491: int width;
! 492:
! 493: /* This assumes that there is no '\b' in linebuf. */
! 494: while ( curr > lmargin
! 495: && column > lmargin
! 496: && (!(attr[curr - 1] & (AT_ANSI|AT_BINARY))))
! 497: {
! 498: curr = p - linebuf;
! 499: prev_ch = step_char(&p, -1, linebuf + lmargin);
! 500: width = pwidth(ch, attr[curr], prev_ch);
! 501: column -= width;
! 502: if (width > 0)
! 503: return 1;
! 504: ch = prev_ch;
! 505: }
! 506:
! 507: return 0;
1.1 etheisen 508: }
509:
510: /*
1.5 millert 511: * Are we currently within a recognized ANSI escape sequence?
512: */
513: static int
514: in_ansi_esc_seq()
515: {
1.10 ! shadchin 516: char *p;
1.5 millert 517:
518: /*
519: * Search backwards for either an ESC (which means we ARE in a seq);
520: * or an end char (which means we're NOT in a seq).
521: */
1.10 ! shadchin 522: for (p = &linebuf[curr]; p > linebuf; )
1.5 millert 523: {
1.10 ! shadchin 524: LWCHAR ch = step_char(&p, -1, linebuf);
! 525: if (IS_CSI_START(ch))
1.5 millert 526: return (1);
1.10 ! shadchin 527: if (!is_ansi_middle(ch))
1.5 millert 528: return (0);
529: }
530: return (0);
531: }
532:
533: /*
534: * Is a character the end of an ANSI escape sequence?
535: */
536: public int
1.10 ! shadchin 537: is_ansi_end(ch)
! 538: LWCHAR ch;
! 539: {
! 540: if (!is_ascii_char(ch))
! 541: return (0);
! 542: return (strchr(end_ansi_chars, (char) ch) != NULL);
! 543: }
! 544:
! 545: /*
! 546: *
! 547: */
! 548: public int
! 549: is_ansi_middle(ch)
! 550: LWCHAR ch;
1.5 millert 551: {
1.10 ! shadchin 552: if (!is_ascii_char(ch))
! 553: return (0);
! 554: if (is_ansi_end(ch))
! 555: return (0);
! 556: return (strchr(mid_ansi_chars, (char) ch) != NULL);
1.5 millert 557: }
558:
559: /*
1.1 etheisen 560: * Append a character and attribute to the line buffer.
561: */
1.10 ! shadchin 562: #define STORE_CHAR(ch,a,rep,pos) \
! 563: do { \
! 564: if (store_char((ch),(a),(rep),(pos))) return (1); \
! 565: } while (0)
1.5 millert 566:
1.1 etheisen 567: static int
1.10 ! shadchin 568: store_char(ch, a, rep, pos)
! 569: LWCHAR ch;
1.1 etheisen 570: int a;
1.10 ! shadchin 571: char *rep;
1.1 etheisen 572: POSITION pos;
573: {
1.10 ! shadchin 574: int w;
! 575: int replen;
! 576: char cs;
! 577:
! 578: w = (a & (AT_UNDERLINE|AT_BOLD)); /* Pre-use w. */
! 579: if (w != AT_NORMAL)
! 580: last_overstrike = w;
1.1 etheisen 581:
582: #if HILITE_SEARCH
1.5 millert 583: {
1.10 ! shadchin 584: int matches;
! 585: if (is_hilited(pos, pos+1, 0, &matches))
! 586: {
! 587: /*
! 588: * This character should be highlighted.
! 589: * Override the attribute passed in.
! 590: */
! 591: if (a != AT_ANSI)
! 592: a |= AT_HILITE;
! 593: }
1.5 millert 594: }
1.1 etheisen 595: #endif
1.10 ! shadchin 596:
1.5 millert 597: if (ctldisp == OPT_ONPLUS && in_ansi_esc_seq())
1.10 ! shadchin 598: {
! 599: if (!is_ansi_end(ch) && !is_ansi_middle(ch)) {
! 600: /* Remove whole unrecognized sequence. */
! 601: char *p = &linebuf[curr];
! 602: LWCHAR bch;
! 603: do {
! 604: bch = step_char(&p, -1, linebuf);
! 605: } while (p > linebuf && !IS_CSI_START(bch));
! 606: curr = p - linebuf;
! 607: return 0;
! 608: }
! 609: a = AT_ANSI; /* Will force re-AT_'ing around it. */
1.5 millert 610: w = 0;
1.10 ! shadchin 611: }
! 612: else if (ctldisp == OPT_ONPLUS && IS_CSI_START(ch))
! 613: {
! 614: a = AT_ANSI; /* Will force re-AT_'ing around it. */
! 615: w = 0;
! 616: }
1.5 millert 617: else
1.10 ! shadchin 618: {
! 619: char *p = &linebuf[curr];
! 620: LWCHAR prev_ch = step_char(&p, -1, linebuf);
! 621: w = pwidth(ch, a, prev_ch);
! 622: }
! 623:
1.5 millert 624: if (ctldisp != OPT_ON && column + w + attr_ewidth(a) > sc_width)
1.1 etheisen 625: /*
626: * Won't fit on screen.
627: */
628: return (1);
629:
1.10 ! shadchin 630: if (rep == NULL)
! 631: {
! 632: cs = (char) ch;
! 633: rep = &cs;
! 634: replen = 1;
! 635: } else
! 636: {
! 637: #if !SMALL
! 638: replen = utf_len(rep[0]);
! 639: #else
! 640: replen = 1;
! 641: #endif
! 642: }
! 643: if (curr + replen >= size_linebuf-6)
1.5 millert 644: {
1.1 etheisen 645: /*
646: * Won't fit in line buffer.
1.5 millert 647: * Try to expand it.
1.1 etheisen 648: */
1.5 millert 649: if (expand_linebuf())
650: return (1);
651: }
1.1 etheisen 652:
1.10 ! shadchin 653: while (replen-- > 0)
1.1 etheisen 654: {
1.10 ! shadchin 655: linebuf[curr] = *rep++;
! 656: attr[curr] = a;
! 657: curr++;
1.1 etheisen 658: }
659: column += w;
660: return (0);
661: }
662:
663: /*
1.5 millert 664: * Append a tab to the line buffer.
665: * Store spaces to represent the tab.
666: */
667: #define STORE_TAB(a,pos) \
668: do { if (store_tab((a),(pos))) return (1); } while (0)
669:
670: static int
671: store_tab(attr, pos)
672: int attr;
673: POSITION pos;
674: {
675: int to_tab = column + cshift - lmargin;
676: int i;
677:
678: if (ntabstops < 2 || to_tab >= tabstops[ntabstops-1])
679: to_tab = tabdefault -
680: ((to_tab - tabstops[ntabstops-1]) % tabdefault);
681: else
682: {
683: for (i = ntabstops - 2; i >= 0; i--)
684: if (to_tab >= tabstops[i])
685: break;
686: to_tab = tabstops[i+1] - to_tab;
687: }
688:
1.10 ! shadchin 689: if (column + to_tab - 1 + pwidth(' ', attr, 0) + attr_ewidth(attr) > sc_width)
! 690: return 1;
! 691:
1.5 millert 692: do {
1.10 ! shadchin 693: STORE_CHAR(' ', attr, " ", pos);
1.5 millert 694: } while (--to_tab > 0);
695: return 0;
696: }
697:
1.10 ! shadchin 698: #define STORE_PRCHAR(c, pos) \
! 699: do { if (store_prchar((c), (pos))) return 1; } while (0)
! 700:
! 701: static int
! 702: store_prchar(c, pos)
! 703: char c;
! 704: POSITION pos;
! 705: {
! 706: char *s;
! 707:
! 708: /*
! 709: * Convert to printable representation.
! 710: */
! 711: s = prchar(c);
! 712:
! 713: /*
! 714: * Make sure we can get the entire representation
! 715: * of the character on this line.
! 716: */
! 717: if (column + (int) strlen(s) - 1 +
! 718: pwidth(' ', binattr, 0) + attr_ewidth(binattr) > sc_width)
! 719: return 1;
! 720:
! 721: for ( ; *s != 0; s++)
! 722: STORE_CHAR(*s, AT_BINARY, NULL, pos);
! 723:
! 724: return 0;
! 725: }
! 726:
! 727: #if !SMALL
! 728: static int
! 729: flush_mbc_buf(pos)
! 730: POSITION pos;
! 731: {
! 732: int i;
! 733:
! 734: for (i = 0; i < mbc_buf_index; i++)
! 735: if (store_prchar(mbc_buf[i], pos))
! 736: return mbc_buf_index - i;
! 737:
! 738: return 0;
! 739: }
! 740: #endif /* !SMALL */
! 741:
1.5 millert 742: /*
1.1 etheisen 743: * Append a character to the line buffer.
744: * Expand tabs into spaces, handle underlining, boldfacing, etc.
745: * Returns 0 if ok, 1 if couldn't fit in buffer.
746: */
747: public int
748: pappend(c, pos)
1.10 ! shadchin 749: char c;
1.1 etheisen 750: POSITION pos;
751: {
1.5 millert 752: int r;
753:
1.1 etheisen 754: if (pendc)
755: {
1.10 ! shadchin 756: if (do_append(pendc, NULL, pendpos))
1.1 etheisen 757: /*
758: * Oops. We've probably lost the char which
759: * was in pendc, since caller won't back up.
760: */
761: return (1);
762: pendc = '\0';
763: }
764:
765: if (c == '\r' && bs_mode == BS_SPECIAL)
766: {
1.10 ! shadchin 767: #if !SMALL
! 768: if (mbc_buf_len > 0) /* utf_mode must be on. */
! 769: {
! 770: /* Flush incomplete (truncated) sequence. */
! 771: r = flush_mbc_buf(mbc_pos);
! 772: mbc_buf_index = r + 1;
! 773: mbc_buf_len = 0;
! 774: if (r)
! 775: return (mbc_buf_index);
! 776: }
! 777: #endif /* !SMALL */
! 778:
1.1 etheisen 779: /*
780: * Don't put the CR into the buffer until we see
781: * the next char. If the next char is a newline,
782: * discard the CR.
783: */
784: pendc = c;
785: pendpos = pos;
786: return (0);
787: }
788:
1.10 ! shadchin 789: if (!utf_mode)
! 790: {
! 791: r = do_append((LWCHAR) c, NULL, pos);
! 792: }
! 793: #if !SMALL
! 794: else
! 795: {
! 796: /* Perform strict validation in all possible cases. */
! 797: if (mbc_buf_len == 0)
! 798: {
! 799: retry:
! 800: mbc_buf_index = 1;
! 801: *mbc_buf = c;
! 802: if (IS_ASCII_OCTET(c))
! 803: r = do_append((LWCHAR) c, NULL, pos);
! 804: else if (IS_UTF8_LEAD(c))
! 805: {
! 806: mbc_buf_len = utf_len(c);
! 807: mbc_pos = pos;
! 808: return (0);
! 809: } else
! 810: /* UTF8_INVALID or stray UTF8_TRAIL */
! 811: r = flush_mbc_buf(pos);
! 812: } else if (IS_UTF8_TRAIL(c))
! 813: {
! 814: mbc_buf[mbc_buf_index++] = c;
! 815: if (mbc_buf_index < mbc_buf_len)
! 816: return (0);
! 817: if (is_utf8_well_formed(mbc_buf))
! 818: r = do_append(get_wchar(mbc_buf), mbc_buf, mbc_pos);
! 819: else
! 820: /* Complete, but not shortest form, sequence. */
! 821: mbc_buf_index = r = flush_mbc_buf(mbc_pos);
! 822: mbc_buf_len = 0;
! 823: } else
! 824: {
! 825: /* Flush incomplete (truncated) sequence. */
! 826: r = flush_mbc_buf(mbc_pos);
! 827: mbc_buf_index = r + 1;
! 828: mbc_buf_len = 0;
! 829: /* Handle new char. */
! 830: if (!r)
! 831: goto retry;
! 832: }
! 833: }
! 834: #endif /* !SMALL */
! 835:
1.5 millert 836: /*
837: * If we need to shift the line, do it.
838: * But wait until we get to at least the middle of the screen,
839: * so shifting it doesn't affect the chars we're currently
840: * pappending. (Bold & underline can get messed up otherwise.)
841: */
842: if (cshift < hshift && column > sc_width / 2)
843: {
844: linebuf[curr] = '\0';
845: pshift(hshift - cshift);
846: }
1.10 ! shadchin 847: if (r)
! 848: {
! 849: /* How many chars should caller back up? */
! 850: r = (!utf_mode) ? 1 : mbc_buf_index;
! 851: }
1.5 millert 852: return (r);
1.1 etheisen 853: }
854:
855: static int
1.10 ! shadchin 856: do_append(ch, rep, pos)
! 857: LWCHAR ch;
! 858: char *rep;
1.1 etheisen 859: POSITION pos;
860: {
1.5 millert 861: register int a;
1.10 ! shadchin 862: LWCHAR prev_ch;
1.1 etheisen 863:
1.10 ! shadchin 864: a = AT_NORMAL;
1.1 etheisen 865:
1.10 ! shadchin 866: if (ch == '\b')
1.5 millert 867: {
1.10 ! shadchin 868: if (bs_mode == BS_CONTROL)
1.5 millert 869: goto do_control_char;
1.10 ! shadchin 870:
! 871: /*
! 872: * A better test is needed here so we don't
! 873: * backspace over part of the printed
! 874: * representation of a binary character.
! 875: */
! 876: if ( curr <= lmargin
! 877: || column <= lmargin
! 878: || (attr[curr - 1] & (AT_ANSI|AT_BINARY)))
! 879: STORE_PRCHAR('\b', pos);
! 880: else if (bs_mode == BS_NORMAL)
! 881: STORE_CHAR(ch, AT_NORMAL, NULL, pos);
! 882: else if (bs_mode == BS_SPECIAL)
! 883: overstrike = backc();
! 884:
! 885: return 0;
! 886: }
! 887:
! 888: if (overstrike > 0)
1.1 etheisen 889: {
890: /*
891: * Overstrike the character at the current position
892: * in the line buffer. This will cause either
893: * underline (if a "_" is overstruck),
894: * bold (if an identical character is overstruck),
895: * or just deletion of the character in the buffer.
896: */
1.10 ! shadchin 897: overstrike = utf_mode ? -1 : 0;
! 898: /* To be correct, this must be a base character. */
! 899: #if !SMALL
! 900: prev_ch = get_wchar(linebuf + curr);
! 901: #else
! 902: prev_ch = (LWCHAR)((char)(linebuf + curr)[0] & 0xFF);
! 903: #endif /* !SMALL */
! 904: a = attr[curr];
! 905: if (ch == prev_ch)
1.5 millert 906: {
907: /*
908: * Overstriking a char with itself means make it bold.
909: * But overstriking an underscore with itself is
910: * ambiguous. It could mean make it bold, or
911: * it could mean make it underlined.
912: * Use the previous overstrike to resolve it.
913: */
1.10 ! shadchin 914: if (ch == '_')
! 915: {
! 916: if ((a & (AT_BOLD|AT_UNDERLINE)) != AT_NORMAL)
! 917: a |= (AT_BOLD|AT_UNDERLINE);
! 918: else if (last_overstrike != AT_NORMAL)
! 919: a |= last_overstrike;
! 920: else
! 921: a |= AT_BOLD;
! 922: } else
! 923: a |= AT_BOLD;
! 924: } else if (ch == '_')
1.5 millert 925: {
1.10 ! shadchin 926: a |= AT_UNDERLINE;
! 927: ch = prev_ch;
! 928: rep = linebuf + curr;
! 929: } else if (prev_ch == '_')
1.5 millert 930: {
1.10 ! shadchin 931: a |= AT_UNDERLINE;
! 932: }
! 933: /* Else we replace prev_ch, but we keep its attributes. */
! 934: } else if (overstrike < 0)
! 935: {
! 936: #if !SMALL
! 937: if ( is_composing_char(ch)
! 938: || is_combining_char(get_wchar(linebuf + curr), ch))
! 939: /* Continuation of the same overstrike. */
! 940: a = last_overstrike;
1.1 etheisen 941: else
1.10 ! shadchin 942: #endif /* !SMALL */
! 943: overstrike = 0;
! 944: }
! 945:
! 946: if (ch == '\t')
1.1 etheisen 947: {
1.5 millert 948: /*
949: * Expand a tab into spaces.
950: */
1.1 etheisen 951: switch (bs_mode)
952: {
953: case BS_CONTROL:
954: goto do_control_char;
1.5 millert 955: case BS_NORMAL:
1.1 etheisen 956: case BS_SPECIAL:
1.10 ! shadchin 957: STORE_TAB(a, pos);
1.1 etheisen 958: break;
959: }
1.10 ! shadchin 960: } else if ((!utf_mode || is_ascii_char(ch)) && control_char((char)ch))
1.1 etheisen 961: {
962: do_control_char:
1.10 ! shadchin 963: if (ctldisp == OPT_ON || (ctldisp == OPT_ONPLUS && IS_CSI_START(ch)))
1.1 etheisen 964: {
965: /*
966: * Output as a normal character.
967: */
1.10 ! shadchin 968: STORE_CHAR(ch, AT_NORMAL, rep, pos);
1.1 etheisen 969: } else
970: {
1.10 ! shadchin 971: STORE_PRCHAR((char) ch, pos);
! 972: }
! 973: }
! 974: #if !SMALL
! 975: else if (utf_mode && ctldisp != OPT_ON && is_ubin_char(ch))
! 976: {
! 977: char *s;
! 978:
! 979: s = prutfchar(ch);
1.1 etheisen 980:
1.10 ! shadchin 981: if (column + (int) strlen(s) - 1 +
! 982: pwidth(' ', binattr, 0) + attr_ewidth(binattr) > sc_width)
! 983: return (1);
1.1 etheisen 984:
1.10 ! shadchin 985: for ( ; *s != 0; s++)
! 986: STORE_CHAR(*s, AT_BINARY, NULL, pos);
! 987: }
! 988: #endif /* !SMALL */
! 989: else
1.1 etheisen 990: {
1.10 ! shadchin 991: STORE_CHAR(ch, a, rep, pos);
1.1 etheisen 992: }
1.10 ! shadchin 993: return (0);
! 994: }
! 995:
! 996: /*
! 997: *
! 998: */
! 999: public int
! 1000: pflushmbc()
! 1001: {
! 1002: int r = 0;
1.1 etheisen 1003:
1.10 ! shadchin 1004: #if !SMALL
! 1005: if (mbc_buf_len > 0)
! 1006: {
! 1007: /* Flush incomplete (truncated) sequence. */
! 1008: r = flush_mbc_buf(mbc_pos);
! 1009: mbc_buf_len = 0;
! 1010: }
! 1011: #endif /* !SMALL */
! 1012: return r;
1.1 etheisen 1013: }
1014:
1015: /*
1016: * Terminate the line in the line buffer.
1017: */
1018: public void
1.10 ! shadchin 1019: pdone(endline, forw)
1.1 etheisen 1020: int endline;
1.10 ! shadchin 1021: int forw;
1.1 etheisen 1022: {
1.10 ! shadchin 1023: (void) pflushmbc();
! 1024:
1.1 etheisen 1025: if (pendc && (pendc != '\r' || !endline))
1026: /*
1027: * If we had a pending character, put it in the buffer.
1028: * But discard a pending CR if we are at end of line
1029: * (that is, discard the CR in a CR/LF sequence).
1030: */
1.10 ! shadchin 1031: (void) do_append(pendc, NULL, pendpos);
1.1 etheisen 1032:
1033: /*
1.5 millert 1034: * Make sure we've shifted the line, if we need to.
1035: */
1036: if (cshift < hshift)
1037: pshift(hshift - cshift);
1038:
1.10 ! shadchin 1039: if (ctldisp == OPT_ONPLUS && is_ansi_end('m'))
! 1040: {
! 1041: /* Switch to normal attribute at end of line. */
! 1042: char *p = "\033[m";
! 1043: for ( ; *p != '\0'; p++)
! 1044: {
! 1045: linebuf[curr] = *p;
! 1046: attr[curr++] = AT_ANSI;
! 1047: }
! 1048: }
! 1049:
1.5 millert 1050: /*
1.1 etheisen 1051: * Add a newline if necessary,
1052: * and append a '\0' to the end of the line.
1.10 ! shadchin 1053: * We output a newline if we're not at the right edge of the screen,
! 1054: * or if the terminal doesn't auto wrap,
! 1055: * or if this is really the end of the line AND the terminal ignores
! 1056: * a newline at the right edge.
! 1057: * (In the last case we don't want to output a newline if the terminal
! 1058: * doesn't ignore it since that would produce an extra blank line.
! 1059: * But we do want to output a newline if the terminal ignores it in case
! 1060: * the next line is blank. In that case the single newline output for
! 1061: * that blank line would be ignored!)
1.1 etheisen 1062: */
1.10 ! shadchin 1063: if (column < sc_width || !auto_wrap || (endline && ignaw) || ctldisp == OPT_ON)
1.1 etheisen 1064: {
1065: linebuf[curr] = '\n';
1066: attr[curr] = AT_NORMAL;
1067: curr++;
1.10 ! shadchin 1068: }
! 1069: else if (ignaw && column >= sc_width && forw)
! 1070: {
! 1071: /*
! 1072: * Terminals with "ignaw" don't wrap until they *really* need
! 1073: * to, i.e. when the character *after* the last one to fit on a
! 1074: * line is output. But they are too hard to deal with when they
! 1075: * get in the state where a full screen width of characters
! 1076: * have been output but the cursor is sitting on the right edge
! 1077: * instead of at the start of the next line.
! 1078: * So we nudge them into wrapping by outputting a space
! 1079: * character plus a backspace. But do this only if moving
! 1080: * forward; if we're moving backward and drawing this line at
! 1081: * the top of the screen, the space would overwrite the first
! 1082: * char on the next line. We don't need to do this "nudge"
! 1083: * at the top of the screen anyway.
! 1084: */
! 1085: linebuf[curr] = ' ';
! 1086: attr[curr++] = AT_NORMAL;
! 1087: linebuf[curr] = '\b';
! 1088: attr[curr++] = AT_NORMAL;
1.1 etheisen 1089: }
1090: linebuf[curr] = '\0';
1091: attr[curr] = AT_NORMAL;
1.10 ! shadchin 1092: }
1.5 millert 1093:
1.10 ! shadchin 1094: /*
! 1095: *
! 1096: */
! 1097: public void
! 1098: set_status_col(c)
! 1099: char c;
! 1100: {
! 1101: linebuf[0] = c;
! 1102: attr[0] = AT_NORMAL|AT_HILITE;
1.1 etheisen 1103: }
1104:
1105: /*
1106: * Get a character from the current line.
1107: * Return the character as the function return value,
1108: * and the character attribute in *ap.
1109: */
1110: public int
1111: gline(i, ap)
1.5 millert 1112: register int i;
1113: register int *ap;
1.1 etheisen 1114: {
1115: if (is_null_line)
1116: {
1117: /*
1118: * If there is no current line, we pretend the line is
1119: * either "~" or "", depending on the "twiddle" flag.
1120: */
1.10 ! shadchin 1121: if (twiddle)
! 1122: {
! 1123: if (i == 0)
! 1124: {
! 1125: *ap = AT_BOLD;
! 1126: return '~';
! 1127: }
! 1128: --i;
! 1129: }
! 1130: /* Make sure we're back to AT_NORMAL before the '\n'. */
! 1131: *ap = AT_NORMAL;
! 1132: return i ? '\0' : '\n';
1.1 etheisen 1133: }
1134:
1135: *ap = attr[i];
1.10 ! shadchin 1136: return (linebuf[i] & 0xFF);
1.1 etheisen 1137: }
1138:
1139: /*
1140: * Indicate that there is no current line.
1141: */
1142: public void
1143: null_line()
1144: {
1145: is_null_line = 1;
1.5 millert 1146: cshift = 0;
1.1 etheisen 1147: }
1148:
1149: /*
1150: * Analogous to forw_line(), but deals with "raw lines":
1151: * lines which are not split for screen width.
1152: * {{ This is supposed to be more efficient than forw_line(). }}
1153: */
1154: public POSITION
1.10 ! shadchin 1155: forw_raw_line(curr_pos, linep, line_lenp)
1.1 etheisen 1156: POSITION curr_pos;
1157: char **linep;
1.10 ! shadchin 1158: int *line_lenp;
1.1 etheisen 1159: {
1.5 millert 1160: register int n;
1161: register int c;
1.1 etheisen 1162: POSITION new_pos;
1163:
1164: if (curr_pos == NULL_POSITION || ch_seek(curr_pos) ||
1165: (c = ch_forw_get()) == EOI)
1166: return (NULL_POSITION);
1167:
1.5 millert 1168: n = 0;
1.1 etheisen 1169: for (;;)
1170: {
1.10 ! shadchin 1171: if (c == '\n' || c == EOI || ABORT_SIGS())
1.1 etheisen 1172: {
1173: new_pos = ch_tell();
1174: break;
1175: }
1.5 millert 1176: if (n >= size_linebuf-1)
1.1 etheisen 1177: {
1.5 millert 1178: if (expand_linebuf())
1179: {
1180: /*
1181: * Overflowed the input buffer.
1182: * Pretend the line ended here.
1183: */
1184: new_pos = ch_tell() - 1;
1185: break;
1186: }
1.1 etheisen 1187: }
1.5 millert 1188: linebuf[n++] = c;
1.1 etheisen 1189: c = ch_forw_get();
1190: }
1.5 millert 1191: linebuf[n] = '\0';
1.1 etheisen 1192: if (linep != NULL)
1193: *linep = linebuf;
1.10 ! shadchin 1194: if (line_lenp != NULL)
! 1195: *line_lenp = n;
1.1 etheisen 1196: return (new_pos);
1197: }
1198:
1199: /*
1200: * Analogous to back_line(), but deals with "raw lines".
1201: * {{ This is supposed to be more efficient than back_line(). }}
1202: */
1203: public POSITION
1.10 ! shadchin 1204: back_raw_line(curr_pos, linep, line_lenp)
1.1 etheisen 1205: POSITION curr_pos;
1206: char **linep;
1.10 ! shadchin 1207: int *line_lenp;
1.1 etheisen 1208: {
1.5 millert 1209: register int n;
1210: register int c;
1.1 etheisen 1211: POSITION new_pos;
1212:
1213: if (curr_pos == NULL_POSITION || curr_pos <= ch_zero() ||
1214: ch_seek(curr_pos-1))
1215: return (NULL_POSITION);
1216:
1.5 millert 1217: n = size_linebuf;
1218: linebuf[--n] = '\0';
1.1 etheisen 1219: for (;;)
1220: {
1221: c = ch_back_get();
1.10 ! shadchin 1222: if (c == '\n' || ABORT_SIGS())
1.1 etheisen 1223: {
1224: /*
1225: * This is the newline ending the previous line.
1226: * We have hit the beginning of the line.
1227: */
1228: new_pos = ch_tell() + 1;
1229: break;
1230: }
1231: if (c == EOI)
1232: {
1233: /*
1234: * We have hit the beginning of the file.
1235: * This must be the first line in the file.
1236: * This must, of course, be the beginning of the line.
1237: */
1238: new_pos = ch_zero();
1239: break;
1240: }
1.5 millert 1241: if (n <= 0)
1.1 etheisen 1242: {
1.5 millert 1243: int old_size_linebuf = size_linebuf;
1244: if (expand_linebuf())
1245: {
1246: /*
1247: * Overflowed the input buffer.
1248: * Pretend the line ended here.
1249: */
1250: new_pos = ch_tell() + 1;
1251: break;
1252: }
1.1 etheisen 1253: /*
1.5 millert 1254: * Shift the data to the end of the new linebuf.
1.1 etheisen 1255: */
1.5 millert 1256: n = size_linebuf - old_size_linebuf;
1.8 millert 1257: memmove(linebuf + n, linebuf, old_size_linebuf);
1.1 etheisen 1258: }
1.5 millert 1259: linebuf[--n] = c;
1.1 etheisen 1260: }
1261: if (linep != NULL)
1.5 millert 1262: *linep = &linebuf[n];
1.10 ! shadchin 1263: if (line_lenp != NULL)
! 1264: *line_lenp = size_linebuf - 1 - n;
1.1 etheisen 1265: return (new_pos);
1266: }