version 1.9, 2006/01/11 19:20:10 |
version 1.10, 2011/09/16 18:12:09 |
|
|
/* |
/* |
* Copyright (C) 1984-2002 Mark Nudelman |
* Copyright (C) 1984-2011 Mark Nudelman |
* |
* |
* You may distribute under the terms of either the GNU General Public |
* You may distribute under the terms of either the GNU General Public |
* License or the Less License, as specified in the README file. |
* License or the Less License, as specified in the README file. |
|
|
*/ |
*/ |
|
|
#include "less.h" |
#include "less.h" |
|
#include "charset.h" |
|
|
#define IS_CONT(c) (((c) & 0xC0) == 0x80) |
#include <err.h> |
|
|
public char *linebuf = NULL; /* Buffer which holds the current output line */ |
static char *linebuf = NULL; /* Buffer which holds the current output line */ |
static char *attr = NULL; /* Extension of linebuf to hold attributes */ |
static char *attr = NULL; /* Extension of linebuf to hold attributes */ |
public int size_linebuf = 0; /* Size of line buffer (and attr buffer) */ |
public int size_linebuf = 0; /* Size of line buffer (and attr buffer) */ |
|
|
public int cshift; /* Current left-shift of output line buffer */ |
static int cshift; /* Current left-shift of output line buffer */ |
public int hshift; /* Desired 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 tabstops[TABSTOP_MAX] = { 0 }; /* Custom tabstops */ |
public int ntabstops = 1; /* Number of tabstops */ |
public int ntabstops = 1; /* Number of tabstops */ |
|
|
static int last_overstrike = AT_NORMAL; |
static int last_overstrike = AT_NORMAL; |
static int is_null_line; /* There is no current line */ |
static int is_null_line; /* There is no current line */ |
static int lmargin; /* Left margin */ |
static int lmargin; /* Left margin */ |
static int hilites; /* Number of hilites in this line */ |
|
static char pendc; |
static char pendc; |
static POSITION pendpos; |
static POSITION pendpos; |
static char *end_ansi_chars; |
static char *end_ansi_chars; |
|
static char *mid_ansi_chars; |
|
|
|
static int attr_swidth(); |
|
static int attr_ewidth(); |
static int do_append(); |
static int do_append(); |
|
|
|
extern int sigs; |
extern int bs_mode; |
extern int bs_mode; |
extern int linenums; |
extern int linenums; |
extern int ctldisp; |
extern int ctldisp; |
|
|
extern POSITION start_attnpos; |
extern POSITION start_attnpos; |
extern POSITION end_attnpos; |
extern POSITION end_attnpos; |
|
|
|
static char mbc_buf[MAX_UTF_CHAR_LEN]; |
|
static int mbc_buf_len = 0; |
|
static int mbc_buf_index = 0; |
|
static POSITION mbc_pos; |
|
|
/* |
/* |
* Initialize from environment variables. |
* Initialize from environment variables. |
*/ |
*/ |
|
|
end_ansi_chars = lgetenv("LESSANSIENDCHARS"); |
end_ansi_chars = lgetenv("LESSANSIENDCHARS"); |
if (end_ansi_chars == NULL || *end_ansi_chars == '\0') |
if (end_ansi_chars == NULL || *end_ansi_chars == '\0') |
end_ansi_chars = "m"; |
end_ansi_chars = "m"; |
|
|
|
mid_ansi_chars = lgetenv("LESSANSIMIDCHARS"); |
|
if (mid_ansi_chars == NULL || *mid_ansi_chars == '\0') |
|
mid_ansi_chars = "0123456789;[?!\"'#%()*+ "; |
|
|
linebuf = (char *) ecalloc(LINEBUF_SIZE, sizeof(char)); |
linebuf = (char *) ecalloc(LINEBUF_SIZE, sizeof(char)); |
attr = (char *) ecalloc(LINEBUF_SIZE, sizeof(char)); |
attr = (char *) ecalloc(LINEBUF_SIZE, sizeof(char)); |
size_linebuf = LINEBUF_SIZE; |
size_linebuf = LINEBUF_SIZE; |
|
|
/* |
/* |
* Expand the line buffer. |
* Expand the line buffer. |
*/ |
*/ |
static int |
static int |
expand_linebuf() |
expand_linebuf() |
{ |
{ |
|
/* Double the size of the line buffer. */ |
int new_size = size_linebuf * 2; |
int new_size = size_linebuf * 2; |
|
|
|
/* Just realloc to expand the buffer, if we can. */ |
char *new_buf; |
char *new_buf; |
char *new_attr; |
char *new_attr; |
|
|
|
|
err(1, NULL); |
err(1, NULL); |
return 1; |
return 1; |
} |
} |
|
/* |
|
* We realloc'd the buffers; they already have the old contents. |
|
*/ |
memset(new_buf + size_linebuf, 0, new_size - size_linebuf); |
memset(new_buf + size_linebuf, 0, new_size - size_linebuf); |
memset(new_attr + size_linebuf, 0, new_size - size_linebuf); |
memset(new_attr + size_linebuf, 0, new_size - size_linebuf); |
linebuf = new_buf; |
linebuf = new_buf; |
|
|
} |
} |
|
|
/* |
/* |
|
* Is a character ASCII? |
|
*/ |
|
public int |
|
is_ascii_char(ch) |
|
LWCHAR ch; |
|
{ |
|
return (ch <= 0x7F); |
|
} |
|
|
|
/* |
* Rewind the line buffer. |
* Rewind the line buffer. |
*/ |
*/ |
public void |
public void |
|
|
{ |
{ |
curr = 0; |
curr = 0; |
column = 0; |
column = 0; |
|
cshift = 0; |
overstrike = 0; |
overstrike = 0; |
|
last_overstrike = AT_NORMAL; |
|
mbc_buf_len = 0; |
is_null_line = 0; |
is_null_line = 0; |
pendc = '\0'; |
pendc = '\0'; |
lmargin = 0; |
lmargin = 0; |
if (status_col) |
if (status_col) |
lmargin += 1; |
lmargin += 1; |
#if HILITE_SEARCH |
|
hilites = 0; |
|
#endif |
|
} |
} |
|
|
/* |
/* |
|
|
linebuf[curr] = ' '; |
linebuf[curr] = ' '; |
if (start_attnpos != NULL_POSITION && |
if (start_attnpos != NULL_POSITION && |
pos >= start_attnpos && pos < end_attnpos) |
pos >= start_attnpos && pos < end_attnpos) |
attr[curr] = AT_STANDOUT; |
attr[curr] = AT_NORMAL|AT_HILITE; |
else |
else |
attr[curr] = 0; |
attr[curr] = AT_NORMAL; |
curr++; |
curr++; |
column++; |
column++; |
} |
} |
|
|
} |
} |
|
|
/* |
/* |
* Determine how many characters are required to shift N columns. |
* Shift the input line left. |
|
* This means discarding N printable chars at the start of the buffer. |
*/ |
*/ |
static int |
static void |
shift_chars(s, len) |
pshift(shift) |
char *s; |
int shift; |
int len; |
|
{ |
{ |
char *p = s; |
LWCHAR prev_ch = 0; |
|
unsigned char c; |
|
int shifted = 0; |
|
int to; |
|
int from; |
|
int len; |
|
int width; |
|
int prev_attr; |
|
int next_attr; |
|
|
|
if (shift > column - lmargin) |
|
shift = column - lmargin; |
|
if (shift > curr - lmargin) |
|
shift = curr - lmargin; |
|
|
|
to = from = lmargin; |
/* |
/* |
* Each char counts for one column, except ANSI color escape |
* We keep on going when shifted == shift |
* sequences use no columns since they don't move the cursor. |
* to get all combining chars. |
*/ |
*/ |
while (*p != '\0' && len > 0) |
while (shifted <= shift && from < curr) |
{ |
{ |
if (*p++ != ESC) |
c = linebuf[from]; |
|
if (ctldisp == OPT_ONPLUS && IS_CSI_START(c)) |
{ |
{ |
len--; |
/* Keep cumulative effect. */ |
|
linebuf[to] = c; |
|
attr[to++] = attr[from++]; |
|
while (from < curr && linebuf[from]) |
|
{ |
|
linebuf[to] = linebuf[from]; |
|
attr[to++] = attr[from]; |
|
if (!is_ansi_middle(linebuf[from++])) |
|
break; |
|
} |
|
continue; |
|
} |
|
|
|
width = 0; |
|
|
|
#if !SMALL |
|
if (!IS_ASCII_OCTET(c) && utf_mode) |
|
{ |
|
/* Assumes well-formedness validation already done. */ |
|
LWCHAR ch; |
|
|
|
len = utf_len(c); |
|
if (from + len > curr) |
|
break; |
|
ch = get_wchar(linebuf + from); |
|
if (!is_composing_char(ch) && !is_combining_char(prev_ch, ch)) |
|
width = is_wide_char(ch) ? 2 : 1; |
|
prev_ch = ch; |
} else |
} else |
|
#endif /* !SMALL */ |
{ |
{ |
while (*p != '\0') |
len = 1; |
|
if (c == '\b') |
|
/* XXX - Incorrect if several '\b' in a row. */ |
|
#if !SMALL |
|
width = (utf_mode && is_wide_char(prev_ch)) ? -2 : -1; |
|
#else |
|
width = -1; |
|
#endif /* !SMALL */ |
|
else if (!control_char(c)) |
|
width = 1; |
|
prev_ch = 0; |
|
} |
|
|
|
if (width == 2 && shift - shifted == 1) { |
|
/* Should never happen when called by pshift_all(). */ |
|
attr[to] = attr[from]; |
|
/* |
|
* Assume a wide_char will never be the first half of a |
|
* combining_char pair, so reset prev_ch in case we're |
|
* followed by a '\b'. |
|
*/ |
|
prev_ch = linebuf[to++] = ' '; |
|
from += len; |
|
shifted++; |
|
continue; |
|
} |
|
|
|
/* Adjust width for magic cookies. */ |
|
prev_attr = (to > 0) ? attr[to-1] : AT_NORMAL; |
|
next_attr = (from + len < curr) ? attr[from + len] : prev_attr; |
|
if (!is_at_equiv(attr[from], prev_attr) && |
|
!is_at_equiv(attr[from], next_attr)) |
|
{ |
|
width += attr_swidth(attr[from]); |
|
if (from + len < curr) |
|
width += attr_ewidth(attr[from]); |
|
if (is_at_equiv(prev_attr, next_attr)) |
{ |
{ |
if (is_ansi_end(*p++)) |
width += attr_ewidth(prev_attr); |
break; |
if (from + len < curr) |
|
width += attr_swidth(next_attr); |
} |
} |
} |
} |
} |
|
return (p - s); |
|
} |
|
|
|
/* |
if (shift - shifted < width) |
* Determine how many characters are required to shift N columns (UTF version). |
break; |
* {{ FIXME: what about color escape sequences in UTF mode? }} |
from += len; |
*/ |
shifted += width; |
static int |
if (shifted < 0) |
utf_shift_chars(s, len) |
shifted = 0; |
char *s; |
|
int len; |
|
{ |
|
int ulen = 0; |
|
|
|
while (*s != '\0' && len > 0) |
|
{ |
|
if (!IS_CONT(*s)) |
|
len--; |
|
s++; |
|
ulen++; |
|
} |
} |
while (IS_CONT(*s)) |
while (from < curr) |
{ |
{ |
s++; |
linebuf[to] = linebuf[from]; |
ulen++; |
attr[to++] = attr[from++]; |
} |
} |
return (ulen); |
curr = to; |
|
column -= shifted; |
|
cshift += shifted; |
} |
} |
|
|
/* |
/* |
* Shift the input line left. |
* |
* This means discarding N printable chars at the start of the buffer. |
|
*/ |
*/ |
static void |
public void |
pshift(shift) |
pshift_all() |
int shift; |
|
{ |
{ |
int i; |
pshift(column); |
int nchars; |
|
|
|
if (shift > column - lmargin) |
|
shift = column - lmargin; |
|
if (shift > curr - lmargin) |
|
shift = curr - lmargin; |
|
|
|
if (utf_mode) |
|
nchars = utf_shift_chars(linebuf + lmargin, shift); |
|
else |
|
nchars = shift_chars(linebuf + lmargin, shift); |
|
if (nchars > curr) |
|
nchars = curr; |
|
for (i = 0; i < curr - nchars; i++) |
|
{ |
|
linebuf[lmargin + i] = linebuf[lmargin + i + nchars]; |
|
attr[lmargin + i] = attr[lmargin + i + nchars]; |
|
} |
|
curr -= nchars; |
|
column -= shift; |
|
cshift += shift; |
|
} |
} |
|
|
/* |
/* |
|
|
attr_swidth(a) |
attr_swidth(a) |
int a; |
int a; |
{ |
{ |
switch (a) |
int w = 0; |
{ |
|
case AT_BOLD: return (bo_s_width); |
a = apply_at_specials(a); |
case AT_UNDERLINE: return (ul_s_width); |
|
case AT_BLINK: return (bl_s_width); |
if (a & AT_UNDERLINE) |
case AT_STANDOUT: return (so_s_width); |
w += ul_s_width; |
} |
if (a & AT_BOLD) |
return (0); |
w += bo_s_width; |
|
if (a & AT_BLINK) |
|
w += bl_s_width; |
|
if (a & AT_STANDOUT) |
|
w += so_s_width; |
|
|
|
return w; |
} |
} |
|
|
/* |
/* |
|
|
attr_ewidth(a) |
attr_ewidth(a) |
int a; |
int a; |
{ |
{ |
switch (a) |
int w = 0; |
{ |
|
case AT_BOLD: return (bo_e_width); |
a = apply_at_specials(a); |
case AT_UNDERLINE: return (ul_e_width); |
|
case AT_BLINK: return (bl_e_width); |
if (a & AT_UNDERLINE) |
case AT_STANDOUT: return (so_e_width); |
w += ul_e_width; |
} |
if (a & AT_BOLD) |
return (0); |
w += bo_e_width; |
|
if (a & AT_BLINK) |
|
w += bl_e_width; |
|
if (a & AT_STANDOUT) |
|
w += so_e_width; |
|
|
|
return w; |
} |
} |
|
|
/* |
/* |
|
|
* attribute sequence to be inserted, so this must be taken into account. |
* attribute sequence to be inserted, so this must be taken into account. |
*/ |
*/ |
static int |
static int |
pwidth(c, a) |
pwidth(ch, a, prev_ch) |
int c; |
LWCHAR ch; |
int a; |
int a; |
|
LWCHAR prev_ch; |
{ |
{ |
register int w; |
int w; |
|
|
if (utf_mode && IS_CONT(c)) |
if (ch == '\b') |
return (0); |
|
|
|
if (c == '\b') |
|
/* |
/* |
* Backspace moves backwards one position. |
* Backspace moves backwards one or two positions. |
|
* XXX - Incorrect if several '\b' in a row. |
*/ |
*/ |
return (-1); |
#if !SMALL |
|
return (utf_mode && is_wide_char(prev_ch)) ? -2 : -1; |
|
#else |
|
return -1; |
|
#endif /* !SMALL */ |
|
|
if (control_char(c)) |
if (!utf_mode || is_ascii_char(ch)) |
/* |
{ |
* Control characters do unpredicatable things, |
if (control_char((char)ch)) |
* so we don't even try to guess; say it doesn't move. |
{ |
* This can only happen if the -r flag is in effect. |
/* |
*/ |
* Control characters do unpredictable things, |
return (0); |
* 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); |
|
} |
|
} |
|
#if !SMALL |
|
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 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); |
|
} |
|
} |
|
#endif /* !SMALL */ |
|
|
/* |
/* |
* Other characters take one space, |
* Other characters take one or two columns, |
* plus the width of any attribute enter/exit sequence. |
* plus the width of any attribute enter/exit sequence. |
*/ |
*/ |
w = 1; |
w = 1; |
if (curr > 0 && attr[curr-1] != a) |
#if !SMALL |
|
if (is_wide_char(ch)) |
|
w++; |
|
#endif /* !SMALL */ |
|
if (curr > 0 && !is_at_equiv(attr[curr-1], a)) |
w += attr_ewidth(attr[curr-1]); |
w += attr_ewidth(attr[curr-1]); |
if (a && (curr == 0 || attr[curr-1] != a)) |
if ((apply_at_specials(a) != AT_NORMAL) && |
|
(curr == 0 || !is_at_equiv(attr[curr-1], a))) |
w += attr_swidth(a); |
w += attr_swidth(a); |
return (w); |
return (w); |
} |
} |
|
|
/* |
/* |
* Delete the previous character in the line buffer. |
* Delete to the previous base character in the line buffer. |
|
* Return 1 if one is found. |
*/ |
*/ |
static void |
static int |
backc() |
backc() |
{ |
{ |
curr--; |
LWCHAR prev_ch; |
column -= pwidth(linebuf[curr], attr[curr]); |
char *p = linebuf + curr; |
|
LWCHAR ch = step_char(&p, -1, linebuf + lmargin); |
|
int width; |
|
|
|
/* This assumes that there is no '\b' in linebuf. */ |
|
while ( curr > lmargin |
|
&& column > lmargin |
|
&& (!(attr[curr - 1] & (AT_ANSI|AT_BINARY)))) |
|
{ |
|
curr = p - linebuf; |
|
prev_ch = step_char(&p, -1, linebuf + lmargin); |
|
width = pwidth(ch, attr[curr], prev_ch); |
|
column -= width; |
|
if (width > 0) |
|
return 1; |
|
ch = prev_ch; |
|
} |
|
|
|
return 0; |
} |
} |
|
|
/* |
/* |
|
|
static int |
static int |
in_ansi_esc_seq() |
in_ansi_esc_seq() |
{ |
{ |
int i; |
char *p; |
|
|
/* |
/* |
* Search backwards for either an ESC (which means we ARE in a seq); |
* Search backwards for either an ESC (which means we ARE in a seq); |
* or an end char (which means we're NOT in a seq). |
* or an end char (which means we're NOT in a seq). |
*/ |
*/ |
for (i = curr-1; i >= 0; i--) |
for (p = &linebuf[curr]; p > linebuf; ) |
{ |
{ |
if (linebuf[i] == ESC) |
LWCHAR ch = step_char(&p, -1, linebuf); |
|
if (IS_CSI_START(ch)) |
return (1); |
return (1); |
if (is_ansi_end(linebuf[i])) |
if (!is_ansi_middle(ch)) |
return (0); |
return (0); |
} |
} |
return (0); |
return (0); |
|
|
* Is a character the end of an ANSI escape sequence? |
* Is a character the end of an ANSI escape sequence? |
*/ |
*/ |
public int |
public int |
is_ansi_end(c) |
is_ansi_end(ch) |
char c; |
LWCHAR ch; |
{ |
{ |
return (strchr(end_ansi_chars, c) != NULL); |
if (!is_ascii_char(ch)) |
|
return (0); |
|
return (strchr(end_ansi_chars, (char) ch) != NULL); |
} |
} |
|
|
/* |
/* |
|
* |
|
*/ |
|
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); |
|
} |
|
|
|
/* |
* Append a character and attribute to the line buffer. |
* Append a character and attribute to the line buffer. |
*/ |
*/ |
#define STORE_CHAR(c,a,pos) \ |
#define STORE_CHAR(ch,a,rep,pos) \ |
do { if (store_char((c),(a),(pos))) return (1); else curr++; } while (0) |
do { \ |
|
if (store_char((ch),(a),(rep),(pos))) return (1); \ |
|
} while (0) |
|
|
static int |
static int |
store_char(c, a, pos) |
store_char(ch, a, rep, pos) |
int c; |
LWCHAR ch; |
int a; |
int a; |
|
char *rep; |
POSITION pos; |
POSITION pos; |
{ |
{ |
register int w; |
int w; |
|
int replen; |
|
char cs; |
|
|
if (a != AT_NORMAL) |
w = (a & (AT_UNDERLINE|AT_BOLD)); /* Pre-use w. */ |
last_overstrike = a; |
if (w != AT_NORMAL) |
|
last_overstrike = w; |
|
|
#if HILITE_SEARCH |
#if HILITE_SEARCH |
if (is_hilited(pos, pos+1, 0)) |
|
{ |
{ |
/* |
int matches; |
* This character should be highlighted. |
if (is_hilited(pos, pos+1, 0, &matches)) |
* Override the attribute passed in. |
{ |
*/ |
/* |
a = AT_STANDOUT; |
* This character should be highlighted. |
hilites++; |
* Override the attribute passed in. |
|
*/ |
|
if (a != AT_ANSI) |
|
a |= AT_HILITE; |
|
} |
} |
} |
#endif |
#endif |
|
|
if (ctldisp == OPT_ONPLUS && in_ansi_esc_seq()) |
if (ctldisp == OPT_ONPLUS && in_ansi_esc_seq()) |
|
{ |
|
if (!is_ansi_end(ch) && !is_ansi_middle(ch)) { |
|
/* Remove whole unrecognized sequence. */ |
|
char *p = &linebuf[curr]; |
|
LWCHAR bch; |
|
do { |
|
bch = step_char(&p, -1, linebuf); |
|
} while (p > linebuf && !IS_CSI_START(bch)); |
|
curr = p - linebuf; |
|
return 0; |
|
} |
|
a = AT_ANSI; /* Will force re-AT_'ing around it. */ |
w = 0; |
w = 0; |
|
} |
|
else if (ctldisp == OPT_ONPLUS && IS_CSI_START(ch)) |
|
{ |
|
a = AT_ANSI; /* Will force re-AT_'ing around it. */ |
|
w = 0; |
|
} |
else |
else |
w = pwidth(c, a); |
{ |
|
char *p = &linebuf[curr]; |
|
LWCHAR prev_ch = step_char(&p, -1, linebuf); |
|
w = pwidth(ch, a, prev_ch); |
|
} |
|
|
if (ctldisp != OPT_ON && column + w + attr_ewidth(a) > sc_width) |
if (ctldisp != OPT_ON && column + w + attr_ewidth(a) > sc_width) |
/* |
/* |
* Won't fit on screen. |
* Won't fit on screen. |
*/ |
*/ |
return (1); |
return (1); |
|
|
if (curr >= size_linebuf-2) |
if (rep == NULL) |
{ |
{ |
|
cs = (char) ch; |
|
rep = &cs; |
|
replen = 1; |
|
} else |
|
{ |
|
#if !SMALL |
|
replen = utf_len(rep[0]); |
|
#else |
|
replen = 1; |
|
#endif |
|
} |
|
if (curr + replen >= size_linebuf-6) |
|
{ |
/* |
/* |
* Won't fit in line buffer. |
* Won't fit in line buffer. |
* Try to expand it. |
* Try to expand it. |
|
|
return (1); |
return (1); |
} |
} |
|
|
/* |
while (replen-- > 0) |
* Special handling for "magic cookie" terminals. |
|
* If an attribute enter/exit sequence has a printing width > 0, |
|
* and the sequence is adjacent to a space, delete the space. |
|
* We just mark the space as invisible, to avoid having too |
|
* many spaces deleted. |
|
* {{ Note that even if the attribute width is > 1, we |
|
* delete only one space. It's not worth trying to do more. |
|
* It's hardly worth doing this much. }} |
|
*/ |
|
if (curr > 0 && a != AT_NORMAL && |
|
linebuf[curr-1] == ' ' && attr[curr-1] == AT_NORMAL && |
|
attr_swidth(a) > 0) |
|
{ |
{ |
/* |
linebuf[curr] = *rep++; |
* We are about to append an enter-attribute sequence |
attr[curr] = a; |
* just after a space. Delete the space. |
curr++; |
*/ |
|
attr[curr-1] = AT_INVIS; |
|
column--; |
|
} else if (curr > 0 && attr[curr-1] != AT_NORMAL && |
|
attr[curr-1] != AT_INVIS && c == ' ' && a == AT_NORMAL && |
|
attr_ewidth(attr[curr-1]) > 0) |
|
{ |
|
/* |
|
* We are about to append a space just after an |
|
* exit-attribute sequence. Delete the space. |
|
*/ |
|
a = AT_INVIS; |
|
column--; |
|
} |
} |
/* End of magic cookie handling. */ |
|
|
|
linebuf[curr] = c; |
|
attr[curr] = a; |
|
column += w; |
column += w; |
return (0); |
return (0); |
} |
} |
|
|
to_tab = tabstops[i+1] - to_tab; |
to_tab = tabstops[i+1] - to_tab; |
} |
} |
|
|
|
if (column + to_tab - 1 + pwidth(' ', attr, 0) + attr_ewidth(attr) > sc_width) |
|
return 1; |
|
|
do { |
do { |
STORE_CHAR(' ', attr, pos); |
STORE_CHAR(' ', attr, " ", pos); |
} while (--to_tab > 0); |
} while (--to_tab > 0); |
return 0; |
return 0; |
} |
} |
|
|
|
#define STORE_PRCHAR(c, pos) \ |
|
do { if (store_prchar((c), (pos))) return 1; } while (0) |
|
|
|
static int |
|
store_prchar(c, pos) |
|
char c; |
|
POSITION pos; |
|
{ |
|
char *s; |
|
|
|
/* |
|
* Convert to printable representation. |
|
*/ |
|
s = prchar(c); |
|
|
|
/* |
|
* Make sure we can get the entire representation |
|
* of the character on this line. |
|
*/ |
|
if (column + (int) strlen(s) - 1 + |
|
pwidth(' ', binattr, 0) + attr_ewidth(binattr) > sc_width) |
|
return 1; |
|
|
|
for ( ; *s != 0; s++) |
|
STORE_CHAR(*s, AT_BINARY, NULL, pos); |
|
|
|
return 0; |
|
} |
|
|
|
#if !SMALL |
|
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; |
|
} |
|
#endif /* !SMALL */ |
|
|
/* |
/* |
* Append a character to the line buffer. |
* Append a character to the line buffer. |
* Expand tabs into spaces, handle underlining, boldfacing, etc. |
* Expand tabs into spaces, handle underlining, boldfacing, etc. |
|
|
*/ |
*/ |
public int |
public int |
pappend(c, pos) |
pappend(c, pos) |
register int c; |
char c; |
POSITION pos; |
POSITION pos; |
{ |
{ |
int r; |
int r; |
|
|
if (pendc) |
if (pendc) |
{ |
{ |
if (do_append(pendc, pendpos)) |
if (do_append(pendc, NULL, pendpos)) |
/* |
/* |
* Oops. We've probably lost the char which |
* Oops. We've probably lost the char which |
* was in pendc, since caller won't back up. |
* was in pendc, since caller won't back up. |
|
|
|
|
if (c == '\r' && bs_mode == BS_SPECIAL) |
if (c == '\r' && bs_mode == BS_SPECIAL) |
{ |
{ |
|
#if !SMALL |
|
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); |
|
} |
|
#endif /* !SMALL */ |
|
|
/* |
/* |
* Don't put the CR into the buffer until we see |
* Don't put the CR into the buffer until we see |
* the next char. If the next char is a newline, |
* the next char. If the next char is a newline, |
|
|
return (0); |
return (0); |
} |
} |
|
|
r = do_append(c, pos); |
if (!utf_mode) |
|
{ |
|
r = do_append((LWCHAR) c, NULL, pos); |
|
} |
|
#if !SMALL |
|
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((LWCHAR) 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)) |
|
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; |
|
} |
|
} |
|
#endif /* !SMALL */ |
|
|
/* |
/* |
* If we need to shift the line, do it. |
* If we need to shift the line, do it. |
* But wait until we get to at least the middle of the screen, |
* But wait until we get to at least the middle of the screen, |
|
|
linebuf[curr] = '\0'; |
linebuf[curr] = '\0'; |
pshift(hshift - cshift); |
pshift(hshift - cshift); |
} |
} |
|
if (r) |
|
{ |
|
/* How many chars should caller back up? */ |
|
r = (!utf_mode) ? 1 : mbc_buf_index; |
|
} |
return (r); |
return (r); |
} |
} |
|
|
#define IS_UTF8_4BYTE(c) ( ((c) & 0xf8) == 0xf0 ) |
|
#define IS_UTF8_3BYTE(c) ( ((c) & 0xf0) == 0xe0 ) |
|
#define IS_UTF8_2BYTE(c) ( ((c) & 0xe0) == 0xc0 ) |
|
#define IS_UTF8_TRAIL(c) ( ((c) & 0xc0) == 0x80 ) |
|
|
|
static int |
static int |
do_append(c, pos) |
do_append(ch, rep, pos) |
int c; |
LWCHAR ch; |
|
char *rep; |
POSITION pos; |
POSITION pos; |
{ |
{ |
register char *s; |
|
register int a; |
register int a; |
|
LWCHAR prev_ch; |
|
|
#define STOREC(c,a) \ |
a = AT_NORMAL; |
if ((c) == '\t') STORE_TAB((a),pos); else STORE_CHAR((c),(a),pos) |
|
|
|
if (c == '\b') |
if (ch == '\b') |
{ |
{ |
switch (bs_mode) |
if (bs_mode == BS_CONTROL) |
{ |
|
case BS_NORMAL: |
|
STORE_CHAR(c, AT_NORMAL, pos); |
|
break; |
|
case BS_CONTROL: |
|
goto do_control_char; |
goto do_control_char; |
case BS_SPECIAL: |
|
if (curr == 0) |
/* |
break; |
* A better test is needed here so we don't |
backc(); |
* backspace over part of the printed |
overstrike = 1; |
* representation of a binary character. |
break; |
*/ |
} |
if ( curr <= lmargin |
} else if (overstrike) |
|| column <= lmargin |
|
|| (attr[curr - 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; |
|
} |
|
|
|
if (overstrike > 0) |
{ |
{ |
/* |
/* |
* Overstrike the character at the current position |
* Overstrike the character at the current position |
|
|
* bold (if an identical character is overstruck), |
* bold (if an identical character is overstruck), |
* or just deletion of the character in the buffer. |
* or just deletion of the character in the buffer. |
*/ |
*/ |
overstrike--; |
overstrike = utf_mode ? -1 : 0; |
if (utf_mode && IS_UTF8_4BYTE(c) && curr > 2 && (char)c == linebuf[curr-3]) |
/* To be correct, this must be a base character. */ |
|
#if !SMALL |
|
prev_ch = get_wchar(linebuf + curr); |
|
#else |
|
prev_ch = (LWCHAR)((char)(linebuf + curr)[0] & 0xFF); |
|
#endif /* !SMALL */ |
|
a = attr[curr]; |
|
if (ch == prev_ch) |
{ |
{ |
backc(); |
|
backc(); |
|
backc(); |
|
STORE_CHAR(linebuf[curr], AT_BOLD, pos); |
|
overstrike = 3; |
|
} else if (utf_mode && (IS_UTF8_3BYTE(c) || (overstrike==2 && IS_UTF8_TRAIL(c))) && curr > 1 && (char)c == linebuf[curr-2]) |
|
{ |
|
backc(); |
|
backc(); |
|
STORE_CHAR(linebuf[curr], AT_BOLD, pos); |
|
overstrike = 2; |
|
} else if (utf_mode && curr > 0 && (IS_UTF8_2BYTE(c) || (overstrike==1 && IS_UTF8_TRAIL(c))) && (char)c == linebuf[curr-1]) |
|
{ |
|
backc(); |
|
STORE_CHAR(linebuf[curr], AT_BOLD, pos); |
|
overstrike = 1; |
|
} else if (utf_mode && curr > 0 && IS_UTF8_TRAIL(c) && attr[curr-1] == AT_UNDERLINE) |
|
{ |
|
STOREC(c, AT_UNDERLINE); |
|
} else if ((char)c == linebuf[curr]) |
|
{ |
|
/* |
/* |
* Overstriking a char with itself means make it bold. |
* Overstriking a char with itself means make it bold. |
* But overstriking an underscore with itself is |
* But overstriking an underscore with itself is |
|
|
* it could mean make it underlined. |
* it could mean make it underlined. |
* Use the previous overstrike to resolve it. |
* Use the previous overstrike to resolve it. |
*/ |
*/ |
if (c == '_' && last_overstrike != AT_NORMAL) |
if (ch == '_') |
STOREC(c, last_overstrike); |
|
else |
|
STOREC(c, AT_BOLD); |
|
} else if (c == '_') |
|
{ |
|
if (utf_mode) |
|
{ |
{ |
int i; |
if ((a & (AT_BOLD|AT_UNDERLINE)) != AT_NORMAL) |
for (i = 0; i < 5; i++) |
a |= (AT_BOLD|AT_UNDERLINE); |
{ |
else if (last_overstrike != AT_NORMAL) |
if (curr <= i || !IS_CONT(linebuf[curr-i])) |
a |= last_overstrike; |
break; |
else |
attr[curr-i-1] = AT_UNDERLINE; |
a |= AT_BOLD; |
} |
} else |
} |
a |= AT_BOLD; |
STOREC(linebuf[curr], AT_UNDERLINE); |
} else if (ch == '_') |
} else if (linebuf[curr] == '_') |
|
{ |
{ |
if (utf_mode) |
a |= AT_UNDERLINE; |
{ |
ch = prev_ch; |
if (IS_UTF8_2BYTE(c)) |
rep = linebuf + curr; |
overstrike = 1; |
} else if (prev_ch == '_') |
else if (IS_UTF8_3BYTE(c)) |
{ |
overstrike = 2; |
a |= AT_UNDERLINE; |
else if (IS_UTF8_4BYTE(c)) |
} |
overstrike = 3; |
/* Else we replace prev_ch, but we keep its attributes. */ |
} |
} else if (overstrike < 0) |
STOREC(c, AT_UNDERLINE); |
{ |
} else if (control_char(c)) |
#if !SMALL |
goto do_control_char; |
if ( is_composing_char(ch) |
|
|| is_combining_char(get_wchar(linebuf + curr), ch)) |
|
/* Continuation of the same overstrike. */ |
|
a = last_overstrike; |
else |
else |
STOREC(c, AT_NORMAL); |
#endif /* !SMALL */ |
} else if (c == '\t') |
overstrike = 0; |
|
} |
|
|
|
if (ch == '\t') |
{ |
{ |
/* |
/* |
* Expand a tab into spaces. |
* Expand a tab into spaces. |
|
|
goto do_control_char; |
goto do_control_char; |
case BS_NORMAL: |
case BS_NORMAL: |
case BS_SPECIAL: |
case BS_SPECIAL: |
STORE_TAB(AT_NORMAL, pos); |
STORE_TAB(a, pos); |
break; |
break; |
} |
} |
} else if (control_char(c)) |
} else if ((!utf_mode || is_ascii_char(ch)) && control_char((char)ch)) |
{ |
{ |
do_control_char: |
do_control_char: |
if (ctldisp == OPT_ON || (ctldisp == OPT_ONPLUS && c == ESC)) |
if (ctldisp == OPT_ON || (ctldisp == OPT_ONPLUS && IS_CSI_START(ch))) |
{ |
{ |
/* |
/* |
* Output as a normal character. |
* Output as a normal character. |
*/ |
*/ |
STORE_CHAR(c, AT_NORMAL, pos); |
STORE_CHAR(ch, AT_NORMAL, rep, pos); |
} else |
} else |
{ |
{ |
/* |
STORE_PRCHAR((char) ch, pos); |
* Convert to printable representation. |
} |
*/ |
} |
s = prchar(c); |
#if !SMALL |
a = binattr; |
else if (utf_mode && ctldisp != OPT_ON && is_ubin_char(ch)) |
|
{ |
|
char *s; |
|
|
/* |
s = prutfchar(ch); |
* Make sure we can get the entire representation |
|
* of the character on this line. |
|
*/ |
|
if (column + (int) strlen(s) + |
|
attr_swidth(a) + attr_ewidth(a) > sc_width) |
|
return (1); |
|
|
|
for ( ; *s != 0; s++) |
if (column + (int) strlen(s) - 1 + |
STORE_CHAR(*s, a, pos); |
pwidth(' ', binattr, 0) + attr_ewidth(binattr) > sc_width) |
} |
return (1); |
} else |
|
|
for ( ; *s != 0; s++) |
|
STORE_CHAR(*s, AT_BINARY, NULL, pos); |
|
} |
|
#endif /* !SMALL */ |
|
else |
{ |
{ |
STOREC(c, AT_NORMAL); |
STORE_CHAR(ch, a, rep, pos); |
} |
} |
|
return (0); |
|
} |
|
|
return (0); |
/* |
|
* |
|
*/ |
|
public int |
|
pflushmbc() |
|
{ |
|
int r = 0; |
|
|
|
#if !SMALL |
|
if (mbc_buf_len > 0) |
|
{ |
|
/* Flush incomplete (truncated) sequence. */ |
|
r = flush_mbc_buf(mbc_pos); |
|
mbc_buf_len = 0; |
|
} |
|
#endif /* !SMALL */ |
|
return r; |
} |
} |
|
|
/* |
/* |
* Terminate the line in the line buffer. |
* Terminate the line in the line buffer. |
*/ |
*/ |
public void |
public void |
pdone(endline) |
pdone(endline, forw) |
int endline; |
int endline; |
|
int forw; |
{ |
{ |
|
(void) pflushmbc(); |
|
|
if (pendc && (pendc != '\r' || !endline)) |
if (pendc && (pendc != '\r' || !endline)) |
/* |
/* |
* If we had a pending character, put it in the buffer. |
* If we had a pending character, put it in the buffer. |
* But discard a pending CR if we are at end of line |
* But discard a pending CR if we are at end of line |
* (that is, discard the CR in a CR/LF sequence). |
* (that is, discard the CR in a CR/LF sequence). |
*/ |
*/ |
(void) do_append(pendc, pendpos); |
(void) do_append(pendc, NULL, pendpos); |
|
|
/* |
/* |
* Make sure we've shifted the line, if we need to. |
* Make sure we've shifted the line, if we need to. |
|
|
if (cshift < hshift) |
if (cshift < hshift) |
pshift(hshift - cshift); |
pshift(hshift - cshift); |
|
|
|
if (ctldisp == OPT_ONPLUS && is_ansi_end('m')) |
|
{ |
|
/* Switch to normal attribute at end of line. */ |
|
char *p = "\033[m"; |
|
for ( ; *p != '\0'; p++) |
|
{ |
|
linebuf[curr] = *p; |
|
attr[curr++] = AT_ANSI; |
|
} |
|
} |
|
|
/* |
/* |
* Add a newline if necessary, |
* Add a newline if necessary, |
* and append a '\0' to the end of the line. |
* 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 (column < sc_width || !auto_wrap || ignaw || ctldisp == OPT_ON) |
if (column < sc_width || !auto_wrap || (endline && ignaw) || ctldisp == OPT_ON) |
{ |
{ |
linebuf[curr] = '\n'; |
linebuf[curr] = '\n'; |
attr[curr] = AT_NORMAL; |
attr[curr] = AT_NORMAL; |
curr++; |
curr++; |
|
} |
|
else if (ignaw && column >= sc_width && 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. |
|
*/ |
|
linebuf[curr] = ' '; |
|
attr[curr++] = AT_NORMAL; |
|
linebuf[curr] = '\b'; |
|
attr[curr++] = AT_NORMAL; |
} |
} |
linebuf[curr] = '\0'; |
linebuf[curr] = '\0'; |
attr[curr] = AT_NORMAL; |
attr[curr] = AT_NORMAL; |
|
} |
|
|
#if HILITE_SEARCH |
/* |
if (status_col && hilites > 0) |
* |
{ |
*/ |
linebuf[0] = '*'; |
public void |
attr[0] = AT_STANDOUT; |
set_status_col(c) |
} |
char c; |
#endif |
{ |
/* |
linebuf[0] = c; |
* If we are done with this line, reset the current shift. |
attr[0] = AT_NORMAL|AT_HILITE; |
*/ |
|
if (endline) |
|
cshift = 0; |
|
} |
} |
|
|
/* |
/* |
|
|
register int i; |
register int i; |
register int *ap; |
register int *ap; |
{ |
{ |
char *s; |
|
|
|
if (is_null_line) |
if (is_null_line) |
{ |
{ |
/* |
/* |
* If there is no current line, we pretend the line is |
* If there is no current line, we pretend the line is |
* either "~" or "", depending on the "twiddle" flag. |
* either "~" or "", depending on the "twiddle" flag. |
*/ |
*/ |
*ap = AT_BOLD; |
if (twiddle) |
s = (twiddle) ? "~\n" : "\n"; |
{ |
return (s[i]); |
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'; |
} |
} |
|
|
*ap = attr[i]; |
*ap = attr[i]; |
return (linebuf[i] & 0377); |
return (linebuf[i] & 0xFF); |
} |
} |
|
|
/* |
/* |
|
|
* {{ This is supposed to be more efficient than forw_line(). }} |
* {{ This is supposed to be more efficient than forw_line(). }} |
*/ |
*/ |
public POSITION |
public POSITION |
forw_raw_line(curr_pos, linep) |
forw_raw_line(curr_pos, linep, line_lenp) |
POSITION curr_pos; |
POSITION curr_pos; |
char **linep; |
char **linep; |
|
int *line_lenp; |
{ |
{ |
register int n; |
register int n; |
register int c; |
register int c; |
|
|
n = 0; |
n = 0; |
for (;;) |
for (;;) |
{ |
{ |
if (c == '\n' || c == EOI) |
if (c == '\n' || c == EOI || ABORT_SIGS()) |
{ |
{ |
new_pos = ch_tell(); |
new_pos = ch_tell(); |
break; |
break; |
|
|
linebuf[n] = '\0'; |
linebuf[n] = '\0'; |
if (linep != NULL) |
if (linep != NULL) |
*linep = linebuf; |
*linep = linebuf; |
|
if (line_lenp != NULL) |
|
*line_lenp = n; |
return (new_pos); |
return (new_pos); |
} |
} |
|
|
|
|
* {{ This is supposed to be more efficient than back_line(). }} |
* {{ This is supposed to be more efficient than back_line(). }} |
*/ |
*/ |
public POSITION |
public POSITION |
back_raw_line(curr_pos, linep) |
back_raw_line(curr_pos, linep, line_lenp) |
POSITION curr_pos; |
POSITION curr_pos; |
char **linep; |
char **linep; |
|
int *line_lenp; |
{ |
{ |
register int n; |
register int n; |
register int c; |
register int c; |
|
|
for (;;) |
for (;;) |
{ |
{ |
c = ch_back_get(); |
c = ch_back_get(); |
if (c == '\n') |
if (c == '\n' || ABORT_SIGS()) |
{ |
{ |
/* |
/* |
* This is the newline ending the previous line. |
* This is the newline ending the previous line. |
|
|
} |
} |
if (linep != NULL) |
if (linep != NULL) |
*linep = &linebuf[n]; |
*linep = &linebuf[n]; |
|
if (line_lenp != NULL) |
|
*line_lenp = size_linebuf - 1 - n; |
return (new_pos); |
return (new_pos); |
} |
} |