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