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