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