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