2 * Copyright (C) 1984-2023 Mark Nudelman
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.
7 * For more information, see the README file.
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.
20 #if MSDOS_COMPILER==WIN32C
21 #define WIN32_LEAN_AND_MEAN
25 #define MAX_PFX_WIDTH (MAX_LINENUM_WIDTH + MAX_STATUSCOL_WIDTH + 1)
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 */
37 * Buffer of ansi sequences which have been shifted off the left edge
40 static struct xbuffer shifted_ansi;
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. }}
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;
53 public int size_linebuf = 0; /* Size of line buffer (and attr buffer) */
54 static struct ansi_state *line_ansi = NULL;
55 static int 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;
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 int is_null_line; /* There is no current line */
73 static POSITION pendpos;
74 static char *end_ansi_chars;
75 static char *mid_ansi_chars;
78 static int attr_swidth(int a);
79 static int attr_ewidth(int a);
80 static int do_append(LWCHAR ch, char *rep, POSITION pos);
84 extern int proc_backspace;
86 extern int proc_return;
91 extern int status_col;
92 extern int status_col_width;
93 extern int linenum_width;
94 extern int auto_wrap, ignaw;
95 extern int bo_s_width, bo_e_width;
96 extern int ul_s_width, ul_e_width;
97 extern int bl_s_width, bl_e_width;
98 extern int so_s_width, so_e_width;
99 extern int sc_width, sc_height;
101 extern POSITION start_attnpos;
102 extern POSITION end_attnpos;
103 extern char rscroll_char;
104 extern int rscroll_attr;
105 extern int use_color;
106 extern int status_line;
108 static char mbc_buf[MAX_UTF_CHAR_LEN];
109 static int mbc_buf_len = 0;
110 static int mbc_buf_index = 0;
111 static POSITION mbc_pos;
112 static int saved_line_end;
113 static int saved_end_column;
115 /* Configurable color map */
116 struct color_map { int attr; char color[12]; };
117 static struct color_map color_map[] = {
118 { AT_UNDERLINE, "" },
122 { AT_COLOR_ATTN, "Wm" },
123 { AT_COLOR_BIN, "kR" },
124 { AT_COLOR_CTRL, "kR" },
125 { AT_COLOR_ERROR, "kY" },
126 { AT_COLOR_LINENUM, "c" },
127 { AT_COLOR_MARK, "Wb" },
128 { AT_COLOR_PROMPT, "kC" },
129 { AT_COLOR_RSCROLL, "kc" },
130 { AT_COLOR_HEADER, "" },
131 { AT_COLOR_SEARCH, "kG" },
132 { AT_COLOR_SUBSEARCH(1), "ky" },
133 { AT_COLOR_SUBSEARCH(2), "wb" },
134 { AT_COLOR_SUBSEARCH(3), "YM" },
135 { AT_COLOR_SUBSEARCH(4), "Yr" },
136 { AT_COLOR_SUBSEARCH(5), "Wc" },
139 /* State while processing an ANSI escape sequence */
141 int hindex; /* Index into hyperlink prefix */
142 int hlink; /* Processing hyperlink address? */
143 int prev_esc; /* Prev char was ESC (to detect ESC-\ seq) */
147 * Initialize from environment variables.
149 public void init_line(void)
153 end_ansi_chars = lgetenv("LESSANSIENDCHARS");
154 if (isnullenv(end_ansi_chars))
155 end_ansi_chars = "m";
157 mid_ansi_chars = lgetenv("LESSANSIMIDCHARS");
158 if (isnullenv(mid_ansi_chars))
159 mid_ansi_chars = "0123456789:;[?!\"'#%()*+ ";
161 linebuf.buf = (char *) ecalloc(LINEBUF_SIZE, sizeof(char));
162 linebuf.attr = (int *) ecalloc(LINEBUF_SIZE, sizeof(int));
163 size_linebuf = LINEBUF_SIZE;
164 xbuf_init(&shifted_ansi);
165 xbuf_init(&last_ansi);
166 for (ax = 0; ax < NUM_LAST_ANSIS; ax++)
167 xbuf_init(&last_ansis[ax]);
172 * Expand the line buffer.
174 static int expand_linebuf(void)
176 /* Double the size of the line buffer. */
177 int new_size = size_linebuf * 2;
178 char *new_buf = (char *) calloc(new_size, sizeof(char));
179 int *new_attr = (int *) calloc(new_size, sizeof(int));
180 if (new_buf == NULL || new_attr == NULL)
182 if (new_attr != NULL)
189 * We just calloc'd the buffers; copy the old contents.
191 memcpy(new_buf, linebuf.buf, size_linebuf * sizeof(char));
192 memcpy(new_attr, linebuf.attr, size_linebuf * sizeof(int));
195 linebuf.buf = new_buf;
196 linebuf.attr = new_attr;
197 size_linebuf = new_size;
202 * Is a character ASCII?
204 public int is_ascii_char(LWCHAR ch)
211 static void inc_end_column(int w)
213 if (end_column > right_column && w > 0)
215 right_column = end_column;
216 right_curr = linebuf.end;
221 public POSITION line_position(void)
227 * Rewind the line buffer.
229 public void prewind(void)
233 linebuf.print = 6; /* big enough for longest UTF-8 sequence */
235 for (linebuf.end = 0; linebuf.end < linebuf.print; linebuf.end++)
237 linebuf.buf[linebuf.end] = '\0';
238 linebuf.attr[linebuf.end] = 0;
246 last_overstrike = AT_NORMAL;
254 line_pos = NULL_POSITION;
255 xbuf_reset(&shifted_ansi);
256 xbuf_reset(&last_ansi);
257 for (ax = 0; ax < NUM_LAST_ANSIS; ax++)
258 xbuf_reset(&last_ansis[ax]);
263 * Set a character in the line buffer.
265 static void set_linebuf(int n, char ch, int attr)
267 if (n >= size_linebuf)
270 * Won't fit in line buffer.
273 if (expand_linebuf())
277 linebuf.attr[n] = attr;
281 * Append a character to the line buffer.
283 static void add_linebuf(char ch, int attr, int w)
285 set_linebuf(linebuf.end++, ch, attr);
290 * Append a string to the line buffer.
292 static void addstr_linebuf(char *s, int attr, int cw)
294 for ( ; *s != '\0'; s++)
295 add_linebuf(*s, attr, cw);
299 * Set a character in the line prefix buffer.
301 static void set_pfx(int n, char ch, int attr)
304 linebuf.pfx_attr[n] = attr;
308 * Append a character to the line prefix buffer.
310 static void add_pfx(char ch, int attr)
312 set_pfx(linebuf.pfx_end++, ch, attr);
316 * Insert the status column and line number into the line buffer.
318 public void plinestart(POSITION pos)
323 if (linenums == OPT_ONPLUS)
326 * Get the line number and put it in the current line.
327 * {{ Note: since find_linenum calls forw_raw_line,
328 * it may seek in the input file, requiring the caller
329 * of plinestart to re-seek if necessary. }}
330 * {{ Since forw_raw_line modifies linebuf, we must
331 * do this first, before storing anything in linebuf. }}
333 linenum = find_linenum(pos);
337 * Display a status column if the -J option is set.
339 if (status_col || status_line)
341 char c = posmark(pos);
343 line_mark_attr = AT_HILITE|AT_COLOR_MARK;
344 else if (start_attnpos != NULL_POSITION &&
345 pos >= start_attnpos && pos <= end_attnpos)
346 line_mark_attr = AT_HILITE|AT_COLOR_ATTN;
349 add_pfx(c ? c : ' ', line_mark_attr); /* column 0: status */
350 while (linebuf.pfx_end < status_col_width)
351 add_pfx(' ', AT_NORMAL);
356 * Display the line number at the start of each line
357 * if the -N option is set.
359 if (linenums == OPT_ONPLUS)
361 char buf[INT_STRLEN_BOUND(linenum) + 2];
364 linenum = vlinenum(linenum);
369 linenumtoa(linenum, buf, 10);
370 len = (int) strlen(buf);
372 for (i = 0; i < linenum_width - len; i++)
373 add_pfx(' ', AT_NORMAL);
374 for (i = 0; i < len; i++)
375 add_pfx(buf[i], AT_BOLD|AT_COLOR_LINENUM);
376 add_pfx(' ', AT_NORMAL);
378 end_column = linebuf.pfx_end;
382 * Return the width of the line prefix (status column and line number).
383 * {{ Actual line number can be wider than linenum_width. }}
385 public int line_pfx_width(void)
389 width += status_col_width;
390 if (linenums == OPT_ONPLUS)
391 width += linenum_width + 1;
396 * Shift line left so that the last char is just to the left
397 * of the first visible column.
399 public void pshift_all(void)
402 for (i = linebuf.print; i < linebuf.end; i++)
403 if (linebuf.attr[i] == AT_ANSI)
404 xbuf_add_byte(&shifted_ansi, (unsigned char) linebuf.buf[i]);
405 linebuf.end = linebuf.print;
406 end_column = linebuf.pfx_end;
410 * Return the printing width of the start (enter) sequence
411 * for a given character attribute.
413 static int attr_swidth(int a)
417 a = apply_at_specials(a);
419 if (a & AT_UNDERLINE)
432 * Return the printing width of the end (exit) sequence
433 * for a given character attribute.
435 static int attr_ewidth(int a)
439 a = apply_at_specials(a);
441 if (a & AT_UNDERLINE)
454 * Return the printing width of a given character and attribute,
455 * if the character were added after prev_ch.
456 * Adding a character with a given attribute may cause an enter or exit
457 * attribute sequence to be inserted, so this must be taken into account.
459 public int pwidth(LWCHAR ch, int a, LWCHAR prev_ch, int prev_a)
466 * Backspace moves backwards one or two positions.
468 if (prev_a & (AT_ANSI|AT_BINARY))
469 return strlen(prchar('\b'));
470 return (utf_mode && is_wide_char(prev_ch)) ? -2 : -1;
473 if (!utf_mode || is_ascii_char(ch))
475 if (control_char((char)ch))
478 * Control characters do unpredictable things,
479 * so we don't even try to guess; say it doesn't move.
480 * This can only happen if the -r flag is in effect.
486 if (is_composing_char(ch) || is_combining_char(prev_ch, ch))
489 * Composing and combining chars take up no space.
491 * Some terminals, upon failure to compose a
492 * composing character with the character(s) that
493 * precede(s) it will actually take up one end_column
494 * for the composing character; there isn't much
495 * we could do short of testing the (complex)
496 * composition process ourselves and printing
497 * a binary representation when it fails.
504 * Other characters take one or two columns,
505 * plus the width of any attribute enter/exit sequence.
508 if (is_wide_char(ch))
510 if (linebuf.end > 0 && !is_at_equiv(linebuf.attr[linebuf.end-1], a))
511 w += attr_ewidth(linebuf.attr[linebuf.end-1]);
512 if (apply_at_specials(a) != AT_NORMAL &&
513 (linebuf.end == 0 || !is_at_equiv(linebuf.attr[linebuf.end-1], a)))
519 * Delete to the previous base character in the line buffer.
521 static int backc(void)
526 if (linebuf.end == 0)
528 p = &linebuf.buf[linebuf.end];
529 ch = step_char(&p, -1, linebuf.buf);
530 /* Skip back to the next nonzero-width char. */
531 while (p > linebuf.buf)
535 linebuf.end = (int) (p - linebuf.buf);
536 prev_ch = step_char(&p, -1, linebuf.buf);
537 width = pwidth(ch, linebuf.attr[linebuf.end], prev_ch, linebuf.attr[linebuf.end-1]);
539 /* {{ right_column? }} */
548 * Preserve the current position in the line buffer (for word wrapping).
550 public void savec(void)
552 saved_line_end = linebuf.end;
553 saved_end_column = end_column;
557 * Restore the position in the line buffer (start of line for word wrapping).
559 public void loadc(void)
561 linebuf.end = saved_line_end;
562 end_column = saved_end_column;
566 * Is a character the end of an ANSI escape sequence?
568 public int is_ansi_end(LWCHAR ch)
570 if (!is_ascii_char(ch))
572 return (strchr(end_ansi_chars, (char) ch) != NULL);
576 * Can a char appear in an ANSI escape sequence, before the end char?
578 public int is_ansi_middle(LWCHAR ch)
580 if (!is_ascii_char(ch))
584 return (strchr(mid_ansi_chars, (char) ch) != NULL);
588 * Skip past an ANSI escape sequence.
589 * pp is initially positioned just after the CSI_START char.
591 public void skip_ansi(struct ansi_state *pansi, char **pp, constant char *limit)
595 c = step_char(pp, +1, limit);
596 } while (*pp < limit && ansi_step(pansi, c) == ANSI_MID);
597 /* Note that we discard final char, for which is_ansi_end is true. */
601 * Determine if a character starts an ANSI escape sequence.
602 * If so, return an ansi_state struct; otherwise return NULL.
604 public struct ansi_state * ansi_start(LWCHAR ch)
606 struct ansi_state *pansi;
608 if (!IS_CSI_START(ch))
610 pansi = ecalloc(1, sizeof(struct ansi_state));
618 * Determine whether the next char in an ANSI escape sequence
621 public int ansi_step(struct ansi_state *pansi, LWCHAR ch)
625 /* Hyperlink ends with \7 or ESC-backslash. */
629 return (ch == '\\') ? ANSI_END : ANSI_ERR;
630 pansi->prev_esc = (ch == ESC);
633 if (pansi->hindex >= 0)
635 static char hlink_prefix[] = ESCS "]8;";
636 if (ch == hlink_prefix[pansi->hindex] ||
637 (pansi->hindex == 0 && IS_CSI_START(ch)))
640 if (hlink_prefix[pansi->hindex] == '\0')
641 pansi->hlink = 1; /* now processing hyperlink addr */
644 pansi->hindex = -1; /* not a hyperlink */
646 /* Check for SGR sequences */
647 if (is_ansi_middle(ch))
655 * Free an ansi_state structure.
657 public void ansi_done(struct ansi_state *pansi)
663 * Will w characters in attribute a fit on the screen?
665 static int fits_on_screen(int w, int a)
667 if (ctldisp == OPT_ON)
668 /* We're not counting, so say that everything fits. */
670 return (end_column - cshift + w + attr_ewidth(a) <= sc_width);
674 * Append a character and attribute to the line buffer.
676 #define STORE_CHAR(ch,a,rep,pos) \
678 if (store_char((ch),(a),(rep),(pos))) return (1); \
681 static int store_char(LWCHAR ch, int a, char *rep, POSITION pos)
688 i = (a & (AT_UNDERLINE|AT_BOLD));
698 if (pos == NULL_POSITION)
700 /* Color the prompt unless it has ansi sequences in it. */
701 hl_attr = ansi_in_line ? 0 : AT_STANDOUT|AT_COLOR_PROMPT;
702 } else if (a != AT_ANSI)
704 hl_attr = is_hilited_attr(pos, pos+1, 0, &matches);
705 if (hl_attr == 0 && status_line)
706 hl_attr = line_mark_attr;
711 * This character should be highlighted.
712 * Override the attribute passed in.
715 if (highest_hilite != NULL_POSITION && pos != NULL_POSITION && pos > highest_hilite)
716 highest_hilite = pos;
723 * This is the first non-hilited char after a hilite.
724 * Resend the last ANSI seq to restore color.
733 for (ai = 0; ai < NUM_LAST_ANSIS; ai++)
735 int ax = (curr_last_ansi + ai) % NUM_LAST_ANSIS;
736 for (i = 0; i < last_ansis[ax].end; i++)
737 STORE_CHAR(last_ansis[ax].data[i], AT_ANSI, NULL, pos);
746 char *p = &linebuf.buf[linebuf.end];
747 LWCHAR prev_ch = (linebuf.end > 0) ? step_char(&p, -1, linebuf.buf) : 0;
748 int prev_a = (linebuf.end > 0) ? linebuf.attr[linebuf.end-1] : 0;
749 w = pwidth(ch, a, prev_ch, prev_a);
752 if (!fits_on_screen(w, a))
762 replen = utf_len(rep[0]);
765 if (cshift == hshift)
767 if (line_pos == NULL_POSITION)
769 if (shifted_ansi.end > 0)
771 /* Copy shifted ANSI sequences to beginning of line. */
772 for (i = 0; i < shifted_ansi.end; i++)
773 add_linebuf(shifted_ansi.data[i], AT_ANSI, 0);
774 xbuf_reset(&shifted_ansi);
778 /* Add the char to the buf, even if we will left-shift it next. */
780 for (i = 0; i < replen; i++)
781 add_linebuf(*rep++, a, 0);
785 /* We haven't left-shifted enough yet. */
787 xbuf_add_byte(&shifted_ansi, (unsigned char) ch); /* Save ANSI attributes */
788 if (linebuf.end > linebuf.print)
790 /* Shift left enough to put last byte of this char at print-1. */
792 for (i = 0; i < linebuf.print; i++)
794 linebuf.buf[i] = linebuf.buf[i+replen];
795 linebuf.attr[i] = linebuf.attr[i+replen];
797 linebuf.end -= replen;
800 * If the char we just left-shifted was double width,
801 * the 2 spaces we shifted may be too much.
802 * Represent the "half char" at start of line with a highlighted space.
804 while (cshift > hshift)
806 add_linebuf(' ', rscroll_attr, 0);
814 #define STORE_STRING(s,a,pos) \
815 do { if (store_string((s),(a),(pos))) return (1); } while (0)
817 static int store_string(char *s, int a, POSITION pos)
819 if (!fits_on_screen(strlen(s), a))
821 for ( ; *s != 0; s++)
822 STORE_CHAR(*s, a, NULL, pos);
827 * Append a tab to the line buffer.
828 * Store spaces to represent the tab.
830 #define STORE_TAB(a,pos) \
831 do { if (store_tab((a),(pos))) return (1); } while (0)
833 static int store_tab(int attr, POSITION pos)
835 int to_tab = end_column - linebuf.pfx_end;
837 if (ntabstops < 2 || to_tab >= tabstops[ntabstops-1])
838 to_tab = tabdefault -
839 ((to_tab - tabstops[ntabstops-1]) % tabdefault);
843 for (i = ntabstops - 2; i >= 0; i--)
844 if (to_tab >= tabstops[i])
846 to_tab = tabstops[i+1] - to_tab;
850 STORE_CHAR(' ', attr, " ", pos);
851 } while (--to_tab > 0);
855 #define STORE_PRCHAR(c, pos) \
856 do { if (store_prchar((c), (pos))) return 1; } while (0)
858 static int store_prchar(LWCHAR c, POSITION pos)
861 * Convert to printable representation.
863 STORE_STRING(prchar(c), AT_BINARY|AT_COLOR_CTRL, pos);
867 static int flush_mbc_buf(POSITION pos)
871 for (i = 0; i < mbc_buf_index; i++)
872 if (store_prchar(mbc_buf[i], pos))
873 return mbc_buf_index - i;
878 * Append a character to the line buffer.
879 * Expand tabs into spaces, handle underlining, boldfacing, etc.
880 * Returns 0 if ok, 1 if couldn't fit in buffer.
882 public int pappend(int c, POSITION pos)
888 if (c == '\r' && pendc == '\r')
890 if (do_append(pendc, NULL, pendpos))
892 * Oops. We've probably lost the char which
893 * was in pendc, since caller won't back up.
899 if (c == '\r' && (proc_return == OPT_ON || (bs_mode == BS_SPECIAL && proc_return == OPT_OFF)))
901 if (mbc_buf_len > 0) /* utf_mode must be on. */
903 /* Flush incomplete (truncated) sequence. */
904 r = flush_mbc_buf(mbc_pos);
905 mbc_buf_index = r + 1;
908 return (mbc_buf_index);
912 * Don't put the CR into the buffer until we see
913 * the next char. If the next char is a newline,
923 r = do_append(c, NULL, pos);
926 /* Perform strict validation in all possible cases. */
927 if (mbc_buf_len == 0)
932 if (IS_ASCII_OCTET(c))
933 r = do_append(c, NULL, pos);
934 else if (IS_UTF8_LEAD(c))
936 mbc_buf_len = utf_len(c);
940 /* UTF8_INVALID or stray UTF8_TRAIL */
941 r = flush_mbc_buf(pos);
942 } else if (IS_UTF8_TRAIL(c))
944 mbc_buf[mbc_buf_index++] = c;
945 if (mbc_buf_index < mbc_buf_len)
947 if (is_utf8_well_formed(mbc_buf, mbc_buf_index))
948 r = do_append(get_wchar(mbc_buf), mbc_buf, mbc_pos);
950 /* Complete, but not shortest form, sequence. */
951 mbc_buf_index = r = flush_mbc_buf(mbc_pos);
955 /* Flush incomplete (truncated) sequence. */
956 r = flush_mbc_buf(mbc_pos);
957 mbc_buf_index = r + 1;
959 /* Handle new char. */
966 /* How many chars should caller back up? */
967 r = (!utf_mode) ? 1 : mbc_buf_index;
972 static int store_control_char(LWCHAR ch, char *rep, POSITION pos)
974 if (ctldisp == OPT_ON)
976 /* Output the character itself. */
977 STORE_CHAR(ch, AT_NORMAL, rep, pos);
980 /* Output a printable representation of the character. */
981 STORE_PRCHAR((char) ch, pos);
986 static int store_ansi(LWCHAR ch, char *rep, POSITION pos)
988 switch (ansi_step(line_ansi, ch))
991 STORE_CHAR(ch, AT_ANSI, rep, pos);
992 if (line_ansi->hlink)
994 xbuf_add_byte(&last_ansi, (unsigned char) ch);
997 STORE_CHAR(ch, AT_ANSI, rep, pos);
998 ansi_done(line_ansi);
1000 xbuf_add_byte(&last_ansi, (unsigned char) ch);
1001 xbuf_set(&last_ansis[curr_last_ansi], &last_ansi);
1002 xbuf_reset(&last_ansi);
1003 curr_last_ansi = (curr_last_ansi + 1) % NUM_LAST_ANSIS;
1007 /* Remove whole unrecognized sequence. */
1008 char *start = (cshift < hshift) ? xbuf_char_data(&shifted_ansi): linebuf.buf;
1009 int *end = (cshift < hshift) ? &shifted_ansi.end : &linebuf.end;
1010 char *p = start + *end;
1013 bch = step_char(&p, -1, start);
1014 } while (p > start && !IS_CSI_START(bch));
1015 *end = (int) (p - start);
1017 xbuf_reset(&last_ansi);
1018 ansi_done(line_ansi);
1025 static int store_bs(LWCHAR ch, char *rep, POSITION pos)
1027 if (proc_backspace == OPT_ONPLUS || (bs_mode == BS_CONTROL && proc_backspace == OPT_OFF))
1028 return store_control_char(ch, rep, pos);
1029 if (linebuf.end > 0 &&
1030 ((linebuf.end <= linebuf.print && linebuf.buf[linebuf.end-1] == '\0') ||
1031 (linebuf.end > 0 && linebuf.attr[linebuf.end - 1] & (AT_ANSI|AT_BINARY))))
1032 STORE_PRCHAR('\b', pos);
1033 else if (proc_backspace == OPT_OFF && bs_mode == BS_NORMAL)
1034 STORE_CHAR(ch, AT_NORMAL, NULL, pos);
1035 else if (proc_backspace == OPT_ON || (bs_mode == BS_SPECIAL && proc_backspace == OPT_OFF))
1036 overstrike = backc();
1040 static int do_append(LWCHAR ch, char *rep, POSITION pos)
1043 int in_overstrike = overstrike;
1045 if (ctldisp == OPT_ONPLUS && line_ansi == NULL)
1047 line_ansi = ansi_start(ch);
1048 if (line_ansi != NULL)
1053 if (line_ansi != NULL)
1054 return store_ansi(ch, rep, pos);
1057 return store_bs(ch, rep, pos);
1059 if (in_overstrike > 0)
1062 * Overstrike the character at the current position
1063 * in the line buffer. This will cause either
1064 * underline (if a "_" is overstruck),
1065 * bold (if an identical character is overstruck),
1066 * or just replacing the character in the buffer.
1069 overstrike = utf_mode ? -1 : 0;
1072 /* To be correct, this must be a base character. */
1073 prev_ch = get_wchar(&linebuf.buf[linebuf.end]);
1076 prev_ch = (unsigned char) linebuf.buf[linebuf.end];
1078 a = linebuf.attr[linebuf.end];
1082 * Overstriking a char with itself means make it bold.
1083 * But overstriking an underscore with itself is
1084 * ambiguous. It could mean make it bold, or
1085 * it could mean make it underlined.
1086 * Use the previous overstrike to resolve it.
1090 if ((a & (AT_BOLD|AT_UNDERLINE)) != AT_NORMAL)
1091 a |= (AT_BOLD|AT_UNDERLINE);
1092 else if (last_overstrike != AT_NORMAL)
1093 a |= last_overstrike;
1098 } else if (ch == '_')
1102 rep = &linebuf.buf[linebuf.end];
1103 } else if (prev_ch == '_')
1107 /* Else we replace prev_ch, but we keep its attributes. */
1108 } else if (in_overstrike < 0)
1110 if ( is_composing_char(ch)
1111 || is_combining_char(get_wchar(&linebuf.buf[linebuf.end]), ch))
1112 /* Continuation of the same overstrike. */
1113 a = last_overstrike;
1121 * Expand a tab into spaces.
1123 if (proc_tab == OPT_ONPLUS || (bs_mode == BS_CONTROL && proc_tab == OPT_OFF))
1124 return store_control_char(ch, rep, pos);
1128 if ((!utf_mode || is_ascii_char(ch)) && control_char((char)ch))
1130 return store_control_char(ch, rep, pos);
1131 } else if (utf_mode && ctldisp != OPT_ON && is_ubin_char(ch))
1133 STORE_STRING(prutfchar(ch), AT_BINARY, pos);
1136 STORE_CHAR(ch, a, rep, pos);
1144 public int pflushmbc(void)
1148 if (mbc_buf_len > 0)
1150 /* Flush incomplete (truncated) sequence. */
1151 r = flush_mbc_buf(mbc_pos);
1158 * Switch to normal attribute at end of line.
1160 static void add_attr_normal(void)
1162 if (ctldisp != OPT_ONPLUS || !is_ansi_end('m'))
1164 addstr_linebuf("\033[m", AT_ANSI, 0);
1165 if (hlink_in_line) /* Don't send hyperlink clear if we know we don't need to. */
1166 addstr_linebuf("\033]8;;\033\\", AT_ANSI, 0);
1170 * Terminate the line in the line buffer.
1172 public void pdone(int endline, int chopped, int forw)
1176 if (pendc && (pendc != '\r' || !endline))
1178 * If we had a pending character, put it in the buffer.
1179 * But discard a pending CR if we are at end of line
1180 * (that is, discard the CR in a CR/LF sequence).
1182 (void) do_append(pendc, NULL, pendpos);
1184 if (chopped && rscroll_char)
1187 * Display the right scrolling char.
1188 * If we've already filled the rightmost screen char
1189 * (in the buffer), overwrite it.
1191 if (end_column >= sc_width + cshift)
1193 /* We've already written in the rightmost char. */
1194 end_column = right_column;
1195 linebuf.end = right_curr;
1198 while (end_column < sc_width-1 + cshift)
1201 * Space to last (rightmost) char on screen.
1202 * This may be necessary if the char we overwrote
1205 add_linebuf(' ', rscroll_attr, 1);
1207 /* Print rscroll char. It must be single-width. */
1208 add_linebuf(rscroll_char, rscroll_attr, 1);
1215 * If we're coloring a status line, fill out the line with spaces.
1217 if (status_line && line_mark_attr != 0) {
1218 while (end_column +1 < sc_width + cshift)
1219 add_linebuf(' ', line_mark_attr, 1);
1223 * Add a newline if necessary,
1224 * and append a '\0' to the end of the line.
1225 * We output a newline if we're not at the right edge of the screen,
1226 * or if the terminal doesn't auto wrap,
1227 * or if this is really the end of the line AND the terminal ignores
1228 * a newline at the right edge.
1229 * (In the last case we don't want to output a newline if the terminal
1230 * doesn't ignore it since that would produce an extra blank line.
1231 * But we do want to output a newline if the terminal ignores it in case
1232 * the next line is blank. In that case the single newline output for
1233 * that blank line would be ignored!)
1235 if (end_column < sc_width + cshift || !auto_wrap || (endline && ignaw) || ctldisp == OPT_ON)
1237 add_linebuf('\n', AT_NORMAL, 0);
1239 else if (ignaw && end_column >= sc_width + cshift && forw)
1242 * Terminals with "ignaw" don't wrap until they *really* need
1243 * to, i.e. when the character *after* the last one to fit on a
1244 * line is output. But they are too hard to deal with when they
1245 * get in the state where a full screen width of characters
1246 * have been output but the cursor is sitting on the right edge
1247 * instead of at the start of the next line.
1248 * So we nudge them into wrapping by outputting a space
1249 * character plus a backspace. But do this only if moving
1250 * forward; if we're moving backward and drawing this line at
1251 * the top of the screen, the space would overwrite the first
1252 * char on the next line. We don't need to do this "nudge"
1253 * at the top of the screen anyway.
1255 add_linebuf(' ', AT_NORMAL, 1);
1256 add_linebuf('\b', AT_NORMAL, -1);
1258 set_linebuf(linebuf.end, '\0', AT_NORMAL);
1262 * Set an attribute on each char of the line in the line buffer.
1264 public void set_attr_line(int a)
1268 for (i = linebuf.print; i < linebuf.end; i++)
1269 if ((linebuf.attr[i] & AT_COLOR) == 0 || (a & AT_COLOR) == 0)
1270 linebuf.attr[i] |= a;
1274 * Set the char to be displayed in the status column.
1276 public void set_status_col(char c, int attr)
1278 set_pfx(0, c, attr);
1282 * Get a character from the current line.
1283 * Return the character as the function return value,
1284 * and the character attribute in *ap.
1286 public int gline(int i, int *ap)
1291 * If there is no current line, we pretend the line is
1292 * either "~" or "", depending on the "twiddle" flag.
1303 /* Make sure we're back to AT_NORMAL before the '\n'. */
1305 return i ? '\0' : '\n';
1308 if (i < linebuf.pfx_end)
1310 *ap = linebuf.pfx_attr[i];
1311 return linebuf.pfx[i];
1313 i += linebuf.print - linebuf.pfx_end;
1314 *ap = linebuf.attr[i];
1315 return (linebuf.buf[i] & 0xFF);
1319 * Indicate that there is no current line.
1321 public void null_line(void)
1328 * Analogous to forw_line(), but deals with "raw lines":
1329 * lines which are not split for screen width.
1330 * {{ This is supposed to be more efficient than forw_line(). }}
1332 public POSITION forw_raw_line(POSITION curr_pos, char **linep, int *line_lenp)
1338 if (curr_pos == NULL_POSITION || ch_seek(curr_pos) ||
1339 (c = ch_forw_get()) == EOI)
1340 return (NULL_POSITION);
1345 if (c == '\n' || c == EOI || ABORT_SIGS())
1347 new_pos = ch_tell();
1350 if (n >= size_linebuf-1)
1352 if (expand_linebuf())
1355 * Overflowed the input buffer.
1356 * Pretend the line ended here.
1358 new_pos = ch_tell() - 1;
1362 linebuf.buf[n++] = c;
1365 linebuf.buf[n] = '\0';
1367 *linep = linebuf.buf;
1368 if (line_lenp != NULL)
1374 * Analogous to back_line(), but deals with "raw lines".
1375 * {{ This is supposed to be more efficient than back_line(). }}
1377 public POSITION back_raw_line(POSITION curr_pos, char **linep, int *line_lenp)
1383 if (curr_pos == NULL_POSITION || curr_pos <= ch_zero() ||
1384 ch_seek(curr_pos-1))
1385 return (NULL_POSITION);
1388 linebuf.buf[--n] = '\0';
1392 if (c == '\n' || ABORT_SIGS())
1395 * This is the newline ending the previous line.
1396 * We have hit the beginning of the line.
1398 new_pos = ch_tell() + 1;
1404 * We have hit the beginning of the file.
1405 * This must be the first line in the file.
1406 * This must, of course, be the beginning of the line.
1408 new_pos = ch_zero();
1413 int old_size_linebuf = size_linebuf;
1416 if (expand_linebuf())
1419 * Overflowed the input buffer.
1420 * Pretend the line ended here.
1422 new_pos = ch_tell() + 1;
1426 * Shift the data to the end of the new linebuf.
1428 for (fm = linebuf.buf + old_size_linebuf - 1,
1429 to = linebuf.buf + size_linebuf - 1;
1430 fm >= linebuf.buf; fm--, to--)
1432 n = size_linebuf - old_size_linebuf;
1434 linebuf.buf[--n] = c;
1437 *linep = &linebuf.buf[n];
1438 if (line_lenp != NULL)
1439 *line_lenp = size_linebuf - 1 - n;
1444 * Skip cols printable columns at the start of line.
1445 * Return number of bytes skipped.
1447 public int skip_columns(int cols, char **linep, int *line_lenp)
1449 char *line = *linep;
1450 char *eline = line + *line_lenp;
1454 while (cols > 0 && line < eline)
1456 LWCHAR ch = step_char(&line, +1, eline);
1457 struct ansi_state *pansi = ansi_start(ch);
1460 skip_ansi(pansi, &line, eline);
1465 int w = pwidth(ch, 0, pch, 0);
1470 bytes = line - *linep;
1472 *line_lenp -= bytes;
1477 * Append a string to the line buffer.
1479 static int pappstr(constant char *str)
1481 while (*str != '\0')
1483 if (pappend(*str++, NULL_POSITION))
1484 /* Doesn't fit on screen. */
1491 * Load a string into the line buffer.
1492 * If the string is too long to fit on the screen,
1493 * truncate the beginning of the string to fit.
1495 public void load_line(constant char *str)
1497 int save_hshift = hshift;
1503 if (pappstr(str) == 0)
1506 * Didn't fit on screen; increase left shift by one.
1507 * {{ This gets very inefficient if the string
1508 * is much longer than the screen width. }}
1512 set_linebuf(linebuf.end, '\0', AT_NORMAL);
1513 hshift = save_hshift;
1517 * Find the shift necessary to show the end of the longest displayed line.
1519 public int rrshift(void)
1526 save_width = sc_width;
1528 pos = position(TOP);
1529 for (line = 0; line < sc_height && pos != NULL_POSITION; line++)
1531 pos = forw_line(pos);
1532 if (end_column > longest)
1533 longest = end_column;
1535 sc_width = save_width;
1536 if (longest < sc_width)
1538 return longest - sc_width;
1542 * Get the color_map index associated with a given attribute.
1544 static int lookup_color_index(int attr)
1547 for (cx = 0; cx < sizeof(color_map)/sizeof(*color_map); cx++)
1548 if (color_map[cx].attr == attr)
1553 static int color_index(int attr)
1555 if (use_color && (attr & AT_COLOR))
1556 return lookup_color_index(attr & AT_COLOR);
1557 if (attr & AT_UNDERLINE)
1558 return lookup_color_index(AT_UNDERLINE);
1560 return lookup_color_index(AT_BOLD);
1561 if (attr & AT_BLINK)
1562 return lookup_color_index(AT_BLINK);
1563 if (attr & AT_STANDOUT)
1564 return lookup_color_index(AT_STANDOUT);
1569 * Set the color string to use for a given attribute.
1571 public int set_color_map(int attr, char *colorstr)
1573 int cx = color_index(attr);
1576 if (strlen(colorstr)+1 > sizeof(color_map[cx].color))
1578 if (*colorstr != '\0' && parse_color(colorstr, NULL, NULL) == CT_NULL)
1580 strcpy(color_map[cx].color, colorstr);
1585 * Get the color string to use for a given attribute.
1587 public char * get_color_map(int attr)
1589 int cx = color_index(attr);
1592 return color_map[cx].color;