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