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