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