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