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