xref: /dragonfly/contrib/less/line.c (revision ec1c3f3a)
1 /*
2  * Copyright (C) 1984-2022  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  * Routines to manipulate the "line buffer".
12  * The line buffer holds a line of output as it is being built
13  * in preparation for output to the screen.
14  */
15 
16 #include "less.h"
17 #include "charset.h"
18 #include "position.h"
19 
20 #if MSDOS_COMPILER==WIN32C
21 #define WIN32_LEAN_AND_MEAN
22 #include <windows.h>
23 #endif
24 
25 #define MAX_PFX_WIDTH (MAX_LINENUM_WIDTH + MAX_STATUSCOL_WIDTH + 1)
26 static struct {
27 	char *buf;    /* Buffer which holds the current output line */
28 	int *attr;   /* Parallel to buf, to hold attributes */
29 	int print;    /* Index in buf of first printable char */
30 	int end;      /* Number of chars in buf */
31 	char pfx[MAX_PFX_WIDTH]; /* Holds status column and line number */
32 	int pfx_attr[MAX_PFX_WIDTH];
33 	int pfx_end;  /* Number of chars in pfx */
34 } linebuf;
35 
36 /*
37  * Buffer of ansi sequences which have been shifted off the left edge
38  * of the screen.
39  */
40 struct xbuffer shifted_ansi;
41 
42 /*
43  * Ring buffer of last ansi sequences sent.
44  * While sending a line, these will be resent at the end
45  * of any highlighted string, to restore text modes.
46  * {{ Not ideal, since we don't really know how many to resend. }}
47  */
48 #define NUM_LAST_ANSIS 3
49 static struct xbuffer last_ansi;
50 static struct xbuffer last_ansis[NUM_LAST_ANSIS];
51 static int curr_last_ansi;
52 
53 public int size_linebuf = 0; /* Size of line buffer (and attr buffer) */
54 static struct ansi_state *line_ansi = NULL;
55 static int ansi_in_line;
56 static int hlink_in_line;
57 static int line_mark_attr;
58 static int cshift;   /* Current left-shift of output line buffer */
59 public int hshift;   /* Desired left-shift of output line buffer */
60 public int tabstops[TABSTOP_MAX] = { 0 }; /* Custom tabstops */
61 public int ntabstops = 1;        /* Number of tabstops */
62 public int tabdefault = 8;       /* Default repeated tabstops */
63 public POSITION highest_hilite;  /* Pos of last hilite in file found so far */
64 
65 static int end_column;  /* Printable length, accounting for backspaces, etc. */
66 static int right_curr;
67 static int right_column;
68 static int overstrike;  /* Next char should overstrike previous char */
69 static int last_overstrike = AT_NORMAL;
70 static int is_null_line;  /* There is no current line */
71 static LWCHAR pendc;
72 static POSITION pendpos;
73 static char *end_ansi_chars;
74 static char *mid_ansi_chars;
75 static int in_hilite;
76 
77 static int attr_swidth LESSPARAMS ((int a));
78 static int attr_ewidth LESSPARAMS ((int a));
79 static int do_append LESSPARAMS ((LWCHAR ch, char *rep, POSITION pos));
80 
81 extern int sigs;
82 extern int bs_mode;
83 extern int linenums;
84 extern int ctldisp;
85 extern int twiddle;
86 extern int binattr;
87 extern int status_col;
88 extern int status_col_width;
89 extern int linenum_width;
90 extern int auto_wrap, ignaw;
91 extern int bo_s_width, bo_e_width;
92 extern int ul_s_width, ul_e_width;
93 extern int bl_s_width, bl_e_width;
94 extern int so_s_width, so_e_width;
95 extern int sc_width, sc_height;
96 extern int utf_mode;
97 extern POSITION start_attnpos;
98 extern POSITION end_attnpos;
99 extern char rscroll_char;
100 extern int rscroll_attr;
101 extern int use_color;
102 extern int status_line;
103 
104 static char mbc_buf[MAX_UTF_CHAR_LEN];
105 static int mbc_buf_len = 0;
106 static int mbc_buf_index = 0;
107 static POSITION mbc_pos;
108 
109 /* Configurable color map */
110 static char color_map[AT_NUM_COLORS][12] = {
111 	"Wm",  /* AT_COLOR_ATTN */
112 	"kR",  /* AT_COLOR_BIN */
113 	"kR",  /* AT_COLOR_CTRL */
114 	"kY",  /* AT_COLOR_ERROR */
115 	"c",   /* AT_COLOR_LINENUM */
116 	"Wb",  /* AT_COLOR_MARK */
117 	"kC",  /* AT_COLOR_PROMPT */
118 	"kc",  /* AT_COLOR_RSCROLL */
119 	"kG",  /* AT_COLOR_SEARCH */
120 	"",    /* AT_COLOR_HEADER */
121 	"",    /* AT_UNDERLINE */
122 	"",    /* AT_BOLD */
123 	"",    /* AT_BLINK */
124 	"",    /* AT_STANDOUT */
125 };
126 
127 /* State while processing an ANSI escape sequence */
128 struct ansi_state {
129 	int hindex;   /* Index into hyperlink prefix */
130 	int hlink;    /* Processing hyperlink address? */
131 	int prev_esc; /* Prev char was ESC (to detect ESC-\ seq) */
132 };
133 
134 /*
135  * Initialize from environment variables.
136  */
137 	public void
138 init_line(VOID_PARAM)
139 {
140 	int ax;
141 
142 	end_ansi_chars = lgetenv("LESSANSIENDCHARS");
143 	if (isnullenv(end_ansi_chars))
144 		end_ansi_chars = "m";
145 
146 	mid_ansi_chars = lgetenv("LESSANSIMIDCHARS");
147 	if (isnullenv(mid_ansi_chars))
148 		mid_ansi_chars = "0123456789:;[?!\"'#%()*+ ";
149 
150 	linebuf.buf = (char *) ecalloc(LINEBUF_SIZE, sizeof(char));
151 	linebuf.attr = (int *) ecalloc(LINEBUF_SIZE, sizeof(int));
152 	size_linebuf = LINEBUF_SIZE;
153 	xbuf_init(&shifted_ansi);
154 	xbuf_init(&last_ansi);
155 	for (ax = 0;  ax < NUM_LAST_ANSIS;  ax++)
156 		xbuf_init(&last_ansis[ax]);
157 	curr_last_ansi = 0;
158 }
159 
160 /*
161  * Expand the line buffer.
162  */
163 	static int
164 expand_linebuf(VOID_PARAM)
165 {
166 	/* Double the size of the line buffer. */
167 	int new_size = size_linebuf * 2;
168 	char *new_buf = (char *) calloc(new_size, sizeof(char));
169 	int *new_attr = (int *) calloc(new_size, sizeof(int));
170 	if (new_buf == NULL || new_attr == NULL)
171 	{
172 		if (new_attr != NULL)
173 			free(new_attr);
174 		if (new_buf != NULL)
175 			free(new_buf);
176 		return 1;
177 	}
178 	/*
179 	 * We just calloc'd the buffers; copy the old contents.
180 	 */
181 	memcpy(new_buf, linebuf.buf, size_linebuf * sizeof(char));
182 	memcpy(new_attr, linebuf.attr, size_linebuf * sizeof(int));
183 	free(linebuf.attr);
184 	free(linebuf.buf);
185 	linebuf.buf = new_buf;
186 	linebuf.attr = new_attr;
187 	size_linebuf = new_size;
188 	return 0;
189 }
190 
191 /*
192  * Is a character ASCII?
193  */
194 	public int
195 is_ascii_char(ch)
196 	LWCHAR ch;
197 {
198 	return (ch <= 0x7F);
199 }
200 
201 /*
202  */
203 	static void
204 inc_end_column(w)
205 	int w;
206 {
207 	if (end_column > right_column && w > 0)
208 	{
209 		right_column = end_column;
210 		right_curr = linebuf.end;
211 	}
212 	end_column += w;
213 }
214 
215 /*
216  * Rewind the line buffer.
217  */
218 	public void
219 prewind(VOID_PARAM)
220 {
221 	int ax;
222 
223 	linebuf.print = 6; /* big enough for longest UTF-8 sequence */
224 	linebuf.pfx_end = 0;
225 	for (linebuf.end = 0; linebuf.end < linebuf.print; linebuf.end++)
226 	{
227 		linebuf.buf[linebuf.end] = '\0';
228 		linebuf.attr[linebuf.end] = 0;
229 	}
230 
231 	end_column = 0;
232 	right_curr = 0;
233 	right_column = 0;
234 	cshift = 0;
235 	overstrike = 0;
236 	last_overstrike = AT_NORMAL;
237 	mbc_buf_len = 0;
238 	is_null_line = 0;
239 	pendc = '\0';
240 	in_hilite = 0;
241 	ansi_in_line = 0;
242 	hlink_in_line = 0;
243 	line_mark_attr = 0;
244 	xbuf_reset(&shifted_ansi);
245 	xbuf_reset(&last_ansi);
246 	for (ax = 0;  ax < NUM_LAST_ANSIS;  ax++)
247 		xbuf_reset(&last_ansis[ax]);
248 	curr_last_ansi = 0;
249 }
250 
251 /*
252  * Set a character in the line buffer.
253  */
254 	static void
255 set_linebuf(n, ch, attr)
256 	int n;
257 	char ch;
258 	int attr;
259 {
260 	linebuf.buf[n] = ch;
261 	linebuf.attr[n] = attr;
262 }
263 
264 /*
265  * Append a character to the line buffer.
266  */
267 	static void
268 add_linebuf(ch, attr, w)
269 	char ch;
270 	int attr;
271 	int w;
272 {
273 	set_linebuf(linebuf.end++, ch, attr);
274 	inc_end_column(w);
275 }
276 
277 /*
278  * Append a string to the line buffer.
279  */
280 	static void
281 addstr_linebuf(s, attr, cw)
282 	char *s;
283 	int attr;
284 	int cw;
285 {
286 	for ( ;  *s != '\0';  s++)
287 		add_linebuf(*s, attr, cw);
288 }
289 
290 /*
291  * Set a character in the line prefix buffer.
292  */
293 	static void
294 set_pfx(n, ch, attr)
295 	int n;
296 	char ch;
297 	int attr;
298 {
299 	linebuf.pfx[n] = ch;
300 	linebuf.pfx_attr[n] = attr;
301 }
302 
303 /*
304  * Append a character to the line prefix buffer.
305  */
306 	static void
307 add_pfx(ch, attr)
308 	char ch;
309 	int attr;
310 {
311 	set_pfx(linebuf.pfx_end++, ch, attr);
312 }
313 
314 /*
315  * Insert the status column and line number into the line buffer.
316  */
317 	public void
318 plinestart(pos)
319 	POSITION pos;
320 {
321 	LINENUM linenum = 0;
322 	int i;
323 
324 	if (linenums == OPT_ONPLUS)
325 	{
326 		/*
327 		 * Get the line number and put it in the current line.
328 		 * {{ Note: since find_linenum calls forw_raw_line,
329 		 *    it may seek in the input file, requiring the caller
330 		 *    of plinestart to re-seek if necessary. }}
331 		 * {{ Since forw_raw_line modifies linebuf, we must
332 		 *    do this first, before storing anything in linebuf. }}
333 		 */
334 		linenum = find_linenum(pos);
335 	}
336 
337 	/*
338 	 * Display a status column if the -J option is set.
339 	 */
340 	if (status_col || status_line)
341 	{
342 		char c = posmark(pos);
343 		if (c != 0)
344 			line_mark_attr = AT_HILITE|AT_COLOR_MARK;
345 		else if (start_attnpos != NULL_POSITION &&
346 		         pos >= start_attnpos && pos <= end_attnpos)
347 			line_mark_attr = AT_HILITE|AT_COLOR_ATTN;
348 		if (status_col)
349 		{
350 			add_pfx(c ? c : ' ', line_mark_attr); /* column 0: status */
351 			while (linebuf.pfx_end < status_col_width)
352 				add_pfx(' ', AT_NORMAL);
353 		}
354 	}
355 
356 	/*
357 	 * Display the line number at the start of each line
358 	 * if the -N option is set.
359 	 */
360 	if (linenums == OPT_ONPLUS)
361 	{
362 		char buf[INT_STRLEN_BOUND(linenum) + 2];
363 		int len;
364 
365 		linenum = vlinenum(linenum);
366 		if (linenum == 0)
367 			len = 0;
368 		else
369 		{
370 			linenumtoa(linenum, buf);
371 			len = (int) strlen(buf);
372 		}
373 		for (i = 0; i < linenum_width - len; i++)
374 			add_pfx(' ', AT_NORMAL);
375 		for (i = 0; i < len; i++)
376 			add_pfx(buf[i], AT_BOLD|AT_COLOR_LINENUM);
377 		add_pfx(' ', AT_NORMAL);
378 	}
379 	end_column = linebuf.pfx_end;
380 }
381 
382 /*
383  * Return the width of the line prefix (status column and line number).
384  * {{ Actual line number can be wider than linenum_width. }}
385  */
386 	public int
387 line_pfx_width(VOID_PARAM)
388 {
389 	int width = 0;
390 	if (status_col)
391 		width += status_col_width;
392 	if (linenums == OPT_ONPLUS)
393 		width += linenum_width + 1;
394 	return width;
395 }
396 
397 /*
398  * Shift line left so that the last char is just to the left
399  * of the first visible column.
400  */
401 	public void
402 pshift_all(VOID_PARAM)
403 {
404 	int i;
405 	for (i = linebuf.print;  i < linebuf.end;  i++)
406 		if (linebuf.attr[i] == AT_ANSI)
407 			xbuf_add(&shifted_ansi, linebuf.buf[i]);
408 	linebuf.end = linebuf.print;
409 	end_column = linebuf.pfx_end;
410 }
411 
412 /*
413  * Return the printing width of the start (enter) sequence
414  * for a given character attribute.
415  */
416 	static int
417 attr_swidth(a)
418 	int a;
419 {
420 	int w = 0;
421 
422 	a = apply_at_specials(a);
423 
424 	if (a & AT_UNDERLINE)
425 		w += ul_s_width;
426 	if (a & AT_BOLD)
427 		w += bo_s_width;
428 	if (a & AT_BLINK)
429 		w += bl_s_width;
430 	if (a & AT_STANDOUT)
431 		w += so_s_width;
432 
433 	return w;
434 }
435 
436 /*
437  * Return the printing width of the end (exit) sequence
438  * for a given character attribute.
439  */
440 	static int
441 attr_ewidth(a)
442 	int a;
443 {
444 	int w = 0;
445 
446 	a = apply_at_specials(a);
447 
448 	if (a & AT_UNDERLINE)
449 		w += ul_e_width;
450 	if (a & AT_BOLD)
451 		w += bo_e_width;
452 	if (a & AT_BLINK)
453 		w += bl_e_width;
454 	if (a & AT_STANDOUT)
455 		w += so_e_width;
456 
457 	return w;
458 }
459 
460 /*
461  * Return the printing width of a given character and attribute,
462  * if the character were added after prev_ch.
463  * Adding a character with a given attribute may cause an enter or exit
464  * attribute sequence to be inserted, so this must be taken into account.
465  */
466 	public int
467 pwidth(ch, a, prev_ch, prev_a)
468 	LWCHAR ch;
469 	int a;
470 	LWCHAR prev_ch;
471 	int prev_a;
472 {
473 	int w;
474 
475 	if (ch == '\b')
476 	{
477 		/*
478 		 * Backspace moves backwards one or two positions.
479 		 */
480 		if (prev_a & (AT_ANSI|AT_BINARY))
481 			return strlen(prchar('\b'));
482 		return (utf_mode && is_wide_char(prev_ch)) ? -2 : -1;
483 	}
484 
485 	if (!utf_mode || is_ascii_char(ch))
486 	{
487 		if (control_char((char)ch))
488 		{
489 			/*
490 			 * Control characters do unpredictable things,
491 			 * so we don't even try to guess; say it doesn't move.
492 			 * This can only happen if the -r flag is in effect.
493 			 */
494 			return (0);
495 		}
496 	} else
497 	{
498 		if (is_composing_char(ch) || is_combining_char(prev_ch, ch))
499 		{
500 			/*
501 			 * Composing and combining chars take up no space.
502 			 *
503 			 * Some terminals, upon failure to compose a
504 			 * composing character with the character(s) that
505 			 * precede(s) it will actually take up one end_column
506 			 * for the composing character; there isn't much
507 			 * we could do short of testing the (complex)
508 			 * composition process ourselves and printing
509 			 * a binary representation when it fails.
510 			 */
511 			return (0);
512 		}
513 	}
514 
515 	/*
516 	 * Other characters take one or two columns,
517 	 * plus the width of any attribute enter/exit sequence.
518 	 */
519 	w = 1;
520 	if (is_wide_char(ch))
521 		w++;
522 	if (linebuf.end > 0 && !is_at_equiv(linebuf.attr[linebuf.end-1], a))
523 		w += attr_ewidth(linebuf.attr[linebuf.end-1]);
524 	if (apply_at_specials(a) != AT_NORMAL &&
525 	    (linebuf.end == 0 || !is_at_equiv(linebuf.attr[linebuf.end-1], a)))
526 		w += attr_swidth(a);
527 	return (w);
528 }
529 
530 /*
531  * Delete to the previous base character in the line buffer.
532  */
533 	static int
534 backc(VOID_PARAM)
535 {
536 	LWCHAR ch;
537 	char *p;
538 
539 	if (linebuf.end == 0)
540 		return (0);
541 	p = &linebuf.buf[linebuf.end];
542 	ch = step_char(&p, -1, linebuf.buf);
543 	/* Skip back to the next nonzero-width char. */
544 	while (p > linebuf.buf)
545 	{
546 		LWCHAR prev_ch;
547 		int width;
548 		linebuf.end = (int) (p - linebuf.buf);
549 		prev_ch = step_char(&p, -1, linebuf.buf);
550 		width = pwidth(ch, linebuf.attr[linebuf.end], prev_ch, linebuf.attr[linebuf.end-1]);
551 		end_column -= width;
552 		/* {{ right_column? }} */
553 		if (width > 0)
554 			break;
555 		ch = prev_ch;
556 	}
557 	return (1);
558 }
559 
560 /*
561  * Is a character the end of an ANSI escape sequence?
562  */
563 	public int
564 is_ansi_end(ch)
565 	LWCHAR ch;
566 {
567 	if (!is_ascii_char(ch))
568 		return (0);
569 	return (strchr(end_ansi_chars, (char) ch) != NULL);
570 }
571 
572 /*
573  * Can a char appear in an ANSI escape sequence, before the end char?
574  */
575 	public int
576 is_ansi_middle(ch)
577 	LWCHAR ch;
578 {
579 	if (!is_ascii_char(ch))
580 		return (0);
581 	if (is_ansi_end(ch))
582 		return (0);
583 	return (strchr(mid_ansi_chars, (char) ch) != NULL);
584 }
585 
586 /*
587  * Skip past an ANSI escape sequence.
588  * pp is initially positioned just after the CSI_START char.
589  */
590 	public void
591 skip_ansi(pansi, pp, limit)
592 	struct ansi_state *pansi;
593 	char **pp;
594 	constant char *limit;
595 {
596 	LWCHAR c;
597 	do {
598 		c = step_char(pp, +1, limit);
599 	} while (*pp < limit && ansi_step(pansi, c) == ANSI_MID);
600 	/* Note that we discard final char, for which is_ansi_end is true. */
601 }
602 
603 /*
604  * Determine if a character starts an ANSI escape sequence.
605  * If so, return an ansi_state struct; otherwise return NULL.
606  */
607 	public struct ansi_state *
608 ansi_start(ch)
609 	LWCHAR ch;
610 {
611 	struct ansi_state *pansi;
612 
613 	if (!IS_CSI_START(ch))
614 		return NULL;
615 	pansi = ecalloc(1, sizeof(struct ansi_state));
616 	pansi->hindex = 0;
617 	pansi->hlink = 0;
618 	pansi->prev_esc = 0;
619 	return pansi;
620 }
621 
622 /*
623  * Determine whether the next char in an ANSI escape sequence
624  * ends the sequence.
625  */
626 	public int
627 ansi_step(pansi, ch)
628 	struct ansi_state *pansi;
629 	LWCHAR ch;
630 {
631 	if (pansi->hlink)
632 	{
633 		/* Hyperlink ends with \7 or ESC-backslash. */
634 		if (ch == '\7')
635 			return ANSI_END;
636 		if (pansi->prev_esc && ch == '\\')
637 			return ANSI_END;
638 		pansi->prev_esc = (ch == ESC);
639 		return ANSI_MID;
640 	}
641 	if (pansi->hindex >= 0)
642 	{
643 		static char hlink_prefix[] = ESCS "]8;";
644 		if (ch == hlink_prefix[pansi->hindex] ||
645 		    (pansi->hindex == 0 && IS_CSI_START(ch)))
646 		{
647 			pansi->hindex++;
648 			if (hlink_prefix[pansi->hindex] == '\0')
649 				pansi->hlink = 1; /* now processing hyperlink addr */
650 			return ANSI_MID;
651 		}
652 		pansi->hindex = -1; /* not a hyperlink */
653 	}
654 	/* Check for SGR sequences */
655 	if (is_ansi_middle(ch))
656 		return ANSI_MID;
657 	if (is_ansi_end(ch))
658 		return ANSI_END;
659 	return ANSI_ERR;
660 }
661 
662 /*
663  * Free an ansi_state structure.
664  */
665 	public void
666 ansi_done(pansi)
667 	struct ansi_state *pansi;
668 {
669 	free(pansi);
670 }
671 
672 /*
673  * Will w characters in attribute a fit on the screen?
674  */
675 	static int
676 fits_on_screen(w, a)
677 	int w;
678 	int a;
679 {
680 	if (ctldisp == OPT_ON)
681 		/* We're not counting, so say that everything fits. */
682 		return 1;
683 	return (end_column - cshift + w + attr_ewidth(a) <= sc_width);
684 }
685 
686 /*
687  * Append a character and attribute to the line buffer.
688  */
689 #define STORE_CHAR(ch,a,rep,pos) \
690 	do { \
691 		if (store_char((ch),(a),(rep),(pos))) return (1); \
692 	} while (0)
693 
694 	static int
695 store_char(ch, a, rep, pos)
696 	LWCHAR ch;
697 	int a;
698 	char *rep;
699 	POSITION pos;
700 {
701 	int w;
702 	int i;
703 	int replen;
704 	char cs;
705 
706 	i = (a & (AT_UNDERLINE|AT_BOLD));
707 	if (i != AT_NORMAL)
708 		last_overstrike = i;
709 
710 #if HILITE_SEARCH
711 	{
712 		int matches;
713 		int resend_last = 0;
714 		int hl_attr;
715 
716 		if (pos == NULL_POSITION)
717 		{
718 			/* Color the prompt unless it has ansi sequences in it. */
719 			hl_attr = ansi_in_line ? 0 : AT_STANDOUT|AT_COLOR_PROMPT;
720 		} else
721 		{
722 			hl_attr = is_hilited_attr(pos, pos+1, 0, &matches);
723 			if (hl_attr == 0 && status_line)
724 				hl_attr = line_mark_attr;
725 		}
726 		if (hl_attr)
727 		{
728 			/*
729 			 * This character should be highlighted.
730 			 * Override the attribute passed in.
731 			 */
732 			if (a != AT_ANSI)
733 			{
734 				if (highest_hilite != NULL_POSITION && pos != NULL_POSITION && pos > highest_hilite)
735 					highest_hilite = pos;
736 				a |= hl_attr;
737 			}
738 			in_hilite = 1;
739 		} else
740 		{
741 			if (in_hilite)
742 			{
743 				/*
744 				 * This is the first non-hilited char after a hilite.
745 				 * Resend the last ANSI seq to restore color.
746 				 */
747 				resend_last = 1;
748 			}
749 			in_hilite = 0;
750 		}
751 		if (resend_last)
752 		{
753 			int ai;
754 			for (ai = 0;  ai < NUM_LAST_ANSIS;  ai++)
755 			{
756 				int ax = (curr_last_ansi + ai) % NUM_LAST_ANSIS;
757 				for (i = 0;  i < last_ansis[ax].end;  i++)
758 					STORE_CHAR(last_ansis[ax].data[i], AT_ANSI, NULL, pos);
759 			}
760 		}
761 	}
762 #endif
763 
764 	if (a == AT_ANSI) {
765 		w = 0;
766 	} else {
767 		char *p = &linebuf.buf[linebuf.end];
768 		LWCHAR prev_ch = (linebuf.end > 0) ? step_char(&p, -1, linebuf.buf) : 0;
769 		int prev_a = (linebuf.end > 0) ? linebuf.attr[linebuf.end-1] : 0;
770 		w = pwidth(ch, a, prev_ch, prev_a);
771 	}
772 
773 	if (!fits_on_screen(w, a))
774 		return (1);
775 
776 	if (rep == NULL)
777 	{
778 		cs = (char) ch;
779 		rep = &cs;
780 		replen = 1;
781 	} else
782 	{
783 		replen = utf_len(rep[0]);
784 	}
785 	if (linebuf.end + replen >= size_linebuf-6)
786 	{
787 		/*
788 		 * Won't fit in line buffer.
789 		 * Try to expand it.
790 		 */
791 		if (expand_linebuf())
792 			return (1);
793 	}
794 
795 	if (cshift == hshift && shifted_ansi.end > 0)
796 	{
797 		/* Copy shifted ANSI sequences to beginning of line. */
798 		for (i = 0;  i < shifted_ansi.end;  i++)
799 			add_linebuf(shifted_ansi.data[i], AT_ANSI, 0);
800 		xbuf_reset(&shifted_ansi);
801 	}
802 	/* Add the char to the buf, even if we will left-shift it next. */
803 	inc_end_column(w);
804 	for (i = 0;  i < replen;  i++)
805 		add_linebuf(*rep++, a, 0);
806 
807 	if (cshift < hshift)
808 	{
809 		/* We haven't left-shifted enough yet. */
810 		if (a == AT_ANSI)
811 			xbuf_add(&shifted_ansi, ch); /* Save ANSI attributes */
812 		if (linebuf.end > linebuf.print)
813 		{
814 			/* Shift left enough to put last byte of this char at print-1. */
815 			int i;
816 			for (i = 0; i < linebuf.print; i++)
817 			{
818 				linebuf.buf[i] = linebuf.buf[i+replen];
819 				linebuf.attr[i] = linebuf.attr[i+replen];
820 			}
821 			linebuf.end -= replen;
822 			cshift += w;
823 			/*
824 			 * If the char we just left-shifted was double width,
825 			 * the 2 spaces we shifted may be too much.
826 			 * Represent the "half char" at start of line with a highlighted space.
827 			 */
828 			while (cshift > hshift)
829 			{
830 				add_linebuf(' ', rscroll_attr, 0);
831 				cshift--;
832 			}
833 		}
834 	}
835 	return (0);
836 }
837 
838 #define STORE_STRING(s,a,pos) \
839 	do { if (store_string((s),(a),(pos))) return (1); } while (0)
840 
841 	static int
842 store_string(s, a, pos)
843 	char *s;
844 	int a;
845 	POSITION pos;
846 {
847 	if (!fits_on_screen(strlen(s), a))
848 		return 1;
849 	for ( ;  *s != 0;  s++)
850 		STORE_CHAR(*s, a, NULL, pos);
851 	return 0;
852 }
853 
854 /*
855  * Append a tab to the line buffer.
856  * Store spaces to represent the tab.
857  */
858 #define STORE_TAB(a,pos) \
859 	do { if (store_tab((a),(pos))) return (1); } while (0)
860 
861 	static int
862 store_tab(attr, pos)
863 	int attr;
864 	POSITION pos;
865 {
866 	int to_tab = end_column - linebuf.pfx_end;
867 
868 	if (ntabstops < 2 || to_tab >= tabstops[ntabstops-1])
869 		to_tab = tabdefault -
870 		     ((to_tab - tabstops[ntabstops-1]) % tabdefault);
871 	else
872 	{
873 		int i;
874 		for (i = ntabstops - 2;  i >= 0;  i--)
875 			if (to_tab >= tabstops[i])
876 				break;
877 		to_tab = tabstops[i+1] - to_tab;
878 	}
879 
880 	do {
881 		STORE_CHAR(' ', attr, " ", pos);
882 	} while (--to_tab > 0);
883 	return 0;
884 }
885 
886 #define STORE_PRCHAR(c, pos) \
887 	do { if (store_prchar((c), (pos))) return 1; } while (0)
888 
889 	static int
890 store_prchar(c, pos)
891 	LWCHAR c;
892 	POSITION pos;
893 {
894 	/*
895 	 * Convert to printable representation.
896 	 */
897 	STORE_STRING(prchar(c), AT_BINARY|AT_COLOR_CTRL, pos);
898 	return 0;
899 }
900 
901 	static int
902 flush_mbc_buf(pos)
903 	POSITION pos;
904 {
905 	int i;
906 
907 	for (i = 0; i < mbc_buf_index; i++)
908 		if (store_prchar(mbc_buf[i], pos))
909 			return mbc_buf_index - i;
910 	return 0;
911 }
912 
913 /*
914  * Append a character to the line buffer.
915  * Expand tabs into spaces, handle underlining, boldfacing, etc.
916  * Returns 0 if ok, 1 if couldn't fit in buffer.
917  */
918 	public int
919 pappend(c, pos)
920 	int c;
921 	POSITION pos;
922 {
923 	int r;
924 
925 	if (pendc)
926 	{
927 		if (c == '\r' && pendc == '\r')
928 			return (0);
929 		if (do_append(pendc, NULL, pendpos))
930 			/*
931 			 * Oops.  We've probably lost the char which
932 			 * was in pendc, since caller won't back up.
933 			 */
934 			return (1);
935 		pendc = '\0';
936 	}
937 
938 	if (c == '\r' && bs_mode == BS_SPECIAL)
939 	{
940 		if (mbc_buf_len > 0)  /* utf_mode must be on. */
941 		{
942 			/* Flush incomplete (truncated) sequence. */
943 			r = flush_mbc_buf(mbc_pos);
944 			mbc_buf_index = r + 1;
945 			mbc_buf_len = 0;
946 			if (r)
947 				return (mbc_buf_index);
948 		}
949 
950 		/*
951 		 * Don't put the CR into the buffer until we see
952 		 * the next char.  If the next char is a newline,
953 		 * discard the CR.
954 		 */
955 		pendc = c;
956 		pendpos = pos;
957 		return (0);
958 	}
959 
960 	if (!utf_mode)
961 	{
962 		r = do_append(c, NULL, pos);
963 	} else
964 	{
965 		/* Perform strict validation in all possible cases. */
966 		if (mbc_buf_len == 0)
967 		{
968 		retry:
969 			mbc_buf_index = 1;
970 			*mbc_buf = c;
971 			if (IS_ASCII_OCTET(c))
972 				r = do_append(c, NULL, pos);
973 			else if (IS_UTF8_LEAD(c))
974 			{
975 				mbc_buf_len = utf_len(c);
976 				mbc_pos = pos;
977 				return (0);
978 			} else
979 				/* UTF8_INVALID or stray UTF8_TRAIL */
980 				r = flush_mbc_buf(pos);
981 		} else if (IS_UTF8_TRAIL(c))
982 		{
983 			mbc_buf[mbc_buf_index++] = c;
984 			if (mbc_buf_index < mbc_buf_len)
985 				return (0);
986 			if (is_utf8_well_formed(mbc_buf, mbc_buf_index))
987 				r = do_append(get_wchar(mbc_buf), mbc_buf, mbc_pos);
988 			else
989 				/* Complete, but not shortest form, sequence. */
990 				mbc_buf_index = r = flush_mbc_buf(mbc_pos);
991 			mbc_buf_len = 0;
992 		} else
993 		{
994 			/* Flush incomplete (truncated) sequence.  */
995 			r = flush_mbc_buf(mbc_pos);
996 			mbc_buf_index = r + 1;
997 			mbc_buf_len = 0;
998 			/* Handle new char.  */
999 			if (!r)
1000 				goto retry;
1001 		}
1002 	}
1003 	if (r)
1004 	{
1005 		/* How many chars should caller back up? */
1006 		r = (!utf_mode) ? 1 : mbc_buf_index;
1007 	}
1008 	return (r);
1009 }
1010 
1011 	static int
1012 store_control_char(ch, rep, pos)
1013 	LWCHAR ch;
1014 	char *rep;
1015 	POSITION pos;
1016 {
1017 	if (ctldisp == OPT_ON)
1018 	{
1019 		/* Output the character itself. */
1020 		STORE_CHAR(ch, AT_NORMAL, rep, pos);
1021 	} else
1022 	{
1023 		/* Output a printable representation of the character. */
1024 		STORE_PRCHAR((char) ch, pos);
1025 	}
1026 	return (0);
1027 }
1028 
1029 	static int
1030 store_ansi(ch, rep, pos)
1031 	LWCHAR ch;
1032 	char *rep;
1033 	POSITION pos;
1034 {
1035 	switch (ansi_step(line_ansi, ch))
1036 	{
1037 	case ANSI_MID:
1038 		STORE_CHAR(ch, AT_ANSI, rep, pos);
1039 		if (line_ansi->hlink)
1040 			hlink_in_line = 1;
1041 		xbuf_add(&last_ansi, ch);
1042 		break;
1043 	case ANSI_END:
1044 		STORE_CHAR(ch, AT_ANSI, rep, pos);
1045 		ansi_done(line_ansi);
1046 		line_ansi = NULL;
1047 		xbuf_add(&last_ansi, ch);
1048 		xbuf_set(&last_ansis[curr_last_ansi], &last_ansi);
1049 		xbuf_reset(&last_ansi);
1050 		curr_last_ansi = (curr_last_ansi + 1) % NUM_LAST_ANSIS;
1051 		break;
1052 	case ANSI_ERR:
1053 		{
1054 			/* Remove whole unrecognized sequence.  */
1055 			char *start = (cshift < hshift) ? shifted_ansi.data : linebuf.buf;
1056 			int *end = (cshift < hshift) ? &shifted_ansi.end : &linebuf.end;
1057 			char *p = start + *end;
1058 			LWCHAR bch;
1059 			do {
1060 				bch = step_char(&p, -1, start);
1061 			} while (p > start && !IS_CSI_START(bch));
1062 			*end = (int) (p - start);
1063 		}
1064 		xbuf_reset(&last_ansi);
1065 		ansi_done(line_ansi);
1066 		line_ansi = NULL;
1067 		break;
1068 	}
1069 	return (0);
1070 }
1071 
1072 	static int
1073 store_bs(ch, rep, pos)
1074 	LWCHAR ch;
1075 	char *rep;
1076 	POSITION pos;
1077 {
1078 	if (bs_mode == BS_CONTROL)
1079 		return store_control_char(ch, rep, pos);
1080 	if (linebuf.end > 0 &&
1081 		((linebuf.end <= linebuf.print && linebuf.buf[linebuf.end-1] == '\0') ||
1082 	     (linebuf.end > 0 && linebuf.attr[linebuf.end - 1] & (AT_ANSI|AT_BINARY))))
1083 		STORE_PRCHAR('\b', pos);
1084 	else if (bs_mode == BS_NORMAL)
1085 		STORE_CHAR(ch, AT_NORMAL, NULL, pos);
1086 	else if (bs_mode == BS_SPECIAL)
1087 		overstrike = backc();
1088 	return 0;
1089 }
1090 
1091 	static int
1092 do_append(ch, rep, pos)
1093 	LWCHAR ch;
1094 	char *rep;
1095 	POSITION pos;
1096 {
1097 	int a = AT_NORMAL;
1098 
1099 	if (ctldisp == OPT_ONPLUS && line_ansi == NULL)
1100 	{
1101 		line_ansi = ansi_start(ch);
1102 		if (line_ansi != NULL)
1103 			ansi_in_line = 1;
1104 	}
1105 
1106 	if (line_ansi != NULL)
1107 		return store_ansi(ch, rep, pos);
1108 
1109 	if (ch == '\b')
1110 		return store_bs(ch, rep, pos);
1111 
1112 	if (overstrike > 0)
1113 	{
1114 		/*
1115 		 * Overstrike the character at the current position
1116 		 * in the line buffer.  This will cause either
1117 		 * underline (if a "_" is overstruck),
1118 		 * bold (if an identical character is overstruck),
1119 		 * or just replacing the character in the buffer.
1120 		 */
1121 		LWCHAR prev_ch;
1122 		overstrike = utf_mode ? -1 : 0;
1123 		if (utf_mode)
1124 		{
1125 			/* To be correct, this must be a base character.  */
1126 			prev_ch = get_wchar(&linebuf.buf[linebuf.end]);
1127 		} else
1128 		{
1129 			prev_ch = (unsigned char) linebuf.buf[linebuf.end];
1130 		}
1131 		a = linebuf.attr[linebuf.end];
1132 		if (ch == prev_ch)
1133 		{
1134 			/*
1135 			 * Overstriking a char with itself means make it bold.
1136 			 * But overstriking an underscore with itself is
1137 			 * ambiguous.  It could mean make it bold, or
1138 			 * it could mean make it underlined.
1139 			 * Use the previous overstrike to resolve it.
1140 			 */
1141 			if (ch == '_')
1142 			{
1143 				if ((a & (AT_BOLD|AT_UNDERLINE)) != AT_NORMAL)
1144 					a |= (AT_BOLD|AT_UNDERLINE);
1145 				else if (last_overstrike != AT_NORMAL)
1146 					a |= last_overstrike;
1147 				else
1148 					a |= AT_BOLD;
1149 			} else
1150 				a |= AT_BOLD;
1151 		} else if (ch == '_')
1152 		{
1153 			a |= AT_UNDERLINE;
1154 			ch = prev_ch;
1155 			rep = &linebuf.buf[linebuf.end];
1156 		} else if (prev_ch == '_')
1157 		{
1158 			a |= AT_UNDERLINE;
1159 		}
1160 		/* Else we replace prev_ch, but we keep its attributes.  */
1161 	} else if (overstrike < 0)
1162 	{
1163 		if (   is_composing_char(ch)
1164 		    || is_combining_char(get_wchar(&linebuf.buf[linebuf.end]), ch))
1165 			/* Continuation of the same overstrike.  */
1166 			a = last_overstrike;
1167 		else
1168 			overstrike = 0;
1169 	}
1170 
1171 	if (ch == '\t')
1172 	{
1173 		/*
1174 		 * Expand a tab into spaces.
1175 		 */
1176 		switch (bs_mode)
1177 		{
1178 		case BS_CONTROL:
1179 			return store_control_char(ch, rep, pos);
1180 		case BS_NORMAL:
1181 		case BS_SPECIAL:
1182 			STORE_TAB(a, pos);
1183 			break;
1184 		}
1185 		return (0);
1186 	}
1187 	if ((!utf_mode || is_ascii_char(ch)) && control_char((char)ch))
1188 	{
1189 		return store_control_char(ch, rep, pos);
1190 	} else if (utf_mode && ctldisp != OPT_ON && is_ubin_char(ch))
1191 	{
1192 		STORE_STRING(prutfchar(ch), AT_BINARY, pos);
1193 	} else
1194 	{
1195 		STORE_CHAR(ch, a, rep, pos);
1196 	}
1197 	return (0);
1198 }
1199 
1200 /*
1201  *
1202  */
1203 	public int
1204 pflushmbc(VOID_PARAM)
1205 {
1206 	int r = 0;
1207 
1208 	if (mbc_buf_len > 0)
1209 	{
1210 		/* Flush incomplete (truncated) sequence.  */
1211 		r = flush_mbc_buf(mbc_pos);
1212 		mbc_buf_len = 0;
1213 	}
1214 	return r;
1215 }
1216 
1217 /*
1218  * Switch to normal attribute at end of line.
1219  */
1220 	static void
1221 add_attr_normal(VOID_PARAM)
1222 {
1223 	if (ctldisp != OPT_ONPLUS || !is_ansi_end('m'))
1224 		return;
1225 	addstr_linebuf("\033[m", AT_ANSI, 0);
1226 	if (hlink_in_line) /* Don't send hyperlink clear if we know we don't need to. */
1227 		addstr_linebuf("\033]8;;\033\\", AT_ANSI, 0);
1228 }
1229 
1230 /*
1231  * Terminate the line in the line buffer.
1232  */
1233 	public void
1234 pdone(endline, chopped, forw)
1235 	int endline;
1236 	int chopped;
1237 	int forw;
1238 {
1239 	(void) pflushmbc();
1240 
1241 	if (pendc && (pendc != '\r' || !endline))
1242 		/*
1243 		 * If we had a pending character, put it in the buffer.
1244 		 * But discard a pending CR if we are at end of line
1245 		 * (that is, discard the CR in a CR/LF sequence).
1246 		 */
1247 		(void) do_append(pendc, NULL, pendpos);
1248 
1249 	if (chopped && rscroll_char)
1250 	{
1251 		/*
1252 		 * Display the right scrolling char.
1253 		 * If we've already filled the rightmost screen char
1254 		 * (in the buffer), overwrite it.
1255 		 */
1256 		if (end_column >= sc_width + cshift)
1257 		{
1258 			/* We've already written in the rightmost char. */
1259 			end_column = right_column;
1260 			linebuf.end = right_curr;
1261 		}
1262 		add_attr_normal();
1263 		while (end_column < sc_width-1 + cshift)
1264 		{
1265 			/*
1266 			 * Space to last (rightmost) char on screen.
1267 			 * This may be necessary if the char we overwrote
1268 			 * was double-width.
1269 			 */
1270 			add_linebuf(' ', rscroll_attr, 1);
1271 		}
1272 		/* Print rscroll char. It must be single-width. */
1273 		add_linebuf(rscroll_char, rscroll_attr, 1);
1274 	} else
1275 	{
1276 		add_attr_normal();
1277 	}
1278 
1279 	/*
1280 	 * If we're coloring a status line, fill out the line with spaces.
1281 	 */
1282 	if (status_line && line_mark_attr != 0) {
1283 		while (end_column +1 < sc_width + cshift)
1284 			add_linebuf(' ', line_mark_attr, 1);
1285 	}
1286 
1287 	/*
1288 	 * Add a newline if necessary,
1289 	 * and append a '\0' to the end of the line.
1290 	 * We output a newline if we're not at the right edge of the screen,
1291 	 * or if the terminal doesn't auto wrap,
1292 	 * or if this is really the end of the line AND the terminal ignores
1293 	 * a newline at the right edge.
1294 	 * (In the last case we don't want to output a newline if the terminal
1295 	 * doesn't ignore it since that would produce an extra blank line.
1296 	 * But we do want to output a newline if the terminal ignores it in case
1297 	 * the next line is blank.  In that case the single newline output for
1298 	 * that blank line would be ignored!)
1299 	 */
1300 	if (end_column < sc_width + cshift || !auto_wrap || (endline && ignaw) || ctldisp == OPT_ON)
1301 	{
1302 		add_linebuf('\n', AT_NORMAL, 0);
1303 	}
1304 	else if (ignaw && end_column >= sc_width + cshift && forw)
1305 	{
1306 		/*
1307 		 * Terminals with "ignaw" don't wrap until they *really* need
1308 		 * to, i.e. when the character *after* the last one to fit on a
1309 		 * line is output. But they are too hard to deal with when they
1310 		 * get in the state where a full screen width of characters
1311 		 * have been output but the cursor is sitting on the right edge
1312 		 * instead of at the start of the next line.
1313 		 * So we nudge them into wrapping by outputting a space
1314 		 * character plus a backspace.  But do this only if moving
1315 		 * forward; if we're moving backward and drawing this line at
1316 		 * the top of the screen, the space would overwrite the first
1317 		 * char on the next line.  We don't need to do this "nudge"
1318 		 * at the top of the screen anyway.
1319 		 */
1320 		add_linebuf(' ', AT_NORMAL, 1);
1321 		add_linebuf('\b', AT_NORMAL, -1);
1322 	}
1323 	set_linebuf(linebuf.end, '\0', AT_NORMAL);
1324 }
1325 
1326 /*
1327  * Set an attribute on each char of the line in the line buffer.
1328  */
1329 	public void
1330 set_attr_line(a)
1331 	int a;
1332 {
1333 	int i;
1334 
1335 	for (i = linebuf.print;  i < linebuf.end;  i++)
1336 		linebuf.attr[i] |= a;
1337 }
1338 
1339 /*
1340  * Set the char to be displayed in the status column.
1341  */
1342 	public void
1343 set_status_col(c, attr)
1344 	int c;
1345 	int attr;
1346 {
1347 	set_pfx(0, c, attr);
1348 }
1349 
1350 /*
1351  * Get a character from the current line.
1352  * Return the character as the function return value,
1353  * and the character attribute in *ap.
1354  */
1355 	public int
1356 gline(i, ap)
1357 	int i;
1358 	int *ap;
1359 {
1360 	if (is_null_line)
1361 	{
1362 		/*
1363 		 * If there is no current line, we pretend the line is
1364 		 * either "~" or "", depending on the "twiddle" flag.
1365 		 */
1366 		if (twiddle)
1367 		{
1368 			if (i == 0)
1369 			{
1370 				*ap = AT_BOLD;
1371 				return '~';
1372 			}
1373 			--i;
1374 		}
1375 		/* Make sure we're back to AT_NORMAL before the '\n'.  */
1376 		*ap = AT_NORMAL;
1377 		return i ? '\0' : '\n';
1378 	}
1379 
1380 	if (i < linebuf.pfx_end)
1381 	{
1382 		*ap = linebuf.pfx_attr[i];
1383 		return linebuf.pfx[i];
1384 	}
1385 	i += linebuf.print - linebuf.pfx_end;
1386 	*ap = linebuf.attr[i];
1387 	return (linebuf.buf[i] & 0xFF);
1388 }
1389 
1390 /*
1391  * Indicate that there is no current line.
1392  */
1393 	public void
1394 null_line(VOID_PARAM)
1395 {
1396 	is_null_line = 1;
1397 	cshift = 0;
1398 }
1399 
1400 /*
1401  * Analogous to forw_line(), but deals with "raw lines":
1402  * lines which are not split for screen width.
1403  * {{ This is supposed to be more efficient than forw_line(). }}
1404  */
1405 	public POSITION
1406 forw_raw_line(curr_pos, linep, line_lenp)
1407 	POSITION curr_pos;
1408 	char **linep;
1409 	int *line_lenp;
1410 {
1411 	int n;
1412 	int c;
1413 	POSITION new_pos;
1414 
1415 	if (curr_pos == NULL_POSITION || ch_seek(curr_pos) ||
1416 		(c = ch_forw_get()) == EOI)
1417 		return (NULL_POSITION);
1418 
1419 	n = 0;
1420 	for (;;)
1421 	{
1422 		if (c == '\n' || c == EOI || ABORT_SIGS())
1423 		{
1424 			new_pos = ch_tell();
1425 			break;
1426 		}
1427 		if (n >= size_linebuf-1)
1428 		{
1429 			if (expand_linebuf())
1430 			{
1431 				/*
1432 				 * Overflowed the input buffer.
1433 				 * Pretend the line ended here.
1434 				 */
1435 				new_pos = ch_tell() - 1;
1436 				break;
1437 			}
1438 		}
1439 		linebuf.buf[n++] = c;
1440 		c = ch_forw_get();
1441 	}
1442 	linebuf.buf[n] = '\0';
1443 	if (linep != NULL)
1444 		*linep = linebuf.buf;
1445 	if (line_lenp != NULL)
1446 		*line_lenp = n;
1447 	return (new_pos);
1448 }
1449 
1450 /*
1451  * Analogous to back_line(), but deals with "raw lines".
1452  * {{ This is supposed to be more efficient than back_line(). }}
1453  */
1454 	public POSITION
1455 back_raw_line(curr_pos, linep, line_lenp)
1456 	POSITION curr_pos;
1457 	char **linep;
1458 	int *line_lenp;
1459 {
1460 	int n;
1461 	int c;
1462 	POSITION new_pos;
1463 
1464 	if (curr_pos == NULL_POSITION || curr_pos <= ch_zero() ||
1465 		ch_seek(curr_pos-1))
1466 		return (NULL_POSITION);
1467 
1468 	n = size_linebuf;
1469 	linebuf.buf[--n] = '\0';
1470 	for (;;)
1471 	{
1472 		c = ch_back_get();
1473 		if (c == '\n' || ABORT_SIGS())
1474 		{
1475 			/*
1476 			 * This is the newline ending the previous line.
1477 			 * We have hit the beginning of the line.
1478 			 */
1479 			new_pos = ch_tell() + 1;
1480 			break;
1481 		}
1482 		if (c == EOI)
1483 		{
1484 			/*
1485 			 * We have hit the beginning of the file.
1486 			 * This must be the first line in the file.
1487 			 * This must, of course, be the beginning of the line.
1488 			 */
1489 			new_pos = ch_zero();
1490 			break;
1491 		}
1492 		if (n <= 0)
1493 		{
1494 			int old_size_linebuf = size_linebuf;
1495 			char *fm;
1496 			char *to;
1497 			if (expand_linebuf())
1498 			{
1499 				/*
1500 				 * Overflowed the input buffer.
1501 				 * Pretend the line ended here.
1502 				 */
1503 				new_pos = ch_tell() + 1;
1504 				break;
1505 			}
1506 			/*
1507 			 * Shift the data to the end of the new linebuf.
1508 			 */
1509 			for (fm = linebuf.buf + old_size_linebuf - 1,
1510 			      to = linebuf.buf + size_linebuf - 1;
1511 			     fm >= linebuf.buf;  fm--, to--)
1512 				*to = *fm;
1513 			n = size_linebuf - old_size_linebuf;
1514 		}
1515 		linebuf.buf[--n] = c;
1516 	}
1517 	if (linep != NULL)
1518 		*linep = &linebuf.buf[n];
1519 	if (line_lenp != NULL)
1520 		*line_lenp = size_linebuf - 1 - n;
1521 	return (new_pos);
1522 }
1523 
1524 /*
1525  * Append a string to the line buffer.
1526  */
1527 	static int
1528 pappstr(str)
1529 	constant char *str;
1530 {
1531 	while (*str != '\0')
1532 	{
1533 		if (pappend(*str++, NULL_POSITION))
1534 			/* Doesn't fit on screen. */
1535 			return 1;
1536 	}
1537 	return 0;
1538 }
1539 
1540 /*
1541  * Load a string into the line buffer.
1542  * If the string is too long to fit on the screen,
1543  * truncate the beginning of the string to fit.
1544  */
1545 	public void
1546 load_line(str)
1547 	constant char *str;
1548 {
1549 	int save_hshift = hshift;
1550 
1551 	hshift = 0;
1552 	for (;;)
1553 	{
1554 		prewind();
1555 		if (pappstr(str) == 0)
1556 			break;
1557 		/*
1558 		 * Didn't fit on screen; increase left shift by one.
1559 		 * {{ This gets very inefficient if the string
1560 		 * is much longer than the screen width. }}
1561 		 */
1562 		hshift += 1;
1563 	}
1564 	set_linebuf(linebuf.end, '\0', AT_NORMAL);
1565 	hshift = save_hshift;
1566 }
1567 
1568 /*
1569  * Find the shift necessary to show the end of the longest displayed line.
1570  */
1571 	public int
1572 rrshift(VOID_PARAM)
1573 {
1574 	POSITION pos;
1575 	int save_width;
1576 	int line;
1577 	int longest = 0;
1578 
1579 	save_width = sc_width;
1580 	sc_width = INT_MAX;
1581 	pos = position(TOP);
1582 	for (line = 0; line < sc_height && pos != NULL_POSITION; line++)
1583 	{
1584 		pos = forw_line(pos);
1585 		if (end_column > longest)
1586 			longest = end_column;
1587 	}
1588 	sc_width = save_width;
1589 	if (longest < sc_width)
1590 		return 0;
1591 	return longest - sc_width;
1592 }
1593 
1594 /*
1595  * Get the color_map index associated with a given attribute.
1596  */
1597 	static int
1598 color_index(attr)
1599 	int attr;
1600 {
1601 	if (use_color)
1602 	{
1603 		switch (attr & AT_COLOR)
1604 		{
1605 		case AT_COLOR_ATTN:    return 0;
1606 		case AT_COLOR_BIN:     return 1;
1607 		case AT_COLOR_CTRL:    return 2;
1608 		case AT_COLOR_ERROR:   return 3;
1609 		case AT_COLOR_LINENUM: return 4;
1610 		case AT_COLOR_MARK:    return 5;
1611 		case AT_COLOR_PROMPT:  return 6;
1612 		case AT_COLOR_RSCROLL: return 7;
1613 		case AT_COLOR_SEARCH:  return 8;
1614 		case AT_COLOR_HEADER:  return 9;
1615 		}
1616 	}
1617 	if (attr & AT_UNDERLINE)
1618 		return 10;
1619 	if (attr & AT_BOLD)
1620 		return 11;
1621 	if (attr & AT_BLINK)
1622 		return 12;
1623 	if (attr & AT_STANDOUT)
1624 		return 13;
1625 	return -1;
1626 }
1627 
1628 /*
1629  * Set the color string to use for a given attribute.
1630  */
1631 	public int
1632 set_color_map(attr, colorstr)
1633 	int attr;
1634 	char *colorstr;
1635 {
1636 	int cx = color_index(attr);
1637 	if (cx < 0)
1638 		return -1;
1639 	if (strlen(colorstr)+1 > sizeof(color_map[cx]))
1640 		return -1;
1641 	if (*colorstr != '\0' && parse_color(colorstr, NULL, NULL) == CT_NULL)
1642 		return -1;
1643 	strcpy(color_map[cx], colorstr);
1644 	return 0;
1645 }
1646 
1647 /*
1648  * Get the color string to use for a given attribute.
1649  */
1650 	public char *
1651 get_color_map(attr)
1652 	int attr;
1653 {
1654 	int cx = color_index(attr);
1655 	if (cx < 0)
1656 		return NULL;
1657 	return color_map[cx];
1658 }
1659