xref: /csrg-svn/usr.bin/more/line.c (revision 62131)
135209Sbostic /*
235209Sbostic  * Copyright (c) 1988 Mark Nudleman
3*62131Sbostic  * Copyright (c) 1988, 1993
4*62131Sbostic  *	The Regents of the University of California.  All rights reserved.
535209Sbostic  *
642742Sbostic  * %sccs.include.redist.c%
735209Sbostic  */
835209Sbostic 
935209Sbostic #ifndef lint
10*62131Sbostic static char sccsid[] = "@(#)line.c	8.1 (Berkeley) 06/06/93";
1135209Sbostic #endif /* not lint */
1235209Sbostic 
1335209Sbostic /*
1435209Sbostic  * Routines to manipulate the "line buffer".
1535209Sbostic  * The line buffer holds a line of output as it is being built
1635209Sbostic  * in preparation for output to the screen.
1735209Sbostic  * We keep track of the PRINTABLE length of the line as it is being built.
1835209Sbostic  */
1935209Sbostic 
2036251Sbostic #include <sys/types.h>
2136251Sbostic #include <ctype.h>
2236251Sbostic #include <less.h>
2335209Sbostic 
2435209Sbostic static char linebuf[1024];	/* Buffer which holds the current output line */
2535209Sbostic static char *curr;		/* Pointer into linebuf */
2635209Sbostic static int column;		/* Printable length, accounting for
2735209Sbostic 				   backspaces, etc. */
2835209Sbostic /*
2936251Sbostic  * A ridiculously complex state machine takes care of backspaces.  The
3036251Sbostic  * complexity arises from the attempt to deal with all cases, especially
3136251Sbostic  * involving long lines with underlining, boldfacing or whatever.  There
3236251Sbostic  * are still some cases which will break it.
3335209Sbostic  *
3435209Sbostic  * There are four states:
3535209Sbostic  *	LN_NORMAL is the normal state (not in underline mode).
3635209Sbostic  *	LN_UNDERLINE means we are in underline mode.  We expect to get
3735209Sbostic  *		either a sequence like "_\bX" or "X\b_" to continue
3835209Sbostic  *		underline mode, or anything else to end underline mode.
3935209Sbostic  *	LN_BOLDFACE means we are in boldface mode.  We expect to get sequences
4035209Sbostic  *		like "X\bX\b...X\bX" to continue boldface mode, or anything
4135209Sbostic  *		else to end boldface mode.
4235209Sbostic  *	LN_UL_X means we are one character after LN_UNDERLINE
4335209Sbostic  *		(we have gotten the '_' in "_\bX" or the 'X' in "X\b_").
4435209Sbostic  *	LN_UL_XB means we are one character after LN_UL_X
4535209Sbostic  *		(we have gotten the backspace in "_\bX" or "X\b_";
4635209Sbostic  *		we expect one more ordinary character,
4735209Sbostic  *		which will put us back in state LN_UNDERLINE).
4835209Sbostic  *	LN_BO_X means we are one character after LN_BOLDFACE
4935209Sbostic  *		(we have gotten the 'X' in "X\bX").
5035209Sbostic  *	LN_BO_XB means we are one character after LN_BO_X
5135209Sbostic  *		(we have gotten the backspace in "X\bX";
5235209Sbostic  *		we expect one more 'X' which will put us back
5335209Sbostic  *		in LN_BOLDFACE).
5435209Sbostic  */
5535209Sbostic static int ln_state;		/* Currently in normal/underline/bold/etc mode? */
5635209Sbostic #define	LN_NORMAL	0	/* Not in underline, boldface or whatever mode */
5735209Sbostic #define	LN_UNDERLINE	1	/* In underline, need next char */
5835209Sbostic #define	LN_UL_X		2	/* In underline, got char, need \b */
5935209Sbostic #define	LN_UL_XB	3	/* In underline, got char & \b, need one more */
6035209Sbostic #define	LN_BOLDFACE	4	/* In boldface, need next char */
6135209Sbostic #define	LN_BO_X		5	/* In boldface, got char, need \b */
6235209Sbostic #define	LN_BO_XB	6	/* In boldface, got char & \b, need same char */
6335209Sbostic 
6436251Sbostic char *line;			/* Pointer to the current line.
6535209Sbostic 				   Usually points to linebuf. */
6635209Sbostic 
6735209Sbostic extern int bs_mode;
6835209Sbostic extern int tabstop;
6935209Sbostic extern int bo_width, be_width;
7035209Sbostic extern int ul_width, ue_width;
7135209Sbostic extern int sc_width, sc_height;
7235209Sbostic 
7335209Sbostic /*
7435209Sbostic  * Rewind the line buffer.
7535209Sbostic  */
prewind()7635209Sbostic prewind()
7735209Sbostic {
7835209Sbostic 	line = curr = linebuf;
7935209Sbostic 	ln_state = LN_NORMAL;
8035209Sbostic 	column = 0;
8135209Sbostic }
8235209Sbostic 
8335209Sbostic /*
8435209Sbostic  * Append a character to the line buffer.
8535209Sbostic  * Expand tabs into spaces, handle underlining, boldfacing, etc.
8635209Sbostic  * Returns 0 if ok, 1 if couldn't fit in buffer.
8735209Sbostic  */
8836251Sbostic #define	NEW_COLUMN(addon) \
8936251Sbostic 	if (column + addon + (ln_state ? ue_width : 0) > sc_width) \
9036251Sbostic 		return(1); \
9136251Sbostic 	else \
9236251Sbostic 		column += addon
9335209Sbostic 
pappend(c)9435209Sbostic pappend(c)
9535209Sbostic 	int c;
9635209Sbostic {
9736251Sbostic 	if (c == '\0') {
9835209Sbostic 		/*
9935209Sbostic 		 * Terminate any special modes, if necessary.
10035209Sbostic 		 * Append a '\0' to the end of the line.
10135209Sbostic 		 */
10236251Sbostic 		switch (ln_state) {
10335209Sbostic 		case LN_UL_X:
10435209Sbostic 			curr[0] = curr[-1];
10535209Sbostic 			curr[-1] = UE_CHAR;
10635209Sbostic 			curr++;
10735209Sbostic 			break;
10835209Sbostic 		case LN_BO_X:
10935209Sbostic 			curr[0] = curr[-1];
11035209Sbostic 			curr[-1] = BE_CHAR;
11135209Sbostic 			curr++;
11235209Sbostic 			break;
11335209Sbostic 		case LN_UL_XB:
11435209Sbostic 		case LN_UNDERLINE:
11535209Sbostic 			*curr++ = UE_CHAR;
11635209Sbostic 			break;
11735209Sbostic 		case LN_BO_XB:
11835209Sbostic 		case LN_BOLDFACE:
11935209Sbostic 			*curr++ = BE_CHAR;
12035209Sbostic 			break;
12135209Sbostic 		}
12235209Sbostic 		ln_state = LN_NORMAL;
12335209Sbostic 		*curr = '\0';
12436251Sbostic 		return(0);
12535209Sbostic 	}
12635209Sbostic 
12735209Sbostic 	if (curr > linebuf + sizeof(linebuf) - 12)
12835209Sbostic 		/*
12935209Sbostic 		 * Almost out of room in the line buffer.
13035209Sbostic 		 * Don't take any chances.
13135209Sbostic 		 * {{ Linebuf is supposed to be big enough that this
13235209Sbostic 		 *    will never happen, but may need to be made
13335209Sbostic 		 *    bigger for wide screens or lots of backspaces. }}
13435209Sbostic 		 */
13536251Sbostic 		return(1);
13635209Sbostic 
13736251Sbostic 	if (!bs_mode) {
13835209Sbostic 		/*
13935209Sbostic 		 * Advance the state machine.
14035209Sbostic 		 */
14136251Sbostic 		switch (ln_state) {
14235209Sbostic 		case LN_NORMAL:
14336251Sbostic 			if (curr <= linebuf + 1
14436251Sbostic 			    || curr[-1] != (char)('H' | 0200))
14535209Sbostic 				break;
14636251Sbostic 			column -= 2;
14735209Sbostic 			if (c == curr[-2])
14835209Sbostic 				goto enter_boldface;
14935209Sbostic 			if (c == '_' || curr[-2] == '_')
15035209Sbostic 				goto enter_underline;
15135209Sbostic 			curr -= 2;
15235209Sbostic 			break;
15335209Sbostic 
15435209Sbostic enter_boldface:
15535209Sbostic 			/*
15635209Sbostic 			 * We have "X\bX" (including the current char).
15735209Sbostic 			 * Switch into boldface mode.
15835209Sbostic 			 */
15950517Scael 			column--;
16035209Sbostic 			if (column + bo_width + be_width + 1 >= sc_width)
16135209Sbostic 				/*
16235209Sbostic 				 * Not enough room left on the screen to
16335209Sbostic 				 * enter and exit boldface mode.
16435209Sbostic 				 */
16535209Sbostic 				return (1);
16635209Sbostic 
16736251Sbostic 			if (bo_width > 0 && curr > linebuf + 2
16836251Sbostic 			    && curr[-3] == ' ') {
16935209Sbostic 				/*
17035209Sbostic 				 * Special case for magic cookie terminals:
17135209Sbostic 				 * if the previous char was a space, replace
17235209Sbostic 				 * it with the "enter boldface" sequence.
17335209Sbostic 				 */
17435209Sbostic 				curr[-3] = BO_CHAR;
17535209Sbostic 				column += bo_width-1;
17636251Sbostic 			} else {
17735209Sbostic 				curr[-1] = curr[-2];
17835209Sbostic 				curr[-2] = BO_CHAR;
17935209Sbostic 				column += bo_width;
18035209Sbostic 				curr++;
18135209Sbostic 			}
18235209Sbostic 			goto ln_bo_xb_case;
18335209Sbostic 
18435209Sbostic enter_underline:
18535209Sbostic 			/*
18635209Sbostic 			 * We have either "_\bX" or "X\b_" (including
18735209Sbostic 			 * the current char).  Switch into underline mode.
18835209Sbostic 			 */
18950517Scael 			column--;
19035209Sbostic 			if (column + ul_width + ue_width + 1 >= sc_width)
19135209Sbostic 				/*
19235209Sbostic 				 * Not enough room left on the screen to
19335209Sbostic 				 * enter and exit underline mode.
19435209Sbostic 				 */
19535209Sbostic 				return (1);
19635209Sbostic 
19735209Sbostic 			if (ul_width > 0 &&
19835209Sbostic 			    curr > linebuf + 2 && curr[-3] == ' ')
19935209Sbostic 			{
20035209Sbostic 				/*
20135209Sbostic 				 * Special case for magic cookie terminals:
20235209Sbostic 				 * if the previous char was a space, replace
20335209Sbostic 				 * it with the "enter underline" sequence.
20435209Sbostic 				 */
20535209Sbostic 				curr[-3] = UL_CHAR;
20635209Sbostic 				column += ul_width-1;
20735209Sbostic 			} else
20835209Sbostic 			{
20935209Sbostic 				curr[-1] = curr[-2];
21035209Sbostic 				curr[-2] = UL_CHAR;
21135209Sbostic 				column += ul_width;
21235209Sbostic 				curr++;
21335209Sbostic 			}
21435209Sbostic 			goto ln_ul_xb_case;
21535209Sbostic 			/*NOTREACHED*/
21635209Sbostic 		case LN_UL_XB:
21735209Sbostic 			/*
21835209Sbostic 			 * Termination of a sequence "_\bX" or "X\b_".
21935209Sbostic 			 */
22035209Sbostic 			if (c != '_' && curr[-2] != '_' && c == curr[-2])
22135209Sbostic 			{
22235209Sbostic 				/*
22335209Sbostic 				 * We seem to have run on from underlining
22435209Sbostic 				 * into boldfacing - this is a nasty fix, but
22535209Sbostic 				 * until this whole routine is rewritten as a
22635209Sbostic 				 * real DFA, ...  well ...
22735209Sbostic 				 */
22835209Sbostic 				curr[0] = curr[-2];
22935209Sbostic 				curr[-2] = UE_CHAR;
23035209Sbostic 				curr[-1] = BO_CHAR;
23135209Sbostic 				curr += 2; /* char & non-existent backspace */
23235209Sbostic 				ln_state = LN_BO_XB;
23335209Sbostic 				goto ln_bo_xb_case;
23435209Sbostic 			}
23535209Sbostic ln_ul_xb_case:
23635209Sbostic 			if (c == '_')
23735209Sbostic 				c = curr[-2];
23835209Sbostic 			curr -= 2;
23935209Sbostic 			ln_state = LN_UNDERLINE;
24035209Sbostic 			break;
24135209Sbostic 		case LN_BO_XB:
24235209Sbostic 			/*
24335209Sbostic 			 * Termination of a sequnce "X\bX".
24435209Sbostic 			 */
24535209Sbostic 			if (c != curr[-2] && (c == '_' || curr[-2] == '_'))
24635209Sbostic 			{
24735209Sbostic 				/*
24835209Sbostic 				 * We seem to have run on from
24935209Sbostic 				 * boldfacing into underlining.
25035209Sbostic 				 */
25135209Sbostic 				curr[0] = curr[-2];
25235209Sbostic 				curr[-2] = BE_CHAR;
25335209Sbostic 				curr[-1] = UL_CHAR;
25435209Sbostic 				curr += 2; /* char & non-existent backspace */
25535209Sbostic 				ln_state = LN_UL_XB;
25635209Sbostic 				goto ln_ul_xb_case;
25735209Sbostic 			}
25835209Sbostic ln_bo_xb_case:
25935209Sbostic 			curr -= 2;
26035209Sbostic 			ln_state = LN_BOLDFACE;
26135209Sbostic 			break;
26235209Sbostic 		case LN_UNDERLINE:
26335209Sbostic 			if (column + ue_width + bo_width + 1 + be_width >= sc_width)
26435209Sbostic 				/*
26535209Sbostic 				 * We have just barely enough room to
26635209Sbostic 				 * exit underline mode and handle a possible
26735209Sbostic 				 * underline/boldface run on mixup.
26835209Sbostic 				 */
26935209Sbostic 				return (1);
27035209Sbostic 			ln_state = LN_UL_X;
27135209Sbostic 			break;
27235209Sbostic 		case LN_BOLDFACE:
27335209Sbostic 			if (c == '\b')
27435209Sbostic 			{
27535209Sbostic 				ln_state = LN_BO_XB;
27635209Sbostic 				break;
27735209Sbostic 			}
27835209Sbostic 			if (column + be_width + ul_width + 1 + ue_width >= sc_width)
27935209Sbostic 				/*
28035209Sbostic 				 * We have just barely enough room to
28135209Sbostic 				 * exit underline mode and handle a possible
28235209Sbostic 				 * underline/boldface run on mixup.
28335209Sbostic 				 */
28435209Sbostic 				return (1);
28535209Sbostic 			ln_state = LN_BO_X;
28635209Sbostic 			break;
28735209Sbostic 		case LN_UL_X:
28835209Sbostic 			if (c == '\b')
28935209Sbostic 				ln_state = LN_UL_XB;
29035209Sbostic 			else
29135209Sbostic 			{
29235209Sbostic 				/*
29335209Sbostic 				 * Exit underline mode.
29435209Sbostic 				 * We have to shuffle the chars a bit
29535209Sbostic 				 * to make this work.
29635209Sbostic 				 */
29735209Sbostic 				curr[0] = curr[-1];
29835209Sbostic 				curr[-1] = UE_CHAR;
29935209Sbostic 				column += ue_width;
30035209Sbostic 				if (ue_width > 0 && curr[0] == ' ')
30135209Sbostic 					/*
30235209Sbostic 					 * Another special case for magic
30335209Sbostic 					 * cookie terminals: if the next
30435209Sbostic 					 * char is a space, replace it
30535209Sbostic 					 * with the "exit underline" sequence.
30635209Sbostic 					 */
30735209Sbostic 					column--;
30835209Sbostic 				else
30935209Sbostic 					curr++;
31035209Sbostic 				ln_state = LN_NORMAL;
31135209Sbostic 			}
31235209Sbostic 			break;
31335209Sbostic 		case LN_BO_X:
31435209Sbostic 			if (c == '\b')
31535209Sbostic 				ln_state = LN_BO_XB;
31635209Sbostic 			else
31735209Sbostic 			{
31835209Sbostic 				/*
31935209Sbostic 				 * Exit boldface mode.
32035209Sbostic 				 * We have to shuffle the chars a bit
32135209Sbostic 				 * to make this work.
32235209Sbostic 				 */
32335209Sbostic 				curr[0] = curr[-1];
32435209Sbostic 				curr[-1] = BE_CHAR;
32535209Sbostic 				column += be_width;
32635209Sbostic 				if (be_width > 0 && curr[0] == ' ')
32735209Sbostic 					/*
32835209Sbostic 					 * Another special case for magic
32935209Sbostic 					 * cookie terminals: if the next
33035209Sbostic 					 * char is a space, replace it
33135209Sbostic 					 * with the "exit boldface" sequence.
33235209Sbostic 					 */
33335209Sbostic 					column--;
33435209Sbostic 				else
33535209Sbostic 					curr++;
33635209Sbostic 				ln_state = LN_NORMAL;
33735209Sbostic 			}
33835209Sbostic 			break;
33935209Sbostic 		}
34035209Sbostic 	}
34136251Sbostic 
34236251Sbostic 	if (c == '\t') {
34335209Sbostic 		/*
34435209Sbostic 		 * Expand a tab into spaces.
34535209Sbostic 		 */
34636251Sbostic 		do {
34736251Sbostic 			NEW_COLUMN(1);
34835209Sbostic 		} while ((column % tabstop) != 0);
34935209Sbostic 		*curr++ = '\t';
35035209Sbostic 		return (0);
35135209Sbostic 	}
35235209Sbostic 
35336251Sbostic 	if (c == '\b') {
35436251Sbostic 		if (ln_state == LN_NORMAL)
35536251Sbostic 			NEW_COLUMN(2);
35636251Sbostic 		else
35735209Sbostic 			column--;
35836251Sbostic 		*curr++ = ('H' | 0200);
35936251Sbostic 		return(0);
36035209Sbostic 	}
36135209Sbostic 
36236251Sbostic 	if (CONTROL_CHAR(c)) {
36335209Sbostic 		/*
36436251Sbostic 		 * Put a "^X" into the buffer.  The 0200 bit is used to tell
36536251Sbostic 		 * put_line() to prefix the char with a ^.  We don't actually
36636251Sbostic 		 * put the ^ in the buffer because we sometimes need to move
36736251Sbostic 		 * chars around, and such movement might separate the ^ from
36836251Sbostic 		 * its following character.
36935209Sbostic 		 */
37036251Sbostic 		NEW_COLUMN(2);
37136251Sbostic 		*curr++ = (CARAT_CHAR(c) | 0200);
37236251Sbostic 		return(0);
37335209Sbostic 	}
37435209Sbostic 
37535209Sbostic 	/*
37635209Sbostic 	 * Ordinary character.  Just put it in the buffer.
37735209Sbostic 	 */
37836251Sbostic 	NEW_COLUMN(1);
37935209Sbostic 	*curr++ = c;
38035209Sbostic 	return (0);
38135209Sbostic }
38235209Sbostic 
38335209Sbostic /*
38435209Sbostic  * Analogous to forw_line(), but deals with "raw lines":
38535209Sbostic  * lines which are not split for screen width.
38635209Sbostic  * {{ This is supposed to be more efficient than forw_line(). }}
38735209Sbostic  */
38836251Sbostic off_t
forw_raw_line(curr_pos)38935209Sbostic forw_raw_line(curr_pos)
39036251Sbostic 	off_t curr_pos;
39135209Sbostic {
39235209Sbostic 	register char *p;
39335209Sbostic 	register int c;
39436251Sbostic 	off_t new_pos, ch_tell();
39535209Sbostic 
39635209Sbostic 	if (curr_pos == NULL_POSITION || ch_seek(curr_pos) ||
39735209Sbostic 		(c = ch_forw_get()) == EOI)
39835209Sbostic 		return (NULL_POSITION);
39935209Sbostic 
40035209Sbostic 	p = linebuf;
40135209Sbostic 
40235209Sbostic 	for (;;)
40335209Sbostic 	{
40435209Sbostic 		if (c == '\n' || c == EOI)
40535209Sbostic 		{
40635209Sbostic 			new_pos = ch_tell();
40735209Sbostic 			break;
40835209Sbostic 		}
40935209Sbostic 		if (p >= &linebuf[sizeof(linebuf)-1])
41035209Sbostic 		{
41135209Sbostic 			/*
41235209Sbostic 			 * Overflowed the input buffer.
41335209Sbostic 			 * Pretend the line ended here.
41435209Sbostic 			 * {{ The line buffer is supposed to be big
41535209Sbostic 			 *    enough that this never happens. }}
41635209Sbostic 			 */
41735209Sbostic 			new_pos = ch_tell() - 1;
41835209Sbostic 			break;
41935209Sbostic 		}
42035209Sbostic 		*p++ = c;
42135209Sbostic 		c = ch_forw_get();
42235209Sbostic 	}
42335209Sbostic 	*p = '\0';
42435209Sbostic 	line = linebuf;
42535209Sbostic 	return (new_pos);
42635209Sbostic }
42735209Sbostic 
42835209Sbostic /*
42935209Sbostic  * Analogous to back_line(), but deals with "raw lines".
43035209Sbostic  * {{ This is supposed to be more efficient than back_line(). }}
43135209Sbostic  */
43236251Sbostic off_t
back_raw_line(curr_pos)43335209Sbostic back_raw_line(curr_pos)
43436251Sbostic 	off_t curr_pos;
43535209Sbostic {
43635209Sbostic 	register char *p;
43735209Sbostic 	register int c;
43836251Sbostic 	off_t new_pos, ch_tell();
43935209Sbostic 
44036251Sbostic 	if (curr_pos == NULL_POSITION || curr_pos <= (off_t)0 ||
44135209Sbostic 		ch_seek(curr_pos-1))
44235209Sbostic 		return (NULL_POSITION);
44335209Sbostic 
44435209Sbostic 	p = &linebuf[sizeof(linebuf)];
44535209Sbostic 	*--p = '\0';
44635209Sbostic 
44735209Sbostic 	for (;;)
44835209Sbostic 	{
44935209Sbostic 		c = ch_back_get();
45035209Sbostic 		if (c == '\n')
45135209Sbostic 		{
45235209Sbostic 			/*
45335209Sbostic 			 * This is the newline ending the previous line.
45435209Sbostic 			 * We have hit the beginning of the line.
45535209Sbostic 			 */
45635209Sbostic 			new_pos = ch_tell() + 1;
45735209Sbostic 			break;
45835209Sbostic 		}
45935209Sbostic 		if (c == EOI)
46035209Sbostic 		{
46135209Sbostic 			/*
46235209Sbostic 			 * We have hit the beginning of the file.
46335209Sbostic 			 * This must be the first line in the file.
46435209Sbostic 			 * This must, of course, be the beginning of the line.
46535209Sbostic 			 */
46636251Sbostic 			new_pos = (off_t)0;
46735209Sbostic 			break;
46835209Sbostic 		}
46935209Sbostic 		if (p <= linebuf)
47035209Sbostic 		{
47135209Sbostic 			/*
47235209Sbostic 			 * Overflowed the input buffer.
47335209Sbostic 			 * Pretend the line ended here.
47435209Sbostic 			 */
47535209Sbostic 			new_pos = ch_tell() + 1;
47635209Sbostic 			break;
47735209Sbostic 		}
47835209Sbostic 		*--p = c;
47935209Sbostic 	}
48035209Sbostic 	line = p;
48135209Sbostic 	return (new_pos);
48235209Sbostic }
483