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