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