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