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