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