Home | History | Annotate | Line # | Download | only in more
line.c revision 1.2
      1 /*	$NetBSD: line.c,v 1.2 1998/01/09 08:03:29 perry Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1988 Mark Nudleman
      5  * Copyright (c) 1988, 1993
      6  *	The Regents of the University of California.  All rights reserved.
      7  *
      8  * Redistribution and use in source and binary forms, with or without
      9  * modification, are permitted provided that the following conditions
     10  * are met:
     11  * 1. Redistributions of source code must retain the above copyright
     12  *    notice, this list of conditions and the following disclaimer.
     13  * 2. Redistributions in binary form must reproduce the above copyright
     14  *    notice, this list of conditions and the following disclaimer in the
     15  *    documentation and/or other materials provided with the distribution.
     16  * 3. All advertising materials mentioning features or use of this software
     17  *    must display the following acknowledgement:
     18  *	This product includes software developed by the University of
     19  *	California, Berkeley and its contributors.
     20  * 4. Neither the name of the University nor the names of its contributors
     21  *    may be used to endorse or promote products derived from this software
     22  *    without specific prior written permission.
     23  *
     24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     34  * SUCH DAMAGE.
     35  */
     36 
     37 #ifndef lint
     38 static char sccsid[] = "@(#)line.c	8.1 (Berkeley) 6/6/93";
     39 #endif /* not lint */
     40 
     41 /*
     42  * Routines to manipulate the "line buffer".
     43  * The line buffer holds a line of output as it is being built
     44  * in preparation for output to the screen.
     45  * We keep track of the PRINTABLE length of the line as it is being built.
     46  */
     47 
     48 #include <sys/types.h>
     49 #include <ctype.h>
     50 #include <less.h>
     51 
     52 static char linebuf[1024];	/* Buffer which holds the current output line */
     53 static char *curr;		/* Pointer into linebuf */
     54 static int column;		/* Printable length, accounting for
     55 				   backspaces, etc. */
     56 /*
     57  * A ridiculously complex state machine takes care of backspaces.  The
     58  * complexity arises from the attempt to deal with all cases, especially
     59  * involving long lines with underlining, boldfacing or whatever.  There
     60  * are still some cases which will break it.
     61  *
     62  * There are four states:
     63  *	LN_NORMAL is the normal state (not in underline mode).
     64  *	LN_UNDERLINE means we are in underline mode.  We expect to get
     65  *		either a sequence like "_\bX" or "X\b_" to continue
     66  *		underline mode, or anything else to end underline mode.
     67  *	LN_BOLDFACE means we are in boldface mode.  We expect to get sequences
     68  *		like "X\bX\b...X\bX" to continue boldface mode, or anything
     69  *		else to end boldface mode.
     70  *	LN_UL_X means we are one character after LN_UNDERLINE
     71  *		(we have gotten the '_' in "_\bX" or the 'X' in "X\b_").
     72  *	LN_UL_XB means we are one character after LN_UL_X
     73  *		(we have gotten the backspace in "_\bX" or "X\b_";
     74  *		we expect one more ordinary character,
     75  *		which will put us back in state LN_UNDERLINE).
     76  *	LN_BO_X means we are one character after LN_BOLDFACE
     77  *		(we have gotten the 'X' in "X\bX").
     78  *	LN_BO_XB means we are one character after LN_BO_X
     79  *		(we have gotten the backspace in "X\bX";
     80  *		we expect one more 'X' which will put us back
     81  *		in LN_BOLDFACE).
     82  */
     83 static int ln_state;		/* Currently in normal/underline/bold/etc mode? */
     84 #define	LN_NORMAL	0	/* Not in underline, boldface or whatever mode */
     85 #define	LN_UNDERLINE	1	/* In underline, need next char */
     86 #define	LN_UL_X		2	/* In underline, got char, need \b */
     87 #define	LN_UL_XB	3	/* In underline, got char & \b, need one more */
     88 #define	LN_BOLDFACE	4	/* In boldface, need next char */
     89 #define	LN_BO_X		5	/* In boldface, got char, need \b */
     90 #define	LN_BO_XB	6	/* In boldface, got char & \b, need same char */
     91 
     92 char *line;			/* Pointer to the current line.
     93 				   Usually points to linebuf. */
     94 
     95 extern int bs_mode;
     96 extern int tabstop;
     97 extern int bo_width, be_width;
     98 extern int ul_width, ue_width;
     99 extern int sc_width, sc_height;
    100 
    101 /*
    102  * Rewind the line buffer.
    103  */
    104 prewind()
    105 {
    106 	line = curr = linebuf;
    107 	ln_state = LN_NORMAL;
    108 	column = 0;
    109 }
    110 
    111 /*
    112  * Append a character to the line buffer.
    113  * Expand tabs into spaces, handle underlining, boldfacing, etc.
    114  * Returns 0 if ok, 1 if couldn't fit in buffer.
    115  */
    116 #define	NEW_COLUMN(addon) \
    117 	if (column + addon + (ln_state ? ue_width : 0) > sc_width) \
    118 		return(1); \
    119 	else \
    120 		column += addon
    121 
    122 pappend(c)
    123 	int c;
    124 {
    125 	if (c == '\0') {
    126 		/*
    127 		 * Terminate any special modes, if necessary.
    128 		 * Append a '\0' to the end of the line.
    129 		 */
    130 		switch (ln_state) {
    131 		case LN_UL_X:
    132 			curr[0] = curr[-1];
    133 			curr[-1] = UE_CHAR;
    134 			curr++;
    135 			break;
    136 		case LN_BO_X:
    137 			curr[0] = curr[-1];
    138 			curr[-1] = BE_CHAR;
    139 			curr++;
    140 			break;
    141 		case LN_UL_XB:
    142 		case LN_UNDERLINE:
    143 			*curr++ = UE_CHAR;
    144 			break;
    145 		case LN_BO_XB:
    146 		case LN_BOLDFACE:
    147 			*curr++ = BE_CHAR;
    148 			break;
    149 		}
    150 		ln_state = LN_NORMAL;
    151 		*curr = '\0';
    152 		return(0);
    153 	}
    154 
    155 	if (curr > linebuf + sizeof(linebuf) - 12)
    156 		/*
    157 		 * Almost out of room in the line buffer.
    158 		 * Don't take any chances.
    159 		 * {{ Linebuf is supposed to be big enough that this
    160 		 *    will never happen, but may need to be made
    161 		 *    bigger for wide screens or lots of backspaces. }}
    162 		 */
    163 		return(1);
    164 
    165 	if (!bs_mode) {
    166 		/*
    167 		 * Advance the state machine.
    168 		 */
    169 		switch (ln_state) {
    170 		case LN_NORMAL:
    171 			if (curr <= linebuf + 1
    172 			    || curr[-1] != (char)('H' | 0200))
    173 				break;
    174 			column -= 2;
    175 			if (c == curr[-2])
    176 				goto enter_boldface;
    177 			if (c == '_' || curr[-2] == '_')
    178 				goto enter_underline;
    179 			curr -= 2;
    180 			break;
    181 
    182 enter_boldface:
    183 			/*
    184 			 * We have "X\bX" (including the current char).
    185 			 * Switch into boldface mode.
    186 			 */
    187 			column--;
    188 			if (column + bo_width + be_width + 1 >= sc_width)
    189 				/*
    190 				 * Not enough room left on the screen to
    191 				 * enter and exit boldface mode.
    192 				 */
    193 				return (1);
    194 
    195 			if (bo_width > 0 && curr > linebuf + 2
    196 			    && curr[-3] == ' ') {
    197 				/*
    198 				 * Special case for magic cookie terminals:
    199 				 * if the previous char was a space, replace
    200 				 * it with the "enter boldface" sequence.
    201 				 */
    202 				curr[-3] = BO_CHAR;
    203 				column += bo_width-1;
    204 			} else {
    205 				curr[-1] = curr[-2];
    206 				curr[-2] = BO_CHAR;
    207 				column += bo_width;
    208 				curr++;
    209 			}
    210 			goto ln_bo_xb_case;
    211 
    212 enter_underline:
    213 			/*
    214 			 * We have either "_\bX" or "X\b_" (including
    215 			 * the current char).  Switch into underline mode.
    216 			 */
    217 			column--;
    218 			if (column + ul_width + ue_width + 1 >= sc_width)
    219 				/*
    220 				 * Not enough room left on the screen to
    221 				 * enter and exit underline mode.
    222 				 */
    223 				return (1);
    224 
    225 			if (ul_width > 0 &&
    226 			    curr > linebuf + 2 && curr[-3] == ' ')
    227 			{
    228 				/*
    229 				 * Special case for magic cookie terminals:
    230 				 * if the previous char was a space, replace
    231 				 * it with the "enter underline" sequence.
    232 				 */
    233 				curr[-3] = UL_CHAR;
    234 				column += ul_width-1;
    235 			} else
    236 			{
    237 				curr[-1] = curr[-2];
    238 				curr[-2] = UL_CHAR;
    239 				column += ul_width;
    240 				curr++;
    241 			}
    242 			goto ln_ul_xb_case;
    243 			/*NOTREACHED*/
    244 		case LN_UL_XB:
    245 			/*
    246 			 * Termination of a sequence "_\bX" or "X\b_".
    247 			 */
    248 			if (c != '_' && curr[-2] != '_' && c == curr[-2])
    249 			{
    250 				/*
    251 				 * We seem to have run on from underlining
    252 				 * into boldfacing - this is a nasty fix, but
    253 				 * until this whole routine is rewritten as a
    254 				 * real DFA, ...  well ...
    255 				 */
    256 				curr[0] = curr[-2];
    257 				curr[-2] = UE_CHAR;
    258 				curr[-1] = BO_CHAR;
    259 				curr += 2; /* char & non-existent backspace */
    260 				ln_state = LN_BO_XB;
    261 				goto ln_bo_xb_case;
    262 			}
    263 ln_ul_xb_case:
    264 			if (c == '_')
    265 				c = curr[-2];
    266 			curr -= 2;
    267 			ln_state = LN_UNDERLINE;
    268 			break;
    269 		case LN_BO_XB:
    270 			/*
    271 			 * Termination of a sequnce "X\bX".
    272 			 */
    273 			if (c != curr[-2] && (c == '_' || curr[-2] == '_'))
    274 			{
    275 				/*
    276 				 * We seem to have run on from
    277 				 * boldfacing into underlining.
    278 				 */
    279 				curr[0] = curr[-2];
    280 				curr[-2] = BE_CHAR;
    281 				curr[-1] = UL_CHAR;
    282 				curr += 2; /* char & non-existent backspace */
    283 				ln_state = LN_UL_XB;
    284 				goto ln_ul_xb_case;
    285 			}
    286 ln_bo_xb_case:
    287 			curr -= 2;
    288 			ln_state = LN_BOLDFACE;
    289 			break;
    290 		case LN_UNDERLINE:
    291 			if (column + ue_width + bo_width + 1 + be_width >= sc_width)
    292 				/*
    293 				 * We have just barely enough room to
    294 				 * exit underline mode and handle a possible
    295 				 * underline/boldface run on mixup.
    296 				 */
    297 				return (1);
    298 			ln_state = LN_UL_X;
    299 			break;
    300 		case LN_BOLDFACE:
    301 			if (c == '\b')
    302 			{
    303 				ln_state = LN_BO_XB;
    304 				break;
    305 			}
    306 			if (column + be_width + ul_width + 1 + ue_width >= sc_width)
    307 				/*
    308 				 * We have just barely enough room to
    309 				 * exit underline mode and handle a possible
    310 				 * underline/boldface run on mixup.
    311 				 */
    312 				return (1);
    313 			ln_state = LN_BO_X;
    314 			break;
    315 		case LN_UL_X:
    316 			if (c == '\b')
    317 				ln_state = LN_UL_XB;
    318 			else
    319 			{
    320 				/*
    321 				 * Exit underline mode.
    322 				 * We have to shuffle the chars a bit
    323 				 * to make this work.
    324 				 */
    325 				curr[0] = curr[-1];
    326 				curr[-1] = UE_CHAR;
    327 				column += ue_width;
    328 				if (ue_width > 0 && curr[0] == ' ')
    329 					/*
    330 					 * Another special case for magic
    331 					 * cookie terminals: if the next
    332 					 * char is a space, replace it
    333 					 * with the "exit underline" sequence.
    334 					 */
    335 					column--;
    336 				else
    337 					curr++;
    338 				ln_state = LN_NORMAL;
    339 			}
    340 			break;
    341 		case LN_BO_X:
    342 			if (c == '\b')
    343 				ln_state = LN_BO_XB;
    344 			else
    345 			{
    346 				/*
    347 				 * Exit boldface mode.
    348 				 * We have to shuffle the chars a bit
    349 				 * to make this work.
    350 				 */
    351 				curr[0] = curr[-1];
    352 				curr[-1] = BE_CHAR;
    353 				column += be_width;
    354 				if (be_width > 0 && curr[0] == ' ')
    355 					/*
    356 					 * Another special case for magic
    357 					 * cookie terminals: if the next
    358 					 * char is a space, replace it
    359 					 * with the "exit boldface" sequence.
    360 					 */
    361 					column--;
    362 				else
    363 					curr++;
    364 				ln_state = LN_NORMAL;
    365 			}
    366 			break;
    367 		}
    368 	}
    369 
    370 	if (c == '\t') {
    371 		/*
    372 		 * Expand a tab into spaces.
    373 		 */
    374 		do {
    375 			NEW_COLUMN(1);
    376 		} while ((column % tabstop) != 0);
    377 		*curr++ = '\t';
    378 		return (0);
    379 	}
    380 
    381 	if (c == '\b') {
    382 		if (ln_state == LN_NORMAL)
    383 			NEW_COLUMN(2);
    384 		else
    385 			column--;
    386 		*curr++ = ('H' | 0200);
    387 		return(0);
    388 	}
    389 
    390 	if (CONTROL_CHAR(c)) {
    391 		/*
    392 		 * Put a "^X" into the buffer.  The 0200 bit is used to tell
    393 		 * put_line() to prefix the char with a ^.  We don't actually
    394 		 * put the ^ in the buffer because we sometimes need to move
    395 		 * chars around, and such movement might separate the ^ from
    396 		 * its following character.
    397 		 */
    398 		NEW_COLUMN(2);
    399 		*curr++ = (CARAT_CHAR(c) | 0200);
    400 		return(0);
    401 	}
    402 
    403 	/*
    404 	 * Ordinary character.  Just put it in the buffer.
    405 	 */
    406 	NEW_COLUMN(1);
    407 	*curr++ = c;
    408 	return (0);
    409 }
    410 
    411 /*
    412  * Analogous to forw_line(), but deals with "raw lines":
    413  * lines which are not split for screen width.
    414  * {{ This is supposed to be more efficient than forw_line(). }}
    415  */
    416 off_t
    417 forw_raw_line(curr_pos)
    418 	off_t curr_pos;
    419 {
    420 	register char *p;
    421 	register int c;
    422 	off_t new_pos, ch_tell();
    423 
    424 	if (curr_pos == NULL_POSITION || ch_seek(curr_pos) ||
    425 		(c = ch_forw_get()) == EOI)
    426 		return (NULL_POSITION);
    427 
    428 	p = linebuf;
    429 
    430 	for (;;)
    431 	{
    432 		if (c == '\n' || c == EOI)
    433 		{
    434 			new_pos = ch_tell();
    435 			break;
    436 		}
    437 		if (p >= &linebuf[sizeof(linebuf)-1])
    438 		{
    439 			/*
    440 			 * Overflowed the input buffer.
    441 			 * Pretend the line ended here.
    442 			 * {{ The line buffer is supposed to be big
    443 			 *    enough that this never happens. }}
    444 			 */
    445 			new_pos = ch_tell() - 1;
    446 			break;
    447 		}
    448 		*p++ = c;
    449 		c = ch_forw_get();
    450 	}
    451 	*p = '\0';
    452 	line = linebuf;
    453 	return (new_pos);
    454 }
    455 
    456 /*
    457  * Analogous to back_line(), but deals with "raw lines".
    458  * {{ This is supposed to be more efficient than back_line(). }}
    459  */
    460 off_t
    461 back_raw_line(curr_pos)
    462 	off_t curr_pos;
    463 {
    464 	register char *p;
    465 	register int c;
    466 	off_t new_pos, ch_tell();
    467 
    468 	if (curr_pos == NULL_POSITION || curr_pos <= (off_t)0 ||
    469 		ch_seek(curr_pos-1))
    470 		return (NULL_POSITION);
    471 
    472 	p = &linebuf[sizeof(linebuf)];
    473 	*--p = '\0';
    474 
    475 	for (;;)
    476 	{
    477 		c = ch_back_get();
    478 		if (c == '\n')
    479 		{
    480 			/*
    481 			 * This is the newline ending the previous line.
    482 			 * We have hit the beginning of the line.
    483 			 */
    484 			new_pos = ch_tell() + 1;
    485 			break;
    486 		}
    487 		if (c == EOI)
    488 		{
    489 			/*
    490 			 * We have hit the beginning of the file.
    491 			 * This must be the first line in the file.
    492 			 * This must, of course, be the beginning of the line.
    493 			 */
    494 			new_pos = (off_t)0;
    495 			break;
    496 		}
    497 		if (p <= linebuf)
    498 		{
    499 			/*
    500 			 * Overflowed the input buffer.
    501 			 * Pretend the line ended here.
    502 			 */
    503 			new_pos = ch_tell() + 1;
    504 			break;
    505 		}
    506 		*--p = c;
    507 	}
    508 	line = p;
    509 	return (new_pos);
    510 }
    511