diff options
Diffstat (limited to 'contrib/less/line.c')
| -rw-r--r-- | contrib/less/line.c | 1500 |
1 files changed, 1500 insertions, 0 deletions
diff --git a/contrib/less/line.c b/contrib/less/line.c new file mode 100644 index 000000000000..57d3cbef2f4d --- /dev/null +++ b/contrib/less/line.c @@ -0,0 +1,1500 @@ +/* + * Copyright (C) 1984-2021 Mark Nudelman + * + * You may distribute under the terms of either the GNU General Public + * License or the Less License, as specified in the README file. + * + * For more information, see the README file. + */ + +/* + * Routines to manipulate the "line buffer". + * The line buffer holds a line of output as it is being built + * in preparation for output to the screen. + */ + +#include "less.h" +#include "charset.h" +#include "position.h" + +#if MSDOS_COMPILER==WIN32C +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#endif + +#define MAX_PFX_WIDTH (MAX_LINENUM_WIDTH + MAX_STATUSCOL_WIDTH + 1) +static struct { + char *buf; /* Buffer which holds the current output line */ + int *attr; /* Parallel to buf, to hold attributes */ + int print; /* Index in buf of first printable char */ + int end; /* Number of chars in buf */ + char pfx[MAX_PFX_WIDTH]; /* Holds status column and line number */ + int pfx_attr[MAX_PFX_WIDTH]; + int pfx_end; /* Number of chars in pfx */ +} linebuf; + +static struct { + char *buf; + int size; + int end; +} shifted_ansi; + +public int size_linebuf = 0; /* Size of line buffer (and attr buffer) */ +static struct ansi_state *line_ansi = NULL; +static int cshift; /* Current left-shift of output line buffer */ +public int hshift; /* Desired left-shift of output line buffer */ +public int tabstops[TABSTOP_MAX] = { 0 }; /* Custom tabstops */ +public int ntabstops = 1; /* Number of tabstops */ +public int tabdefault = 8; /* Default repeated tabstops */ +public POSITION highest_hilite; /* Pos of last hilite in file found so far */ + +static int end_column; /* Printable length, accounting for backspaces, etc. */ +static int right_curr; +static int right_column; +static int overstrike; /* Next char should overstrike previous char */ +static int last_overstrike = AT_NORMAL; +static int is_null_line; /* There is no current line */ +static LWCHAR pendc; +static POSITION pendpos; +static char *end_ansi_chars; +static char *mid_ansi_chars; + +static int attr_swidth LESSPARAMS ((int a)); +static int attr_ewidth LESSPARAMS ((int a)); +static int do_append LESSPARAMS ((LWCHAR ch, char *rep, POSITION pos)); + +extern int sigs; +extern int bs_mode; +extern int linenums; +extern int ctldisp; +extern int twiddle; +extern int binattr; +extern int status_col; +extern int status_col_width; +extern int linenum_width; +extern int auto_wrap, ignaw; +extern int bo_s_width, bo_e_width; +extern int ul_s_width, ul_e_width; +extern int bl_s_width, bl_e_width; +extern int so_s_width, so_e_width; +extern int sc_width, sc_height; +extern int utf_mode; +extern POSITION start_attnpos; +extern POSITION end_attnpos; +extern char rscroll_char; +extern int rscroll_attr; +extern int use_color; + +static char mbc_buf[MAX_UTF_CHAR_LEN]; +static int mbc_buf_len = 0; +static int mbc_buf_index = 0; +static POSITION mbc_pos; + +/* Configurable color map */ +static char color_map[AT_NUM_COLORS][12] = { + "Wm", /* AT_COLOR_ATTN */ + "kR", /* AT_COLOR_BIN */ + "kR", /* AT_COLOR_CTRL */ + "kY", /* AT_COLOR_ERROR */ + "c", /* AT_COLOR_LINENUM */ + "Wb", /* AT_COLOR_MARK */ + "kC", /* AT_COLOR_PROMPT */ + "kc", /* AT_COLOR_RSCROLL */ + "kG", /* AT_COLOR_SEARCH */ + "", /* AT_UNDERLINE */ + "", /* AT_BOLD */ + "", /* AT_BLINK */ + "", /* AT_STANDOUT */ +}; + +/* State while processing an ANSI escape sequence */ +struct ansi_state { + int hindex; /* Index into hyperlink prefix */ + int hlink; /* Processing hyperlink address? */ + int prev_esc; /* Prev char was ESC (to detect ESC-\ seq) */ +}; + +/* + * Initialize from environment variables. + */ + public void +init_line(VOID_PARAM) +{ + end_ansi_chars = lgetenv("LESSANSIENDCHARS"); + if (isnullenv(end_ansi_chars)) + end_ansi_chars = "m"; + + mid_ansi_chars = lgetenv("LESSANSIMIDCHARS"); + if (isnullenv(mid_ansi_chars)) + mid_ansi_chars = "0123456789:;[?!\"'#%()*+ "; + + linebuf.buf = (char *) ecalloc(LINEBUF_SIZE, sizeof(char)); + linebuf.attr = (int *) ecalloc(LINEBUF_SIZE, sizeof(int)); + size_linebuf = LINEBUF_SIZE; + shifted_ansi.buf = NULL; + shifted_ansi.size = 0; +} + +/* + * Expand the line buffer. + */ + static int +expand_linebuf(VOID_PARAM) +{ + /* Double the size of the line buffer. */ + int new_size = size_linebuf * 2; + + /* Just realloc to expand the buffer, if we can. */ +#if HAVE_REALLOC + char *new_buf = (char *) realloc(linebuf.buf, new_size); + int *new_attr = (int *) realloc(linebuf.attr, new_size*sizeof(int)); +#else + char *new_buf = (char *) calloc(new_size, sizeof(char)); + int *new_attr = (int *) calloc(new_size, sizeof(int)); +#endif + if (new_buf == NULL || new_attr == NULL) + { + if (new_attr != NULL) + free(new_attr); + if (new_buf != NULL) + free(new_buf); + return 1; + } +#if !HAVE_REALLOC + /* + * We just calloc'd the buffers; copy the old contents. + */ + memcpy(new_buf, linebuf.buf, size_linebuf * sizeof(char)); + memcpy(new_attr, linebuf.attr, size_linebuf * sizeof(int)); + free(linebuf.attr); + free(linebuf.buf); +#endif + linebuf.buf = new_buf; + linebuf.attr = new_attr; + size_linebuf = new_size; + return 0; +} + +/* + * Is a character ASCII? + */ + public int +is_ascii_char(ch) + LWCHAR ch; +{ + return (ch <= 0x7F); +} + +/* + */ + static void +inc_end_column(w) + int w; +{ + if (end_column > right_column && w > 0) + { + right_column = end_column; + right_curr = linebuf.end; + } + end_column += w; +} + +/* + * Rewind the line buffer. + */ + public void +prewind(VOID_PARAM) +{ + linebuf.print = 6; /* big enough for longest UTF-8 sequence */ + linebuf.pfx_end = 0; + for (linebuf.end = 0; linebuf.end < linebuf.print; linebuf.end++) + { + linebuf.buf[linebuf.end] = '\0'; + linebuf.attr[linebuf.end] = 0; + } + + end_column = 0; + right_curr = 0; + right_column = 0; + cshift = 0; + overstrike = 0; + last_overstrike = AT_NORMAL; + mbc_buf_len = 0; + is_null_line = 0; + pendc = '\0'; + shifted_ansi.end = 0; +} + +/* + * Set a character in the line buffer. + */ + static void +set_linebuf(n, ch, attr) + int n; + char ch; + int attr; +{ + linebuf.buf[n] = ch; + linebuf.attr[n] = attr; +} + +/* + * Append a character to the line buffer. + */ + static void +add_linebuf(ch, attr, w) + char ch; + int attr; + int w; +{ + set_linebuf(linebuf.end++, ch, attr); + inc_end_column(w); +} + +/* + * Set a character in the line prefix buffer. + */ + static void +set_pfx(n, ch, attr) + int n; + char ch; + int attr; +{ + linebuf.pfx[n] = ch; + linebuf.pfx_attr[n] = attr; +} + +/* + * Append a character to the line prefix buffer. + */ + static void +add_pfx(ch, attr) + char ch; + int attr; +{ + set_pfx(linebuf.pfx_end++, ch, attr); +} + +/* + * Insert the status column and line number into the line buffer. + */ + public void +plinestart(pos) + POSITION pos; +{ + LINENUM linenum = 0; + int i; + + if (linenums == OPT_ONPLUS) + { + /* + * Get the line number and put it in the current line. + * {{ Note: since find_linenum calls forw_raw_line, + * it may seek in the input file, requiring the caller + * of plinestart to re-seek if necessary. }} + * {{ Since forw_raw_line modifies linebuf, we must + * do this first, before storing anything in linebuf. }} + */ + linenum = find_linenum(pos); + } + + /* + * Display a status column if the -J option is set. + */ + if (status_col) + { + int a = AT_NORMAL; + char c = posmark(pos); + if (c != 0) + a |= AT_HILITE|AT_COLOR_MARK; + else + { + c = ' '; + if (start_attnpos != NULL_POSITION && + pos >= start_attnpos && pos <= end_attnpos) + a |= AT_HILITE|AT_COLOR_ATTN; + } + add_pfx(c, a); /* column 0: status */ + while (linebuf.pfx_end < status_col_width) + add_pfx(' ', AT_NORMAL); + } + + /* + * Display the line number at the start of each line + * if the -N option is set. + */ + if (linenums == OPT_ONPLUS) + { + char buf[INT_STRLEN_BOUND(linenum) + 2]; + int len; + + linenumtoa(linenum, buf); + len = (int) strlen(buf); + for (i = 0; i < linenum_width - len; i++) + add_pfx(' ', AT_NORMAL); + for (i = 0; i < len; i++) + add_pfx(buf[i], AT_NORMAL|AT_COLOR_LINENUM); + add_pfx(' ', AT_NORMAL); + } + end_column = linebuf.pfx_end; +} + +/* + * Return the width of the line prefix (status column and line number). + * {{ Actual line number can be wider than linenum_width. }} + */ + public int +line_pfx_width(VOID_PARAM) +{ + int width = 0; + if (status_col) + width += status_col_width; + if (linenums == OPT_ONPLUS) + width += linenum_width + 1; + return width; +} + +/* + * Add char to the shifted_ansi buffer. + */ + static void +add_ansi(ch) + char ch; +{ + if (shifted_ansi.end == shifted_ansi.size) + { + /* Expand shifted_ansi buffer. */ + int size = (shifted_ansi.size == 0) ? 8 : shifted_ansi.size * 2; + char *buf = (char *) ecalloc(size, sizeof(char)); + memcpy(buf, shifted_ansi.buf, shifted_ansi.size); + if (shifted_ansi.buf != NULL) free(shifted_ansi.buf); + shifted_ansi.buf = buf; + shifted_ansi.size = size; + } + shifted_ansi.buf[shifted_ansi.end++] = ch; +} + +/* + * Shift line left so that the last char is just to the left + * of the first visible column. + */ + public void +pshift_all(VOID_PARAM) +{ + int i; + for (i = linebuf.print; i < linebuf.end; i++) + if (linebuf.attr[i] == AT_ANSI) + add_ansi(linebuf.buf[i]); + linebuf.end = linebuf.print; + end_column = linebuf.pfx_end; +} + +/* + * Return the printing width of the start (enter) sequence + * for a given character attribute. + */ + static int +attr_swidth(a) + int a; +{ + int w = 0; + + a = apply_at_specials(a); + + if (a & AT_UNDERLINE) + w += ul_s_width; + if (a & AT_BOLD) + w += bo_s_width; + if (a & AT_BLINK) + w += bl_s_width; + if (a & AT_STANDOUT) + w += so_s_width; + + return w; +} + +/* + * Return the printing width of the end (exit) sequence + * for a given character attribute. + */ + static int +attr_ewidth(a) + int a; +{ + int w = 0; + + a = apply_at_specials(a); + + if (a & AT_UNDERLINE) + w += ul_e_width; + if (a & AT_BOLD) + w += bo_e_width; + if (a & AT_BLINK) + w += bl_e_width; + if (a & AT_STANDOUT) + w += so_e_width; + + return w; +} + +/* + * Return the printing width of a given character and attribute, + * if the character were added after prev_ch. + * Adding a character with a given attribute may cause an enter or exit + * attribute sequence to be inserted, so this must be taken into account. + */ + public int +pwidth(ch, a, prev_ch, prev_a) + LWCHAR ch; + int a; + LWCHAR prev_ch; + int prev_a; +{ + int w; + + if (ch == '\b') + { + /* + * Backspace moves backwards one or two positions. + */ + if (prev_a & (AT_ANSI|AT_BINARY)) + return strlen(prchar('\b')); + return (utf_mode && is_wide_char(prev_ch)) ? -2 : -1; + } + + if (!utf_mode || is_ascii_char(ch)) + { + if (control_char((char)ch)) + { + /* + * Control characters do unpredictable things, + * so we don't even try to guess; say it doesn't move. + * This can only happen if the -r flag is in effect. + */ + return (0); + } + } else + { + if (is_composing_char(ch) || is_combining_char(prev_ch, ch)) + { + /* + * Composing and combining chars take up no space. + * + * Some terminals, upon failure to compose a + * composing character with the character(s) that + * precede(s) it will actually take up one end_column + * for the composing character; there isn't much + * we could do short of testing the (complex) + * composition process ourselves and printing + * a binary representation when it fails. + */ + return (0); + } + } + + /* + * Other characters take one or two columns, + * plus the width of any attribute enter/exit sequence. + */ + w = 1; + if (is_wide_char(ch)) + w++; + if (linebuf.end > 0 && !is_at_equiv(linebuf.attr[linebuf.end-1], a)) + w += attr_ewidth(linebuf.attr[linebuf.end-1]); + if (apply_at_specials(a) != AT_NORMAL && + (linebuf.end == 0 || !is_at_equiv(linebuf.attr[linebuf.end-1], a))) + w += attr_swidth(a); + return (w); +} + +/* + * Delete to the previous base character in the line buffer. + */ + static int +backc(VOID_PARAM) +{ + LWCHAR ch; + char *p; + + if (linebuf.end == 0) + return (0); + p = &linebuf.buf[linebuf.end]; + ch = step_char(&p, -1, linebuf.buf); + /* Skip back to the next nonzero-width char. */ + while (p > linebuf.buf) + { + LWCHAR prev_ch; + int width; + linebuf.end = (int) (p - linebuf.buf); + prev_ch = step_char(&p, -1, linebuf.buf); + width = pwidth(ch, linebuf.attr[linebuf.end], prev_ch, linebuf.attr[linebuf.end-1]); + end_column -= width; + /* {{ right_column? }} */ + if (width > 0) + break; + ch = prev_ch; + } + return (1); +} + +/* + * Is a character the end of an ANSI escape sequence? + */ + public int +is_ansi_end(ch) + LWCHAR ch; +{ + if (!is_ascii_char(ch)) + return (0); + return (strchr(end_ansi_chars, (char) ch) != NULL); +} + +/* + * Can a char appear in an ANSI escape sequence, before the end char? + */ + public int +is_ansi_middle(ch) + LWCHAR ch; +{ + if (!is_ascii_char(ch)) + return (0); + if (is_ansi_end(ch)) + return (0); + return (strchr(mid_ansi_chars, (char) ch) != NULL); +} + +/* + * Skip past an ANSI escape sequence. + * pp is initially positioned just after the CSI_START char. + */ + public void +skip_ansi(pansi, pp, limit) + struct ansi_state *pansi; + char **pp; + constant char *limit; +{ + LWCHAR c; + do { + c = step_char(pp, +1, limit); + } while (*pp < limit && ansi_step(pansi, c) == ANSI_MID); + /* Note that we discard final char, for which is_ansi_end is true. */ +} + +/* + * Determine if a character starts an ANSI escape sequence. + * If so, return an ansi_state struct; otherwise return NULL. + */ + public struct ansi_state * +ansi_start(ch) + LWCHAR ch; +{ + struct ansi_state *pansi; + + if (!IS_CSI_START(ch)) + return NULL; + pansi = ecalloc(1, sizeof(struct ansi_state)); + pansi->hindex = 0; + pansi->hlink = 0; + pansi->prev_esc = 0; + return pansi; +} + +/* + * Determine whether the next char in an ANSI escape sequence + * ends the sequence. + */ + public int +ansi_step(pansi, ch) + struct ansi_state *pansi; + LWCHAR ch; +{ + if (pansi->hlink) + { + /* Hyperlink ends with \7 or ESC-backslash. */ + if (ch == '\7') + return ANSI_END; + if (pansi->prev_esc && ch == '\\') + return ANSI_END; + pansi->prev_esc = (ch == ESC); + return ANSI_MID; + } + if (pansi->hindex >= 0) + { + static char hlink_prefix[] = ESCS "]8;"; + if (ch == hlink_prefix[pansi->hindex] || + (pansi->hindex == 0 && IS_CSI_START(ch))) + { + pansi->hindex++; + if (hlink_prefix[pansi->hindex] == '\0') + pansi->hlink = 1; /* now processing hyperlink addr */ + return ANSI_MID; + } + pansi->hindex = -1; /* not a hyperlink */ + } + /* Check for SGR sequences */ + if (is_ansi_middle(ch)) + return ANSI_MID; + if (is_ansi_end(ch)) + return ANSI_END; + return ANSI_ERR; +} + +/* + * Free an ansi_state structure. + */ + public void +ansi_done(pansi) + struct ansi_state *pansi; +{ + free(pansi); +} + +/* + * Append a character and attribute to the line buffer. + */ +#define STORE_CHAR(ch,a,rep,pos) \ + do { \ + if (store_char((ch),(a),(rep),(pos))) return (1); \ + } while (0) + + static int +store_char(ch, a, rep, pos) + LWCHAR ch; + int a; + char *rep; + POSITION pos; +{ + int w; + int i; + int replen; + char cs; + + i = (a & (AT_UNDERLINE|AT_BOLD)); + if (i != AT_NORMAL) + last_overstrike = i; + +#if HILITE_SEARCH + { + int matches; + int hl_attr = is_hilited_attr(pos, pos+1, 0, &matches); + if (hl_attr) + { + /* + * This character should be highlighted. + * Override the attribute passed in. + */ + if (a != AT_ANSI) + { + if (highest_hilite != NULL_POSITION && pos > highest_hilite) + highest_hilite = pos; + a |= hl_attr; + } + } + } +#endif + + if (a == AT_ANSI) { + w = 0; + } else { + char *p = &linebuf.buf[linebuf.end]; + LWCHAR prev_ch = (linebuf.end > 0) ? step_char(&p, -1, linebuf.buf) : 0; + int prev_a = (linebuf.end > 0) ? linebuf.attr[linebuf.end-1] : 0; + w = pwidth(ch, a, prev_ch, prev_a); + } + + if (ctldisp != OPT_ON && end_column - cshift + w + attr_ewidth(a) > sc_width) + /* + * Won't fit on screen. + */ + return (1); + + if (rep == NULL) + { + cs = (char) ch; + rep = &cs; + replen = 1; + } else + { + replen = utf_len(rep[0]); + } + if (linebuf.end + replen >= size_linebuf-6) + { + /* + * Won't fit in line buffer. + * Try to expand it. + */ + if (expand_linebuf()) + return (1); + } + + if (cshift == hshift && shifted_ansi.end > 0) + { + /* Copy shifted ANSI sequences to beginning of line. */ + for (i = 0; i < shifted_ansi.end; i++) + add_linebuf(shifted_ansi.buf[i], AT_ANSI, 0); + shifted_ansi.end = 0; + } + /* Add the char to the buf, even if we will left-shift it next. */ + inc_end_column(w); + for (i = 0; i < replen; i++) + add_linebuf(*rep++, a, 0); + + if (cshift < hshift) + { + /* We haven't left-shifted enough yet. */ + if (a == AT_ANSI) + add_ansi(ch); /* Save ANSI attributes */ + if (linebuf.end > linebuf.print) + { + /* Shift left enough to put last byte of this char at print-1. */ + memcpy(&linebuf.buf[0], &linebuf.buf[replen], linebuf.print); + memcpy(&linebuf.attr[0], &linebuf.attr[replen], linebuf.print); + linebuf.end -= replen; + cshift += w; + /* + * If the char we just left-shifted was double width, + * the 2 spaces we shifted may be too much. + * Represent the "half char" at start of line with a highlighted space. + */ + while (cshift > hshift) + { + add_linebuf(' ', rscroll_attr, 0); + cshift--; + } + } + } + return (0); +} + +/* + * Append a tab to the line buffer. + * Store spaces to represent the tab. + */ +#define STORE_TAB(a,pos) \ + do { if (store_tab((a),(pos))) return (1); } while (0) + + static int +store_tab(attr, pos) + int attr; + POSITION pos; +{ + int to_tab = end_column - linebuf.pfx_end; + + if (ntabstops < 2 || to_tab >= tabstops[ntabstops-1]) + to_tab = tabdefault - + ((to_tab - tabstops[ntabstops-1]) % tabdefault); + else + { + int i; + for (i = ntabstops - 2; i >= 0; i--) + if (to_tab >= tabstops[i]) + break; + to_tab = tabstops[i+1] - to_tab; + } + + do { + STORE_CHAR(' ', attr, " ", pos); + } while (--to_tab > 0); + return 0; +} + +#define STORE_PRCHAR(c, pos) \ + do { if (store_prchar((c), (pos))) return 1; } while (0) + + static int +store_prchar(c, pos) + LWCHAR c; + POSITION pos; +{ + char *s; + + /* + * Convert to printable representation. + */ + s = prchar(c); + for ( ; *s != 0; s++) + STORE_CHAR(*s, AT_BINARY|AT_COLOR_CTRL, NULL, pos); + return 0; +} + + static int +flush_mbc_buf(pos) + POSITION pos; +{ + int i; + + for (i = 0; i < mbc_buf_index; i++) + if (store_prchar(mbc_buf[i], pos)) + return mbc_buf_index - i; + return 0; +} + +/* + * Append a character to the line buffer. + * Expand tabs into spaces, handle underlining, boldfacing, etc. + * Returns 0 if ok, 1 if couldn't fit in buffer. + */ + public int +pappend(c, pos) + int c; + POSITION pos; +{ + int r; + + if (pendc) + { + if (c == '\r' && pendc == '\r') + return (0); + if (do_append(pendc, NULL, pendpos)) + /* + * Oops. We've probably lost the char which + * was in pendc, since caller won't back up. + */ + return (1); + pendc = '\0'; + } + + if (c == '\r' && bs_mode == BS_SPECIAL) + { + if (mbc_buf_len > 0) /* utf_mode must be on. */ + { + /* Flush incomplete (truncated) sequence. */ + r = flush_mbc_buf(mbc_pos); + mbc_buf_index = r + 1; + mbc_buf_len = 0; + if (r) + return (mbc_buf_index); + } + + /* + * Don't put the CR into the buffer until we see + * the next char. If the next char is a newline, + * discard the CR. + */ + pendc = c; + pendpos = pos; + return (0); + } + + if (!utf_mode) + { + r = do_append(c, NULL, pos); + } else + { + /* Perform strict validation in all possible cases. */ + if (mbc_buf_len == 0) + { + retry: + mbc_buf_index = 1; + *mbc_buf = c; + if (IS_ASCII_OCTET(c)) + r = do_append(c, NULL, pos); + else if (IS_UTF8_LEAD(c)) + { + mbc_buf_len = utf_len(c); + mbc_pos = pos; + return (0); + } else + /* UTF8_INVALID or stray UTF8_TRAIL */ + r = flush_mbc_buf(pos); + } else if (IS_UTF8_TRAIL(c)) + { + mbc_buf[mbc_buf_index++] = c; + if (mbc_buf_index < mbc_buf_len) + return (0); + if (is_utf8_well_formed(mbc_buf, mbc_buf_index)) + r = do_append(get_wchar(mbc_buf), mbc_buf, mbc_pos); + else + /* Complete, but not shortest form, sequence. */ + mbc_buf_index = r = flush_mbc_buf(mbc_pos); + mbc_buf_len = 0; + } else + { + /* Flush incomplete (truncated) sequence. */ + r = flush_mbc_buf(mbc_pos); + mbc_buf_index = r + 1; + mbc_buf_len = 0; + /* Handle new char. */ + if (!r) + goto retry; + } + } + if (r) + { + /* How many chars should caller back up? */ + r = (!utf_mode) ? 1 : mbc_buf_index; + } + return (r); +} + + static int +store_control_char(ch, rep, pos) + LWCHAR ch; + char *rep; + POSITION pos; +{ + if (ctldisp == OPT_ON) + { + /* Output the character itself. */ + STORE_CHAR(ch, AT_NORMAL, rep, pos); + } else + { + /* Output a printable representation of the character. */ + STORE_PRCHAR((char) ch, pos); + } + return (0); +} + + static int +store_ansi(ch, rep, pos) + LWCHAR ch; + char *rep; + POSITION pos; +{ + switch (ansi_step(line_ansi, ch)) + { + case ANSI_MID: + STORE_CHAR(ch, AT_ANSI, rep, pos); + break; + case ANSI_END: + STORE_CHAR(ch, AT_ANSI, rep, pos); + ansi_done(line_ansi); + line_ansi = NULL; + break; + case ANSI_ERR: { + /* Remove whole unrecognized sequence. */ + char *start = (cshift < hshift) ? shifted_ansi.buf : linebuf.buf; + int *end = (cshift < hshift) ? &shifted_ansi.end : &linebuf.end; + char *p = start + *end; + LWCHAR bch; + do { + bch = step_char(&p, -1, start); + } while (p > start && !IS_CSI_START(bch)); + *end = (int) (p - start); + ansi_done(line_ansi); + line_ansi = NULL; + break; } + } + return (0); +} + + static int +store_bs(ch, rep, pos) + LWCHAR ch; + char *rep; + POSITION pos; +{ + if (bs_mode == BS_CONTROL) + return store_control_char(ch, rep, pos); + if (linebuf.end > 0 && + ((linebuf.end <= linebuf.print && linebuf.buf[linebuf.end-1] == '\0') || + (linebuf.end > 0 && linebuf.attr[linebuf.end - 1] & (AT_ANSI|AT_BINARY)))) + STORE_PRCHAR('\b', pos); + else if (bs_mode == BS_NORMAL) + STORE_CHAR(ch, AT_NORMAL, NULL, pos); + else if (bs_mode == BS_SPECIAL) + overstrike = backc(); + return 0; +} + + static int +do_append(ch, rep, pos) + LWCHAR ch; + char *rep; + POSITION pos; +{ + int a = AT_NORMAL; + + if (ctldisp == OPT_ONPLUS && line_ansi == NULL) + line_ansi = ansi_start(ch); + + if (line_ansi != NULL) + return store_ansi(ch, rep, pos); + + if (ch == '\b') + return store_bs(ch, rep, pos); + + if (overstrike > 0) + { + /* + * Overstrike the character at the current position + * in the line buffer. This will cause either + * underline (if a "_" is overstruck), + * bold (if an identical character is overstruck), + * or just replacing the character in the buffer. + */ + LWCHAR prev_ch; + overstrike = utf_mode ? -1 : 0; + if (utf_mode) + { + /* To be correct, this must be a base character. */ + prev_ch = get_wchar(&linebuf.buf[linebuf.end]); + } else + { + prev_ch = (unsigned char) linebuf.buf[linebuf.end]; + } + a = linebuf.attr[linebuf.end]; + if (ch == prev_ch) + { + /* + * Overstriking a char with itself means make it bold. + * But overstriking an underscore with itself is + * ambiguous. It could mean make it bold, or + * it could mean make it underlined. + * Use the previous overstrike to resolve it. + */ + if (ch == '_') + { + if ((a & (AT_BOLD|AT_UNDERLINE)) != AT_NORMAL) + a |= (AT_BOLD|AT_UNDERLINE); + else if (last_overstrike != AT_NORMAL) + a |= last_overstrike; + else + a |= AT_BOLD; + } else + a |= AT_BOLD; + } else if (ch == '_') + { + a |= AT_UNDERLINE; + ch = prev_ch; + rep = &linebuf.buf[linebuf.end]; + } else if (prev_ch == '_') + { + a |= AT_UNDERLINE; + } + /* Else we replace prev_ch, but we keep its attributes. */ + } else if (overstrike < 0) + { + if ( is_composing_char(ch) + || is_combining_char(get_wchar(&linebuf.buf[linebuf.end]), ch)) + /* Continuation of the same overstrike. */ + a = last_overstrike; + else + overstrike = 0; + } + + if (ch == '\t') + { + /* + * Expand a tab into spaces. + */ + switch (bs_mode) + { + case BS_CONTROL: + return store_control_char(ch, rep, pos); + case BS_NORMAL: + case BS_SPECIAL: + STORE_TAB(a, pos); + break; + } + return (0); + } + if ((!utf_mode || is_ascii_char(ch)) && control_char((char)ch)) + { + return store_control_char(ch, rep, pos); + } else if (utf_mode && ctldisp != OPT_ON && is_ubin_char(ch)) + { + char *s = prutfchar(ch); + for ( ; *s != 0; s++) + STORE_CHAR(*s, AT_BINARY, NULL, pos); + } else + { + STORE_CHAR(ch, a, rep, pos); + } + return (0); +} + +/* + * + */ + public int +pflushmbc(VOID_PARAM) +{ + int r = 0; + + if (mbc_buf_len > 0) + { + /* Flush incomplete (truncated) sequence. */ + r = flush_mbc_buf(mbc_pos); + mbc_buf_len = 0; + } + return r; +} + +/* + * Switch to normal attribute at end of line. + */ + static void +add_attr_normal(VOID_PARAM) +{ + char *p = "\033[m"; + + if (ctldisp != OPT_ONPLUS || !is_ansi_end('m')) + return; + for ( ; *p != '\0'; p++) + add_linebuf(*p, AT_ANSI, 0); +} + +/* + * Terminate the line in the line buffer. + */ + public void +pdone(endline, chopped, forw) + int endline; + int chopped; + int forw; +{ + (void) pflushmbc(); + + if (pendc && (pendc != '\r' || !endline)) + /* + * If we had a pending character, put it in the buffer. + * But discard a pending CR if we are at end of line + * (that is, discard the CR in a CR/LF sequence). + */ + (void) do_append(pendc, NULL, pendpos); + + if (chopped && rscroll_char) + { + /* + * Display the right scrolling char. + * If we've already filled the rightmost screen char + * (in the buffer), overwrite it. + */ + if (end_column >= sc_width + cshift) + { + /* We've already written in the rightmost char. */ + end_column = right_column; + linebuf.end = right_curr; + } + add_attr_normal(); + while (end_column < sc_width-1 + cshift) + { + /* + * Space to last (rightmost) char on screen. + * This may be necessary if the char we overwrote + * was double-width. + */ + add_linebuf(' ', rscroll_attr, 1); + } + /* Print rscroll char. It must be single-width. */ + add_linebuf(rscroll_char, rscroll_attr, 1); + } else + { + add_attr_normal(); + } + + /* + * Add a newline if necessary, + * and append a '\0' to the end of the line. + * We output a newline if we're not at the right edge of the screen, + * or if the terminal doesn't auto wrap, + * or if this is really the end of the line AND the terminal ignores + * a newline at the right edge. + * (In the last case we don't want to output a newline if the terminal + * doesn't ignore it since that would produce an extra blank line. + * But we do want to output a newline if the terminal ignores it in case + * the next line is blank. In that case the single newline output for + * that blank line would be ignored!) + */ + if (end_column < sc_width + cshift || !auto_wrap || (endline && ignaw) || ctldisp == OPT_ON) + { + add_linebuf('\n', AT_NORMAL, 0); + } + else if (ignaw && end_column >= sc_width + cshift && forw) + { + /* + * Terminals with "ignaw" don't wrap until they *really* need + * to, i.e. when the character *after* the last one to fit on a + * line is output. But they are too hard to deal with when they + * get in the state where a full screen width of characters + * have been output but the cursor is sitting on the right edge + * instead of at the start of the next line. + * So we nudge them into wrapping by outputting a space + * character plus a backspace. But do this only if moving + * forward; if we're moving backward and drawing this line at + * the top of the screen, the space would overwrite the first + * char on the next line. We don't need to do this "nudge" + * at the top of the screen anyway. + */ + add_linebuf(' ', AT_NORMAL, 1); + add_linebuf('\b', AT_NORMAL, -1); + } + set_linebuf(linebuf.end, '\0', AT_NORMAL); +} + +/* + * + */ + public void +set_status_col(c, attr) + int c; + int attr; +{ + set_pfx(0, c, attr); +} + +/* + * Get a character from the current line. + * Return the character as the function return value, + * and the character attribute in *ap. + */ + public int +gline(i, ap) + int i; + int *ap; +{ + if (is_null_line) + { + /* + * If there is no current line, we pretend the line is + * either "~" or "", depending on the "twiddle" flag. + */ + if (twiddle) + { + if (i == 0) + { + *ap = AT_BOLD; + return '~'; + } + --i; + } + /* Make sure we're back to AT_NORMAL before the '\n'. */ + *ap = AT_NORMAL; + return i ? '\0' : '\n'; + } + + if (i < linebuf.pfx_end) + { + *ap = linebuf.pfx_attr[i]; + return linebuf.pfx[i]; + } + i += linebuf.print - linebuf.pfx_end; + *ap = linebuf.attr[i]; + return (linebuf.buf[i] & 0xFF); +} + +/* + * Indicate that there is no current line. + */ + public void +null_line(VOID_PARAM) +{ + is_null_line = 1; + cshift = 0; +} + +/* + * Analogous to forw_line(), but deals with "raw lines": + * lines which are not split for screen width. + * {{ This is supposed to be more efficient than forw_line(). }} + */ + public POSITION +forw_raw_line(curr_pos, linep, line_lenp) + POSITION curr_pos; + char **linep; + int *line_lenp; +{ + int n; + int c; + POSITION new_pos; + + if (curr_pos == NULL_POSITION || ch_seek(curr_pos) || + (c = ch_forw_get()) == EOI) + return (NULL_POSITION); + + n = 0; + for (;;) + { + if (c == '\n' || c == EOI || ABORT_SIGS()) + { + new_pos = ch_tell(); + break; + } + if (n >= size_linebuf-1) + { + if (expand_linebuf()) + { + /* + * Overflowed the input buffer. + * Pretend the line ended here. + */ + new_pos = ch_tell() - 1; + break; + } + } + linebuf.buf[n++] = c; + c = ch_forw_get(); + } + linebuf.buf[n] = '\0'; + if (linep != NULL) + *linep = linebuf.buf; + if (line_lenp != NULL) + *line_lenp = n; + return (new_pos); +} + +/* + * Analogous to back_line(), but deals with "raw lines". + * {{ This is supposed to be more efficient than back_line(). }} + */ + public POSITION +back_raw_line(curr_pos, linep, line_lenp) + POSITION curr_pos; + char **linep; + int *line_lenp; +{ + int n; + int c; + POSITION new_pos; + + if (curr_pos == NULL_POSITION || curr_pos <= ch_zero() || + ch_seek(curr_pos-1)) + return (NULL_POSITION); + + n = size_linebuf; + linebuf.buf[--n] = '\0'; + for (;;) + { + c = ch_back_get(); + if (c == '\n' || ABORT_SIGS()) + { + /* + * This is the newline ending the previous line. + * We have hit the beginning of the line. + */ + new_pos = ch_tell() + 1; + break; + } + if (c == EOI) + { + /* + * We have hit the beginning of the file. + * This must be the first line in the file. + * This must, of course, be the beginning of the line. + */ + new_pos = ch_zero(); + break; + } + if (n <= 0) + { + int old_size_linebuf = size_linebuf; + char *fm; + char *to; + if (expand_linebuf()) + { + /* + * Overflowed the input buffer. + * Pretend the line ended here. + */ + new_pos = ch_tell() + 1; + break; + } + /* + * Shift the data to the end of the new linebuf. + */ + for (fm = linebuf.buf + old_size_linebuf - 1, + to = linebuf.buf + size_linebuf - 1; + fm >= linebuf.buf; fm--, to--) + *to = *fm; + n = size_linebuf - old_size_linebuf; + } + linebuf.buf[--n] = c; + } + if (linep != NULL) + *linep = &linebuf.buf[n]; + if (line_lenp != NULL) + *line_lenp = size_linebuf - 1 - n; + return (new_pos); +} + +/* + * Find the shift necessary to show the end of the longest displayed line. + */ + public int +rrshift(VOID_PARAM) +{ + POSITION pos; + int save_width; + int line; + int longest = 0; + + save_width = sc_width; + sc_width = INT_MAX; + pos = position(TOP); + for (line = 0; line < sc_height && pos != NULL_POSITION; line++) + { + pos = forw_line(pos); + if (end_column > longest) + longest = end_column; + } + sc_width = save_width; + if (longest < sc_width) + return 0; + return longest - sc_width; +} + +/* + * Get the color_map index associated with a given attribute. + */ + static int +color_index(attr) + int attr; +{ + if (use_color) + { + switch (attr & AT_COLOR) + { + case AT_COLOR_ATTN: return 0; + case AT_COLOR_BIN: return 1; + case AT_COLOR_CTRL: return 2; + case AT_COLOR_ERROR: return 3; + case AT_COLOR_LINENUM: return 4; + case AT_COLOR_MARK: return 5; + case AT_COLOR_PROMPT: return 6; + case AT_COLOR_RSCROLL: return 7; + case AT_COLOR_SEARCH: return 8; + } + } + if (attr & AT_UNDERLINE) + return 9; + if (attr & AT_BOLD) + return 10; + if (attr & AT_BLINK) + return 11; + if (attr & AT_STANDOUT) + return 12; + return -1; +} + +/* + * Set the color string to use for a given attribute. + */ + public int +set_color_map(attr, colorstr) + int attr; + char *colorstr; +{ + int cx = color_index(attr); + if (cx < 0) + return -1; + if (strlen(colorstr)+1 > sizeof(color_map[cx])) + return -1; + if (*colorstr != '\0' && parse_color(colorstr, NULL, NULL) == CT_NULL) + return -1; + strcpy(color_map[cx], colorstr); + return 0; +} + +/* + * Get the color string to use for a given attribute. + */ + public char * +get_color_map(attr) + int attr; +{ + int cx = color_index(attr); + if (cx < 0) + return NULL; + return color_map[cx]; +} |
