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