line.c revision 128345
160786Sps/* 2128345Stjr * Copyright (C) 1984-2002 Mark Nudelman 360786Sps * 460786Sps * You may distribute under the terms of either the GNU General Public 560786Sps * License or the Less License, as specified in the README file. 660786Sps * 760786Sps * For more information about less, or for information on how to 860786Sps * contact the author, see the README file. 960786Sps */ 1060786Sps 1160786Sps 1260786Sps/* 1360786Sps * Routines to manipulate the "line buffer". 1460786Sps * The line buffer holds a line of output as it is being built 1560786Sps * in preparation for output to the screen. 1660786Sps */ 1760786Sps 1860786Sps#include "less.h" 1960786Sps 2060786Sps#define IS_CONT(c) (((c) & 0xC0) == 0x80) 2160786Sps 2289019Spspublic char *linebuf = NULL; /* Buffer which holds the current output line */ 2389019Spsstatic char *attr = NULL; /* Extension of linebuf to hold attributes */ 2489019Spspublic int size_linebuf = 0; /* Size of line buffer (and attr buffer) */ 2560786Sps 2660786Spspublic int cshift; /* Current left-shift of output line buffer */ 2760786Spspublic int hshift; /* Desired left-shift of output line buffer */ 2889019Spspublic int tabstops[TABSTOP_MAX] = { 0 }; /* Custom tabstops */ 2989019Spspublic int ntabstops = 1; /* Number of tabstops */ 3089019Spspublic int tabdefault = 8; /* Default repeated tabstops */ 3160786Sps 3260786Spsstatic int curr; /* Index into linebuf */ 3360786Spsstatic int column; /* Printable length, accounting for 3460786Sps backspaces, etc. */ 3560786Spsstatic int overstrike; /* Next char should overstrike previous char */ 36128345Stjrstatic int last_overstrike = AT_NORMAL; 3760786Spsstatic int is_null_line; /* There is no current line */ 3863128Spsstatic int lmargin; /* Left margin */ 3989019Spsstatic int hilites; /* Number of hilites in this line */ 4060786Spsstatic char pendc; 4160786Spsstatic POSITION pendpos; 4260786Spsstatic char *end_ansi_chars; 4360786Sps 4460786Spsstatic int do_append(); 4560786Sps 4660786Spsextern int bs_mode; 4760786Spsextern int linenums; 4860786Spsextern int ctldisp; 4960786Spsextern int twiddle; 5060786Spsextern int binattr; 5163128Spsextern int status_col; 5260786Spsextern int auto_wrap, ignaw; 5360786Spsextern int bo_s_width, bo_e_width; 5460786Spsextern int ul_s_width, ul_e_width; 5560786Spsextern int bl_s_width, bl_e_width; 5660786Spsextern int so_s_width, so_e_width; 5760786Spsextern int sc_width, sc_height; 5860786Spsextern int utf_mode; 5963128Spsextern POSITION start_attnpos; 6063128Spsextern POSITION end_attnpos; 6160786Sps 6260786Sps/* 6360786Sps * Initialize from environment variables. 6460786Sps */ 6560786Sps public void 6660786Spsinit_line() 6760786Sps{ 6860786Sps end_ansi_chars = lgetenv("LESSANSIENDCHARS"); 6960786Sps if (end_ansi_chars == NULL || *end_ansi_chars == '\0') 7060786Sps end_ansi_chars = "m"; 7189019Sps linebuf = (char *) ecalloc(LINEBUF_SIZE, sizeof(char)); 7289019Sps attr = (char *) ecalloc(LINEBUF_SIZE, sizeof(char)); 7389019Sps size_linebuf = LINEBUF_SIZE; 7460786Sps} 7560786Sps 7660786Sps/* 7789019Sps * Expand the line buffer. 7889019Sps */ 7989019Sps static int 8089019Spsexpand_linebuf() 8189019Sps{ 8289019Sps int new_size = size_linebuf + LINEBUF_SIZE; 8389019Sps char *new_buf = (char *) calloc(new_size, sizeof(char)); 8489019Sps char *new_attr = (char *) calloc(new_size, sizeof(char)); 8589019Sps if (new_buf == NULL || new_attr == NULL) 8689019Sps { 8789019Sps if (new_attr != NULL) 8889019Sps free(new_attr); 8989019Sps if (new_buf != NULL) 9089019Sps free(new_buf); 9189019Sps return 1; 9289019Sps } 9389019Sps memcpy(new_buf, linebuf, size_linebuf * sizeof(char)); 9489019Sps memcpy(new_attr, attr, size_linebuf * sizeof(char)); 95128345Stjr free(attr); 96128345Stjr free(linebuf); 9789019Sps linebuf = new_buf; 9889019Sps attr = new_attr; 9989019Sps size_linebuf = new_size; 10089019Sps return 0; 10189019Sps} 10289019Sps 10389019Sps/* 10460786Sps * Rewind the line buffer. 10560786Sps */ 10660786Sps public void 10760786Spsprewind() 10860786Sps{ 10960786Sps curr = 0; 11060786Sps column = 0; 11160786Sps overstrike = 0; 11260786Sps is_null_line = 0; 11360786Sps pendc = '\0'; 11463128Sps lmargin = 0; 115128345Stjr if (status_col) 116128345Stjr lmargin += 1; 11789019Sps#if HILITE_SEARCH 11889019Sps hilites = 0; 11989019Sps#endif 12060786Sps} 12160786Sps 12260786Sps/* 12360786Sps * Insert the line number (of the given position) into the line buffer. 12460786Sps */ 12560786Sps public void 12660786Spsplinenum(pos) 12760786Sps POSITION pos; 12860786Sps{ 129128345Stjr register LINENUM linenum = 0; 13060786Sps register int i; 13160786Sps 13263128Sps if (linenums == OPT_ONPLUS) 13363128Sps { 13463128Sps /* 13563128Sps * Get the line number and put it in the current line. 13663128Sps * {{ Note: since find_linenum calls forw_raw_line, 13763128Sps * it may seek in the input file, requiring the caller 13863128Sps * of plinenum to re-seek if necessary. }} 13963128Sps * {{ Since forw_raw_line modifies linebuf, we must 14063128Sps * do this first, before storing anything in linebuf. }} 14163128Sps */ 142128345Stjr linenum = find_linenum(pos); 14363128Sps } 14463128Sps 14560786Sps /* 14663128Sps * Display a status column if the -J option is set. 14760786Sps */ 14863128Sps if (status_col) 14963128Sps { 15063128Sps linebuf[curr] = ' '; 15163128Sps if (start_attnpos != NULL_POSITION && 15263128Sps pos >= start_attnpos && pos < end_attnpos) 15363128Sps attr[curr] = AT_STANDOUT; 15463128Sps else 15563128Sps attr[curr] = 0; 15663128Sps curr++; 15763128Sps column++; 15863128Sps } 15960786Sps /* 16063128Sps * Display the line number at the start of each line 16163128Sps * if the -N option is set. 16260786Sps */ 16363128Sps if (linenums == OPT_ONPLUS) 16463128Sps { 165128345Stjr char buf[INT_STRLEN_BOUND(pos) + 2]; 166128345Stjr int n; 167128345Stjr 168128345Stjr linenumtoa(linenum, buf); 169128345Stjr n = strlen(buf); 170128345Stjr if (n < MIN_LINENUM_WIDTH) 171128345Stjr n = MIN_LINENUM_WIDTH; 172128345Stjr sprintf(linebuf+curr, "%*s ", n, buf); 173128345Stjr n++; /* One space after the line number. */ 174128345Stjr for (i = 0; i < n; i++) 175128345Stjr attr[curr+i] = AT_NORMAL; 176128345Stjr curr += n; 177128345Stjr column += n; 178128345Stjr lmargin += n; 17963128Sps } 180128345Stjr 18160786Sps /* 18263128Sps * Append enough spaces to bring us to the lmargin. 18360786Sps */ 18463128Sps while (column < lmargin) 18560786Sps { 18660786Sps linebuf[curr] = ' '; 18760786Sps attr[curr++] = AT_NORMAL; 18860786Sps column++; 18963128Sps } 19060786Sps} 19160786Sps 19260786Sps/* 19389019Sps * Determine how many characters are required to shift N columns. 19460786Sps */ 19560786Sps static int 19689019Spsshift_chars(s, len) 19789019Sps char *s; 19889019Sps int len; 19960786Sps{ 20089019Sps char *p = s; 20189019Sps 20289019Sps /* 20389019Sps * Each char counts for one column, except ANSI color escape 20489019Sps * sequences use no columns since they don't move the cursor. 20589019Sps */ 20689019Sps while (*p != '\0' && len > 0) 20789019Sps { 20889019Sps if (*p++ != ESC) 20989019Sps { 21089019Sps len--; 21189019Sps } else 21289019Sps { 21389019Sps while (*p != '\0') 21489019Sps { 21589019Sps if (is_ansi_end(*p++)) 21689019Sps break; 21789019Sps } 21889019Sps } 21989019Sps } 22089019Sps return (p - s); 22189019Sps} 22289019Sps 22389019Sps/* 22489019Sps * Determine how many characters are required to shift N columns (UTF version). 22589019Sps * {{ FIXME: what about color escape sequences in UTF mode? }} 22689019Sps */ 22789019Sps static int 22889019Spsutf_shift_chars(s, len) 22989019Sps char *s; 23089019Sps int len; 23189019Sps{ 23260786Sps int ulen = 0; 23360786Sps 23460786Sps while (*s != '\0' && len > 0) 23560786Sps { 23660786Sps if (!IS_CONT(*s)) 23760786Sps len--; 23860786Sps s++; 23960786Sps ulen++; 24060786Sps } 24160786Sps while (IS_CONT(*s)) 24260786Sps { 24360786Sps s++; 24460786Sps ulen++; 24560786Sps } 24660786Sps return (ulen); 24760786Sps} 24860786Sps 24960786Sps/* 25060786Sps * Shift the input line left. 25160786Sps * This means discarding N printable chars at the start of the buffer. 25260786Sps */ 25360786Sps static void 25460786Spspshift(shift) 25560786Sps int shift; 25660786Sps{ 25760786Sps int i; 25889019Sps int nchars; 25960786Sps 26063128Sps if (shift > column - lmargin) 26163128Sps shift = column - lmargin; 26263128Sps if (shift > curr - lmargin) 26363128Sps shift = curr - lmargin; 26460786Sps 26589019Sps if (utf_mode) 26689019Sps nchars = utf_shift_chars(linebuf + lmargin, shift); 26760786Sps else 26889019Sps nchars = shift_chars(linebuf + lmargin, shift); 26989019Sps if (nchars > curr) 27089019Sps nchars = curr; 27189019Sps for (i = 0; i < curr - nchars; i++) 27260786Sps { 27389019Sps linebuf[lmargin + i] = linebuf[lmargin + i + nchars]; 27489019Sps attr[lmargin + i] = attr[lmargin + i + nchars]; 27560786Sps } 27689019Sps curr -= nchars; 27760786Sps column -= shift; 27860786Sps cshift += shift; 27960786Sps} 28060786Sps 28160786Sps/* 28260786Sps * Return the printing width of the start (enter) sequence 28360786Sps * for a given character attribute. 28460786Sps */ 28560786Sps static int 28660786Spsattr_swidth(a) 28760786Sps int a; 28860786Sps{ 28960786Sps switch (a) 29060786Sps { 29160786Sps case AT_BOLD: return (bo_s_width); 29260786Sps case AT_UNDERLINE: return (ul_s_width); 29360786Sps case AT_BLINK: return (bl_s_width); 29460786Sps case AT_STANDOUT: return (so_s_width); 29560786Sps } 29660786Sps return (0); 29760786Sps} 29860786Sps 29960786Sps/* 30060786Sps * Return the printing width of the end (exit) sequence 30160786Sps * for a given character attribute. 30260786Sps */ 30360786Sps static int 30460786Spsattr_ewidth(a) 30560786Sps int a; 30660786Sps{ 30760786Sps switch (a) 30860786Sps { 30960786Sps case AT_BOLD: return (bo_e_width); 31060786Sps case AT_UNDERLINE: return (ul_e_width); 31160786Sps case AT_BLINK: return (bl_e_width); 31260786Sps case AT_STANDOUT: return (so_e_width); 31360786Sps } 31460786Sps return (0); 31560786Sps} 31660786Sps 31760786Sps/* 31860786Sps * Return the printing width of a given character and attribute, 31960786Sps * if the character were added to the current position in the line buffer. 32060786Sps * Adding a character with a given attribute may cause an enter or exit 32160786Sps * attribute sequence to be inserted, so this must be taken into account. 32260786Sps */ 32360786Sps static int 32460786Spspwidth(c, a) 32560786Sps int c; 32660786Sps int a; 32760786Sps{ 32860786Sps register int w; 32960786Sps 33060786Sps if (utf_mode && IS_CONT(c)) 33160786Sps return (0); 33260786Sps 33360786Sps if (c == '\b') 33460786Sps /* 33560786Sps * Backspace moves backwards one position. 33660786Sps */ 33760786Sps return (-1); 33860786Sps 33960786Sps if (control_char(c)) 34060786Sps /* 34160786Sps * Control characters do unpredicatable things, 34260786Sps * so we don't even try to guess; say it doesn't move. 34360786Sps * This can only happen if the -r flag is in effect. 34460786Sps */ 34560786Sps return (0); 34660786Sps 34760786Sps /* 34860786Sps * Other characters take one space, 34960786Sps * plus the width of any attribute enter/exit sequence. 35060786Sps */ 35160786Sps w = 1; 35260786Sps if (curr > 0 && attr[curr-1] != a) 35360786Sps w += attr_ewidth(attr[curr-1]); 35460786Sps if (a && (curr == 0 || attr[curr-1] != a)) 35560786Sps w += attr_swidth(a); 35660786Sps return (w); 35760786Sps} 35860786Sps 35960786Sps/* 36060786Sps * Delete the previous character in the line buffer. 36160786Sps */ 36260786Sps static void 36360786Spsbackc() 36460786Sps{ 36560786Sps curr--; 36660786Sps column -= pwidth(linebuf[curr], attr[curr]); 36760786Sps} 36860786Sps 36960786Sps/* 37060786Sps * Are we currently within a recognized ANSI escape sequence? 37160786Sps */ 37260786Sps static int 37360786Spsin_ansi_esc_seq() 37460786Sps{ 37560786Sps int i; 37660786Sps 37760786Sps /* 37860786Sps * Search backwards for either an ESC (which means we ARE in a seq); 37960786Sps * or an end char (which means we're NOT in a seq). 38060786Sps */ 38160786Sps for (i = curr-1; i >= 0; i--) 38260786Sps { 38360786Sps if (linebuf[i] == ESC) 38460786Sps return (1); 38589019Sps if (is_ansi_end(linebuf[i])) 38660786Sps return (0); 38760786Sps } 38860786Sps return (0); 38960786Sps} 39060786Sps 39160786Sps/* 39289019Sps * Is a character the end of an ANSI escape sequence? 39389019Sps */ 39489019Sps public int 39589019Spsis_ansi_end(c) 39689019Sps char c; 39789019Sps{ 39889019Sps return (strchr(end_ansi_chars, c) != NULL); 39989019Sps} 40089019Sps 40189019Sps/* 40260786Sps * Append a character and attribute to the line buffer. 40360786Sps */ 40489019Sps#define STORE_CHAR(c,a,pos) \ 40589019Sps do { if (store_char((c),(a),(pos))) return (1); else curr++; } while (0) 40689019Sps 40760786Sps static int 40889019Spsstore_char(c, a, pos) 40960786Sps int c; 41060786Sps int a; 41160786Sps POSITION pos; 41260786Sps{ 41360786Sps register int w; 41460786Sps 415128345Stjr if (a != AT_NORMAL) 416128345Stjr last_overstrike = a; 41760786Sps#if HILITE_SEARCH 41860786Sps if (is_hilited(pos, pos+1, 0)) 41989019Sps { 42060786Sps /* 42160786Sps * This character should be highlighted. 42260786Sps * Override the attribute passed in. 42360786Sps */ 42460786Sps a = AT_STANDOUT; 42589019Sps hilites++; 42689019Sps } 42760786Sps#endif 42860786Sps if (ctldisp == OPT_ONPLUS && in_ansi_esc_seq()) 42960786Sps w = 0; 43060786Sps else 43160786Sps w = pwidth(c, a); 43260786Sps if (ctldisp != OPT_ON && column + w + attr_ewidth(a) > sc_width) 43360786Sps /* 43460786Sps * Won't fit on screen. 43560786Sps */ 43660786Sps return (1); 43760786Sps 43889019Sps if (curr >= size_linebuf-2) 43989019Sps { 44060786Sps /* 44160786Sps * Won't fit in line buffer. 44289019Sps * Try to expand it. 44360786Sps */ 44489019Sps if (expand_linebuf()) 44589019Sps return (1); 44689019Sps } 44760786Sps 44860786Sps /* 44960786Sps * Special handling for "magic cookie" terminals. 45060786Sps * If an attribute enter/exit sequence has a printing width > 0, 45160786Sps * and the sequence is adjacent to a space, delete the space. 45260786Sps * We just mark the space as invisible, to avoid having too 45360786Sps * many spaces deleted. 45460786Sps * {{ Note that even if the attribute width is > 1, we 45560786Sps * delete only one space. It's not worth trying to do more. 45660786Sps * It's hardly worth doing this much. }} 45760786Sps */ 45860786Sps if (curr > 0 && a != AT_NORMAL && 45960786Sps linebuf[curr-1] == ' ' && attr[curr-1] == AT_NORMAL && 46060786Sps attr_swidth(a) > 0) 46160786Sps { 46260786Sps /* 46360786Sps * We are about to append an enter-attribute sequence 46460786Sps * just after a space. Delete the space. 46560786Sps */ 46660786Sps attr[curr-1] = AT_INVIS; 46760786Sps column--; 46860786Sps } else if (curr > 0 && attr[curr-1] != AT_NORMAL && 46960786Sps attr[curr-1] != AT_INVIS && c == ' ' && a == AT_NORMAL && 47060786Sps attr_ewidth(attr[curr-1]) > 0) 47160786Sps { 47260786Sps /* 47360786Sps * We are about to append a space just after an 47460786Sps * exit-attribute sequence. Delete the space. 47560786Sps */ 47660786Sps a = AT_INVIS; 47760786Sps column--; 47860786Sps } 47960786Sps /* End of magic cookie handling. */ 48060786Sps 48160786Sps linebuf[curr] = c; 48260786Sps attr[curr] = a; 48360786Sps column += w; 48460786Sps return (0); 48560786Sps} 48660786Sps 48760786Sps/* 48889019Sps * Append a tab to the line buffer. 48989019Sps * Store spaces to represent the tab. 49089019Sps */ 49189019Sps#define STORE_TAB(a,pos) \ 49289019Sps do { if (store_tab((a),(pos))) return (1); } while (0) 49389019Sps 49489019Sps static int 49589019Spsstore_tab(attr, pos) 49689019Sps int attr; 49789019Sps POSITION pos; 49889019Sps{ 49989019Sps int to_tab = column + cshift - lmargin; 50089019Sps int i; 50189019Sps 50289019Sps if (ntabstops < 2 || to_tab >= tabstops[ntabstops-1]) 50389019Sps to_tab = tabdefault - 50489019Sps ((to_tab - tabstops[ntabstops-1]) % tabdefault); 50589019Sps else 50689019Sps { 50789019Sps for (i = ntabstops - 2; i >= 0; i--) 50889019Sps if (to_tab >= tabstops[i]) 50989019Sps break; 51089019Sps to_tab = tabstops[i+1] - to_tab; 51189019Sps } 51289019Sps 51389019Sps do { 51489019Sps STORE_CHAR(' ', attr, pos); 51589019Sps } while (--to_tab > 0); 51689019Sps return 0; 51789019Sps} 51889019Sps 51989019Sps/* 52060786Sps * Append a character to the line buffer. 52160786Sps * Expand tabs into spaces, handle underlining, boldfacing, etc. 52260786Sps * Returns 0 if ok, 1 if couldn't fit in buffer. 52360786Sps */ 52460786Sps public int 52560786Spspappend(c, pos) 52660786Sps register int c; 52760786Sps POSITION pos; 52860786Sps{ 52960786Sps int r; 53060786Sps 53160786Sps if (pendc) 53260786Sps { 53360786Sps if (do_append(pendc, pendpos)) 53460786Sps /* 53560786Sps * Oops. We've probably lost the char which 53660786Sps * was in pendc, since caller won't back up. 53760786Sps */ 53860786Sps return (1); 53960786Sps pendc = '\0'; 54060786Sps } 54160786Sps 54260786Sps if (c == '\r' && bs_mode == BS_SPECIAL) 54360786Sps { 54460786Sps /* 54560786Sps * Don't put the CR into the buffer until we see 54660786Sps * the next char. If the next char is a newline, 54760786Sps * discard the CR. 54860786Sps */ 54960786Sps pendc = c; 55060786Sps pendpos = pos; 55160786Sps return (0); 55260786Sps } 55360786Sps 55460786Sps r = do_append(c, pos); 55560786Sps /* 55660786Sps * If we need to shift the line, do it. 55760786Sps * But wait until we get to at least the middle of the screen, 55860786Sps * so shifting it doesn't affect the chars we're currently 55960786Sps * pappending. (Bold & underline can get messed up otherwise.) 56060786Sps */ 56160786Sps if (cshift < hshift && column > sc_width / 2) 56289019Sps { 56389019Sps linebuf[curr] = '\0'; 56460786Sps pshift(hshift - cshift); 56589019Sps } 56660786Sps return (r); 56760786Sps} 56860786Sps 569128345Stjr#define IS_UTF8_4BYTE(c) ( ((c) & 0xf8) == 0xf0 ) 570128345Stjr#define IS_UTF8_3BYTE(c) ( ((c) & 0xf0) == 0xe0 ) 571128345Stjr#define IS_UTF8_2BYTE(c) ( ((c) & 0xe0) == 0xc0 ) 572128345Stjr#define IS_UTF8_TRAIL(c) ( ((c) & 0xc0) == 0x80 ) 573128345Stjr 57460786Sps static int 57560786Spsdo_append(c, pos) 57660786Sps int c; 57760786Sps POSITION pos; 57860786Sps{ 57960786Sps register char *s; 58060786Sps register int a; 58160786Sps 58289019Sps#define STOREC(c,a) \ 58389019Sps if ((c) == '\t') STORE_TAB((a),pos); else STORE_CHAR((c),(a),pos) 58460786Sps 58560786Sps if (c == '\b') 58660786Sps { 58760786Sps switch (bs_mode) 58860786Sps { 58960786Sps case BS_NORMAL: 59089019Sps STORE_CHAR(c, AT_NORMAL, pos); 59160786Sps break; 59260786Sps case BS_CONTROL: 59360786Sps goto do_control_char; 59460786Sps case BS_SPECIAL: 59560786Sps if (curr == 0) 59660786Sps break; 59760786Sps backc(); 59860786Sps overstrike = 1; 59960786Sps break; 60060786Sps } 60160786Sps } else if (overstrike) 60260786Sps { 60360786Sps /* 60460786Sps * Overstrike the character at the current position 60560786Sps * in the line buffer. This will cause either 60660786Sps * underline (if a "_" is overstruck), 60760786Sps * bold (if an identical character is overstruck), 60860786Sps * or just deletion of the character in the buffer. 60960786Sps */ 61089019Sps overstrike--; 611128345Stjr if (utf_mode && IS_UTF8_4BYTE(c) && curr > 2 && (char)c == linebuf[curr-3]) 61289019Sps { 61389019Sps backc(); 61489019Sps backc(); 615128345Stjr backc(); 616128345Stjr STORE_CHAR(linebuf[curr], AT_BOLD, pos); 617128345Stjr overstrike = 3; 618128345Stjr } else if (utf_mode && (IS_UTF8_3BYTE(c) || (overstrike==2 && IS_UTF8_TRAIL(c))) && curr > 1 && (char)c == linebuf[curr-2]) 619128345Stjr { 620128345Stjr backc(); 621128345Stjr backc(); 622128345Stjr STORE_CHAR(linebuf[curr], AT_BOLD, pos); 62389019Sps overstrike = 2; 624128345Stjr } else if (utf_mode && curr > 0 && (IS_UTF8_2BYTE(c) || (overstrike==1 && IS_UTF8_TRAIL(c))) && (char)c == linebuf[curr-1]) 62589019Sps { 62689019Sps backc(); 62789019Sps STORE_CHAR(linebuf[curr], AT_BOLD, pos); 62889019Sps overstrike = 1; 629128345Stjr } else if (utf_mode && curr > 0 && IS_UTF8_TRAIL(c) && attr[curr-1] == AT_UNDERLINE) 630128345Stjr { 631128345Stjr STOREC(c, AT_UNDERLINE); 63289019Sps } else if ((char)c == linebuf[curr]) 63389019Sps { 634128345Stjr /* 635128345Stjr * Overstriking a char with itself means make it bold. 636128345Stjr * But overstriking an underscore with itself is 637128345Stjr * ambiguous. It could mean make it bold, or 638128345Stjr * it could mean make it underlined. 639128345Stjr * Use the previous overstrike to resolve it. 640128345Stjr */ 641128345Stjr if (c == '_' && last_overstrike != AT_NORMAL) 642128345Stjr STOREC(c, last_overstrike); 643128345Stjr else 644128345Stjr STOREC(c, AT_BOLD); 64589019Sps } else if (c == '_') 64689019Sps { 64789019Sps if (utf_mode) 64889019Sps { 649128345Stjr int i; 650128345Stjr for (i = 0; i < 5; i++) 651128345Stjr { 652128345Stjr if (curr <= i || !IS_CONT(linebuf[curr-i])) 653128345Stjr break; 654128345Stjr attr[curr-i-1] = AT_UNDERLINE; 655128345Stjr } 65689019Sps } 65760786Sps STOREC(linebuf[curr], AT_UNDERLINE); 65889019Sps } else if (linebuf[curr] == '_') 65989019Sps { 660128345Stjr if (utf_mode) 661128345Stjr { 662128345Stjr if (IS_UTF8_2BYTE(c)) 663128345Stjr overstrike = 1; 664128345Stjr else if (IS_UTF8_3BYTE(c)) 665128345Stjr overstrike = 2; 666128345Stjr else if (IS_UTF8_4BYTE(c)) 667128345Stjr overstrike = 3; 668128345Stjr } 66960786Sps STOREC(c, AT_UNDERLINE); 67089019Sps } else if (control_char(c)) 67160786Sps goto do_control_char; 67260786Sps else 67360786Sps STOREC(c, AT_NORMAL); 67460786Sps } else if (c == '\t') 67560786Sps { 67660786Sps /* 67760786Sps * Expand a tab into spaces. 67860786Sps */ 67960786Sps switch (bs_mode) 68060786Sps { 68160786Sps case BS_CONTROL: 68260786Sps goto do_control_char; 68360786Sps case BS_NORMAL: 68460786Sps case BS_SPECIAL: 68589019Sps STORE_TAB(AT_NORMAL, pos); 68660786Sps break; 68760786Sps } 68860786Sps } else if (control_char(c)) 68960786Sps { 69060786Sps do_control_char: 69160786Sps if (ctldisp == OPT_ON || (ctldisp == OPT_ONPLUS && c == ESC)) 69260786Sps { 69360786Sps /* 69460786Sps * Output as a normal character. 69560786Sps */ 69689019Sps STORE_CHAR(c, AT_NORMAL, pos); 69760786Sps } else 69860786Sps { 69960786Sps /* 70060786Sps * Convert to printable representation. 70160786Sps */ 70260786Sps s = prchar(c); 70360786Sps a = binattr; 70460786Sps 70560786Sps /* 70660786Sps * Make sure we can get the entire representation 70760786Sps * of the character on this line. 70860786Sps */ 70960786Sps if (column + (int) strlen(s) + 71060786Sps attr_swidth(a) + attr_ewidth(a) > sc_width) 71160786Sps return (1); 71260786Sps 71360786Sps for ( ; *s != 0; s++) 71489019Sps STORE_CHAR(*s, a, pos); 71560786Sps } 71660786Sps } else 71760786Sps { 71860786Sps STOREC(c, AT_NORMAL); 71960786Sps } 72060786Sps 72160786Sps return (0); 72260786Sps} 72360786Sps 72460786Sps/* 72560786Sps * Terminate the line in the line buffer. 72660786Sps */ 72760786Sps public void 72860786Spspdone(endline) 72960786Sps int endline; 73060786Sps{ 73160786Sps if (pendc && (pendc != '\r' || !endline)) 73260786Sps /* 73360786Sps * If we had a pending character, put it in the buffer. 73460786Sps * But discard a pending CR if we are at end of line 73560786Sps * (that is, discard the CR in a CR/LF sequence). 73660786Sps */ 73760786Sps (void) do_append(pendc, pendpos); 73860786Sps 73960786Sps /* 74060786Sps * Make sure we've shifted the line, if we need to. 74160786Sps */ 74260786Sps if (cshift < hshift) 74360786Sps pshift(hshift - cshift); 74460786Sps 74560786Sps /* 74660786Sps * Add a newline if necessary, 74760786Sps * and append a '\0' to the end of the line. 74860786Sps */ 74960786Sps if (column < sc_width || !auto_wrap || ignaw || ctldisp == OPT_ON) 75060786Sps { 75160786Sps linebuf[curr] = '\n'; 75260786Sps attr[curr] = AT_NORMAL; 75360786Sps curr++; 75460786Sps } 75560786Sps linebuf[curr] = '\0'; 75660786Sps attr[curr] = AT_NORMAL; 75789019Sps 75889019Sps#if HILITE_SEARCH 75989019Sps if (status_col && hilites > 0) 76089019Sps { 76189019Sps linebuf[0] = '*'; 76289019Sps attr[0] = AT_STANDOUT; 76389019Sps } 76489019Sps#endif 76560786Sps /* 76660786Sps * If we are done with this line, reset the current shift. 76760786Sps */ 76860786Sps if (endline) 76960786Sps cshift = 0; 77060786Sps} 77160786Sps 77260786Sps/* 77360786Sps * Get a character from the current line. 77460786Sps * Return the character as the function return value, 77560786Sps * and the character attribute in *ap. 77660786Sps */ 77760786Sps public int 77860786Spsgline(i, ap) 77960786Sps register int i; 78060786Sps register int *ap; 78160786Sps{ 78260786Sps char *s; 78360786Sps 78460786Sps if (is_null_line) 78560786Sps { 78660786Sps /* 78760786Sps * If there is no current line, we pretend the line is 78860786Sps * either "~" or "", depending on the "twiddle" flag. 78960786Sps */ 79060786Sps *ap = AT_BOLD; 79160786Sps s = (twiddle) ? "~\n" : "\n"; 79260786Sps return (s[i]); 79360786Sps } 79460786Sps 79560786Sps *ap = attr[i]; 79660786Sps return (linebuf[i] & 0377); 79760786Sps} 79860786Sps 79960786Sps/* 80060786Sps * Indicate that there is no current line. 80160786Sps */ 80260786Sps public void 80360786Spsnull_line() 80460786Sps{ 80560786Sps is_null_line = 1; 80660786Sps cshift = 0; 80760786Sps} 80860786Sps 80960786Sps/* 81060786Sps * Analogous to forw_line(), but deals with "raw lines": 81160786Sps * lines which are not split for screen width. 81260786Sps * {{ This is supposed to be more efficient than forw_line(). }} 81360786Sps */ 81460786Sps public POSITION 81560786Spsforw_raw_line(curr_pos, linep) 81660786Sps POSITION curr_pos; 81760786Sps char **linep; 81860786Sps{ 81989019Sps register int n; 82060786Sps register int c; 82160786Sps POSITION new_pos; 82260786Sps 82360786Sps if (curr_pos == NULL_POSITION || ch_seek(curr_pos) || 82460786Sps (c = ch_forw_get()) == EOI) 82560786Sps return (NULL_POSITION); 82660786Sps 82789019Sps n = 0; 82860786Sps for (;;) 82960786Sps { 83060786Sps if (c == '\n' || c == EOI) 83160786Sps { 83260786Sps new_pos = ch_tell(); 83360786Sps break; 83460786Sps } 83589019Sps if (n >= size_linebuf-1) 83660786Sps { 83789019Sps if (expand_linebuf()) 83889019Sps { 83989019Sps /* 84089019Sps * Overflowed the input buffer. 84189019Sps * Pretend the line ended here. 84289019Sps */ 84389019Sps new_pos = ch_tell() - 1; 84489019Sps break; 84589019Sps } 84660786Sps } 84789019Sps linebuf[n++] = c; 84860786Sps c = ch_forw_get(); 84960786Sps } 85089019Sps linebuf[n] = '\0'; 85160786Sps if (linep != NULL) 85260786Sps *linep = linebuf; 85360786Sps return (new_pos); 85460786Sps} 85560786Sps 85660786Sps/* 85760786Sps * Analogous to back_line(), but deals with "raw lines". 85860786Sps * {{ This is supposed to be more efficient than back_line(). }} 85960786Sps */ 86060786Sps public POSITION 86160786Spsback_raw_line(curr_pos, linep) 86260786Sps POSITION curr_pos; 86360786Sps char **linep; 86460786Sps{ 86589019Sps register int n; 86660786Sps register int c; 86760786Sps POSITION new_pos; 86860786Sps 86960786Sps if (curr_pos == NULL_POSITION || curr_pos <= ch_zero() || 87060786Sps ch_seek(curr_pos-1)) 87160786Sps return (NULL_POSITION); 87260786Sps 87389019Sps n = size_linebuf; 87489019Sps linebuf[--n] = '\0'; 87560786Sps for (;;) 87660786Sps { 87760786Sps c = ch_back_get(); 87860786Sps if (c == '\n') 87960786Sps { 88060786Sps /* 88160786Sps * This is the newline ending the previous line. 88260786Sps * We have hit the beginning of the line. 88360786Sps */ 88460786Sps new_pos = ch_tell() + 1; 88560786Sps break; 88660786Sps } 88760786Sps if (c == EOI) 88860786Sps { 88960786Sps /* 89060786Sps * We have hit the beginning of the file. 89160786Sps * This must be the first line in the file. 89260786Sps * This must, of course, be the beginning of the line. 89360786Sps */ 89460786Sps new_pos = ch_zero(); 89560786Sps break; 89660786Sps } 89789019Sps if (n <= 0) 89860786Sps { 89989019Sps int old_size_linebuf = size_linebuf; 90089019Sps char *fm; 90189019Sps char *to; 90289019Sps if (expand_linebuf()) 90389019Sps { 90489019Sps /* 90589019Sps * Overflowed the input buffer. 90689019Sps * Pretend the line ended here. 90789019Sps */ 90889019Sps new_pos = ch_tell() + 1; 90989019Sps break; 91089019Sps } 91160786Sps /* 91289019Sps * Shift the data to the end of the new linebuf. 91360786Sps */ 91489019Sps for (fm = linebuf + old_size_linebuf, 91589019Sps to = linebuf + size_linebuf; 91689019Sps fm >= linebuf; fm--, to--) 91789019Sps *to = *fm; 91889019Sps n = size_linebuf - old_size_linebuf; 91960786Sps } 92089019Sps linebuf[--n] = c; 92160786Sps } 92260786Sps if (linep != NULL) 92389019Sps *linep = &linebuf[n]; 92460786Sps return (new_pos); 92560786Sps} 926