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