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