diff options
Diffstat (limited to 'usr.bin/less/line.c')
-rw-r--r-- | usr.bin/less/line.c | 607 |
1 files changed, 474 insertions, 133 deletions
diff --git a/usr.bin/less/line.c b/usr.bin/less/line.c index bc143fca982..773693c0c44 100644 --- a/usr.bin/less/line.c +++ b/usr.bin/less/line.c @@ -1,29 +1,11 @@ -/* $OpenBSD: line.c,v 1.4 2003/04/06 23:38:07 deraadt Exp $ */ - /* - * Copyright (c) 1984,1985,1989,1994,1995 Mark Nudelman - * All rights reserved. + * Copyright (C) 1984-2002 Mark Nudelman * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice in the documentation and/or other materials provided with - * the distribution. + * You may distribute under the terms of either the GNU General Public + * License or the Less License, as specified in the README file. * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT - * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR - * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE - * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN - * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * For more information about less, or for information on how to + * contact the author, see the README file. */ @@ -35,33 +17,88 @@ #include "less.h" -public char linebuf[1024]; /* Buffer which holds the current output line */ -public int size_linebuf = sizeof(linebuf); +#define IS_CONT(c) (((c) & 0xC0) == 0x80) + +public char *linebuf = NULL; /* Buffer which holds the current output line */ +static char *attr = NULL; /* Extension of linebuf to hold attributes */ +public int size_linebuf = 0; /* Size of line buffer (and attr buffer) */ + +public 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 */ -static char attr[1024]; /* Extension of linebuf to hold attributes */ static int curr; /* Index into linebuf */ static int column; /* Printable length, accounting for backspaces, etc. */ -static int lno_indent; /* Number of chars used for line number */ 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 int lmargin; /* Left margin */ +static int hilites; /* Number of hilites in this line */ static char pendc; static POSITION pendpos; +static char *end_ansi_chars; static int do_append(); extern int bs_mode; -extern int tabstop; extern int linenums; extern int ctldisp; extern int twiddle; extern int binattr; +extern int status_col; 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; + +/* + * Initialize from environment variables. + */ + public void +init_line() +{ + end_ansi_chars = lgetenv("LESSANSIENDCHARS"); + if (end_ansi_chars == NULL || *end_ansi_chars == '\0') + end_ansi_chars = "m"; + linebuf = (char *) ecalloc(LINEBUF_SIZE, sizeof(char)); + attr = (char *) ecalloc(LINEBUF_SIZE, sizeof(char)); + size_linebuf = LINEBUF_SIZE; +} + +/* + * Expand the line buffer. + */ + static int +expand_linebuf() +{ + int new_size = size_linebuf + LINEBUF_SIZE; + char *new_buf = (char *) calloc(new_size, sizeof(char)); + char *new_attr = (char *) calloc(new_size, sizeof(char)); + if (new_buf == NULL || new_attr == NULL) + { + if (new_attr != NULL) + free(new_attr); + if (new_buf != NULL) + free(new_buf); + return 1; + } + memcpy(new_buf, linebuf, size_linebuf * sizeof(char)); + memcpy(new_attr, attr, size_linebuf * sizeof(char)); + free(attr); + free(linebuf); + linebuf = new_buf; + attr = new_attr; + size_linebuf = new_size; + return 0; +} /* * Rewind the line buffer. @@ -73,8 +110,13 @@ prewind() column = 0; overstrike = 0; is_null_line = 0; - lno_indent = 0; pendc = '\0'; + lmargin = 0; + if (status_col) + lmargin += 1; +#if HILITE_SEARCH + hilites = 0; +#endif } /* @@ -84,52 +126,163 @@ prewind() plinenum(pos) POSITION pos; { - int lno; - int i; - int n; + register LINENUM linenum = 0; + register 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 plinenum 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); + } /* - * We display the line number at the start of each line - * only if the -N option is set. + * Display a status column if the -J option is set. */ - if (linenums != OPT_ONPLUS) - return; - + if (status_col) + { + linebuf[curr] = ' '; + if (start_attnpos != NULL_POSITION && + pos >= start_attnpos && pos < end_attnpos) + attr[curr] = AT_STANDOUT; + else + attr[curr] = 0; + curr++; + column++; + } /* - * 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 plinenum to re-seek if necessary. }} + * Display the line number at the start of each line + * if the -N option is set. */ - lno = find_linenum(pos); - - snprintf(&linebuf[curr], sizeof linebuf - curr, "%6d", lno); - n = strlen(&linebuf[curr]); - column += n; - for (i = 0; i < n; i++) - attr[curr++] = 0; + if (linenums == OPT_ONPLUS) + { + char buf[INT_STRLEN_BOUND(pos) + 2]; + int n; + + linenumtoa(linenum, buf, sizeof(buf)); + n = strlen(buf); + if (n < MIN_LINENUM_WIDTH) + n = MIN_LINENUM_WIDTH; + snprintf(linebuf+curr, sizeof(linebuf)-curr, "%*s ", n, buf); + n++; /* One space after the line number. */ + for (i = 0; i < n; i++) + attr[curr+i] = AT_NORMAL; + curr += n; + column += n; + lmargin += n; + } /* - * Append enough spaces to bring us to the next tab stop. - * {{ We could avoid this at the cost of adding some - * complication to the tab stop logic in pappend(). }} + * Append enough spaces to bring us to the lmargin. */ - if (tabstop == 0) - tabstop = 1; - do + while (column < lmargin) { linebuf[curr] = ' '; attr[curr++] = AT_NORMAL; column++; - } while ((column % tabstop) != 0); - lno_indent = column; + } +} + +/* + * Determine how many characters are required to shift N columns. + */ + static int +shift_chars(s, len) + char *s; + int len; +{ + char *p = s; + + /* + * Each char counts for one column, except ANSI color escape + * sequences use no columns since they don't move the cursor. + */ + while (*p != '\0' && len > 0) + { + if (*p++ != ESC) + { + len--; + } else + { + while (*p != '\0') + { + if (is_ansi_end(*p++)) + break; + } + } + } + return (p - s); +} + +/* + * Determine how many characters are required to shift N columns (UTF version). + * {{ FIXME: what about color escape sequences in UTF mode? }} + */ + static int +utf_shift_chars(s, len) + char *s; + int len; +{ + int ulen = 0; + + while (*s != '\0' && len > 0) + { + if (!IS_CONT(*s)) + len--; + s++; + ulen++; + } + while (IS_CONT(*s)) + { + s++; + ulen++; + } + return (ulen); +} + +/* + * Shift the input line left. + * This means discarding N printable chars at the start of the buffer. + */ + static void +pshift(shift) + int shift; +{ + int i; + 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; } /* * Return the printing width of the start (enter) sequence * for a given character attribute. */ - int + static int attr_swidth(a) int a; { @@ -147,7 +300,7 @@ attr_swidth(a) * Return the printing width of the end (exit) sequence * for a given character attribute. */ - int + static int attr_ewidth(a) int a; { @@ -172,7 +325,10 @@ pwidth(c, a) int c; int a; { - int w; + register int w; + + if (utf_mode && IS_CONT(c)) + return (0); if (c == '\b') /* @@ -211,36 +367,83 @@ backc() } /* + * Are we currently within a recognized ANSI escape sequence? + */ + static int +in_ansi_esc_seq() +{ + int i; + + /* + * 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). + */ + for (i = curr-1; i >= 0; i--) + { + if (linebuf[i] == ESC) + return (1); + if (is_ansi_end(linebuf[i])) + return (0); + } + return (0); +} + +/* + * Is a character the end of an ANSI escape sequence? + */ + public int +is_ansi_end(c) + char c; +{ + return (strchr(end_ansi_chars, c) != NULL); +} + +/* * Append a character and attribute to the line buffer. */ +#define STORE_CHAR(c,a,pos) \ + do { if (store_char((c),(a),(pos))) return (1); else curr++; } while (0) + static int -storec(c, a, pos) +store_char(c, a, pos) int c; int a; POSITION pos; { - int w; + register int w; + if (a != AT_NORMAL) + last_overstrike = a; #if HILITE_SEARCH if (is_hilited(pos, pos+1, 0)) + { /* * This character should be highlighted. * Override the attribute passed in. */ a = AT_STANDOUT; + hilites++; + } #endif - w = pwidth(c, a); - if (ctldisp > 0 && column + w + attr_ewidth(a) > sc_width) + if (ctldisp == OPT_ONPLUS && in_ansi_esc_seq()) + w = 0; + else + w = pwidth(c, a); + if (ctldisp != OPT_ON && column + w + attr_ewidth(a) > sc_width) /* * Won't fit on screen. */ return (1); - if (curr >= sizeof(linebuf)-2) + if (curr >= size_linebuf-2) + { /* * Won't fit in line buffer. + * Try to expand it. */ - return (1); + if (expand_linebuf()) + return (1); + } /* * Special handling for "magic cookie" terminals. @@ -282,15 +485,49 @@ storec(c, a, pos) } /* + * 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 = column + cshift - lmargin; + int i; + + if (ntabstops < 2 || to_tab >= tabstops[ntabstops-1]) + to_tab = tabdefault - + ((to_tab - tabstops[ntabstops-1]) % tabdefault); + else + { + 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; +} + +/* * 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; + register int c; POSITION pos; { + int r; + if (pendc) { if (do_append(pendc, pendpos)) @@ -314,21 +551,54 @@ pappend(c, pos) return (0); } - return (do_append(c, pos)); + r = do_append(c, pos); + /* + * If we need to shift the line, do it. + * But wait until we get to at least the middle of the screen, + * so shifting it doesn't affect the chars we're currently + * pappending. (Bold & underline can get messed up otherwise.) + */ + if (cshift < hshift && column > sc_width / 2) + { + linebuf[curr] = '\0'; + pshift(hshift - cshift); + } + 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 do_append(c, pos) int c; POSITION pos; { - char *s; - int a; + register char *s; + register int a; -#define STOREC(c,a) \ - if (storec((c),(a),pos)) return (1); else curr++ +#define STOREC(c,a) \ + if ((c) == '\t') STORE_TAB((a),pos); else STORE_CHAR((c),(a),pos) - if (overstrike) + if (c == '\b') + { + switch (bs_mode) + { + case BS_NORMAL: + STORE_CHAR(c, AT_NORMAL, pos); + break; + case BS_CONTROL: + goto do_control_char; + case BS_SPECIAL: + if (curr == 0) + break; + backc(); + overstrike = 1; + break; + } + } else if (overstrike) { /* * Overstrike the character at the current position @@ -337,53 +607,93 @@ do_append(c, pos) * bold (if an identical character is overstruck), * or just deletion of the character in the buffer. */ - overstrike = 0; - if ((char)c == linebuf[curr]) - STOREC(linebuf[curr], AT_BOLD); - else if (c == '_') + overstrike--; + if (utf_mode && IS_UTF8_4BYTE(c) && curr > 2 && (char)c == linebuf[curr-3]) + { + 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. + * 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 (c == '_' && last_overstrike != AT_NORMAL) + STOREC(c, last_overstrike); + else + STOREC(c, AT_BOLD); + } else if (c == '_') + { + if (utf_mode) + { + int i; + for (i = 0; i < 5; i++) + { + if (curr <= i || !IS_CONT(linebuf[curr-i])) + break; + attr[curr-i-1] = AT_UNDERLINE; + } + } STOREC(linebuf[curr], AT_UNDERLINE); - else if (linebuf[curr] == '_') + } else if (linebuf[curr] == '_') + { + if (utf_mode) + { + if (IS_UTF8_2BYTE(c)) + overstrike = 1; + else if (IS_UTF8_3BYTE(c)) + overstrike = 2; + else if (IS_UTF8_4BYTE(c)) + overstrike = 3; + } STOREC(c, AT_UNDERLINE); - else if (control_char(c)) + } else if (control_char(c)) goto do_control_char; else STOREC(c, AT_NORMAL); - } else if (c == '\b') + } else if (c == '\t') { + /* + * Expand a tab into spaces. + */ switch (bs_mode) { - case BS_NORMAL: - STOREC(c, AT_NORMAL); - break; case BS_CONTROL: goto do_control_char; + case BS_NORMAL: case BS_SPECIAL: - if (curr == 0) - break; - backc(); - overstrike = 1; + STORE_TAB(AT_NORMAL, pos); break; } - } else if (c == '\t') - { - /* - * Expand a tab into spaces. - */ - if (tabstop == 0) - tabstop = 1; - do - { - STOREC(' ', AT_NORMAL); - } while ((column % tabstop) != 0); } else if (control_char(c)) { do_control_char: - if (ctldisp == 0) + if (ctldisp == OPT_ON || (ctldisp == OPT_ONPLUS && c == ESC)) { /* * Output as a normal character. */ - STOREC(c, AT_NORMAL); + STORE_CHAR(c, AT_NORMAL, pos); } else { /* @@ -401,7 +711,7 @@ do_append(c, pos) return (1); for ( ; *s != 0; s++) - STOREC(*s, a); + STORE_CHAR(*s, a, pos); } } else { @@ -427,10 +737,16 @@ pdone(endline) (void) do_append(pendc, pendpos); /* + * Make sure we've shifted the line, if we need to. + */ + if (cshift < hshift) + pshift(hshift - cshift); + + /* * Add a newline if necessary, * and append a '\0' to the end of the line. */ - if (column < sc_width || !auto_wrap || ignaw || ctldisp == 0) + if (column < sc_width || !auto_wrap || ignaw || ctldisp == OPT_ON) { linebuf[curr] = '\n'; attr[curr] = AT_NORMAL; @@ -438,6 +754,19 @@ pdone(endline) } linebuf[curr] = '\0'; attr[curr] = AT_NORMAL; + +#if HILITE_SEARCH + if (status_col && hilites > 0) + { + linebuf[0] = '*'; + attr[0] = AT_STANDOUT; + } +#endif + /* + * If we are done with this line, reset the current shift. + */ + if (endline) + cshift = 0; } /* @@ -447,8 +776,8 @@ pdone(endline) */ public int gline(i, ap) - int i; - int *ap; + register int i; + register int *ap; { char *s; @@ -458,7 +787,7 @@ gline(i, ap) * If there is no current line, we pretend the line is * either "~" or "", depending on the "twiddle" flag. */ - *ap = AT_NORMAL; + *ap = AT_BOLD; s = (twiddle) ? "~\n" : "\n"; return (s[i]); } @@ -474,9 +803,9 @@ gline(i, ap) null_line() { is_null_line = 1; + cshift = 0; } -#if 1 /* * Analogous to forw_line(), but deals with "raw lines": * lines which are not split for screen width. @@ -487,16 +816,15 @@ forw_raw_line(curr_pos, linep) POSITION curr_pos; char **linep; { - char *p; - int c; + register int n; + register int c; POSITION new_pos; if (curr_pos == NULL_POSITION || ch_seek(curr_pos) || (c = ch_forw_get()) == EOI) return (NULL_POSITION); - p = linebuf; - + n = 0; for (;;) { if (c == '\n' || c == EOI) @@ -504,21 +832,22 @@ forw_raw_line(curr_pos, linep) new_pos = ch_tell(); break; } - if (p >= &linebuf[sizeof(linebuf)-1]) + if (n >= size_linebuf-1) { - /* - * Overflowed the input buffer. - * Pretend the line ended here. - * {{ The line buffer is supposed to be big - * enough that this never happens. }} - */ - new_pos = ch_tell() - 1; - break; + if (expand_linebuf()) + { + /* + * Overflowed the input buffer. + * Pretend the line ended here. + */ + new_pos = ch_tell() - 1; + break; + } } - *p++ = c; + linebuf[n++] = c; c = ch_forw_get(); } - *p = '\0'; + linebuf[n] = '\0'; if (linep != NULL) *linep = linebuf; return (new_pos); @@ -533,17 +862,16 @@ back_raw_line(curr_pos, linep) POSITION curr_pos; char **linep; { - char *p; - int c; + register int n; + register int c; POSITION new_pos; if (curr_pos == NULL_POSITION || curr_pos <= ch_zero() || ch_seek(curr_pos-1)) return (NULL_POSITION); - p = &linebuf[sizeof(linebuf)]; - *--p = '\0'; - + n = size_linebuf; + linebuf[--n] = '\0'; for (;;) { c = ch_back_get(); @@ -566,19 +894,32 @@ back_raw_line(curr_pos, linep) new_pos = ch_zero(); break; } - if (p <= linebuf) + 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; + } /* - * Overflowed the input buffer. - * Pretend the line ended here. + * Shift the data to the end of the new linebuf. */ - new_pos = ch_tell() + 1; - break; + for (fm = linebuf + old_size_linebuf, + to = linebuf + size_linebuf; + fm >= linebuf; fm--, to--) + *to = *fm; + n = size_linebuf - old_size_linebuf; } - *--p = c; + linebuf[--n] = c; } if (linep != NULL) - *linep = p; + *linep = &linebuf[n]; return (new_pos); } -#endif |