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