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