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