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