386BSD 0.1 development
[unix-history] / usr / othersrc / public / less-177 / line.c
CommitLineData
89edd2cf
WJ
1/*
2 * Routines to manipulate the "line buffer".
3 * The line buffer holds a line of output as it is being built
4 * in preparation for output to the screen.
5 */
6
7#include "less.h"
8
9static char linebuf[1024]; /* Buffer which holds the current output line */
10static char attr[1024]; /* Extension of linebuf to hold attributes */
11static int curr; /* Index into linebuf */
12static int column; /* Printable length, accounting for
13 backspaces, etc. */
14static int overstrike; /* Next char should overstrike previous char */
15static int is_null_line; /* There is no current line */
16static char pendc;
17
18static int do_append();
19
20extern int bs_mode;
21extern int tabstop;
22extern int linenums;
23extern int ctldisp;
24extern int twiddle;
25extern int binattr;
26extern int auto_wrap, ignaw;
27extern int bo_s_width, bo_e_width;
28extern int ul_s_width, ul_e_width;
29extern int bl_s_width, bl_e_width;
30extern int sc_width, sc_height;
31
32/*
33 * Rewind the line buffer.
34 */
35 public void
36prewind()
37{
38 curr = 0;
39 column = 0;
40 overstrike = 0;
41 is_null_line = 0;
42 pendc = '\0';
43}
44
45/*
46 * Insert the line number (of the given position) into the line buffer.
47 */
48 public void
49plinenum(pos)
50 POSITION pos;
51{
52 register int lno;
53 register int i;
54 register int n;
55
56 /*
57 * We display the line number at the start of each line
58 * only if the -N option is set.
59 */
60 if (linenums != 2)
61 return;
62
63 /*
64 * Get the line number and put it in the current line.
65 * {{ Note: since find_linenum calls forw_raw_line,
66 * it may seek in the input file, requiring the caller
67 * of plinenum to re-seek if necessary. }}
68 */
69 lno = find_linenum(pos);
70
71 sprintf(&linebuf[curr], "%6d", lno);
72 n = strlen(&linebuf[curr]);
73 column += n;
74 for (i = 0; i < n; i++)
75 attr[curr++] = 0;
76
77 /*
78 * Append enough spaces to bring us to the next tab stop.
79 * {{ We could avoid this at the cost of adding some
80 * complication to the tab stop logic in pappend(). }}
81 */
82 do
83 {
84 linebuf[curr] = ' ';
85 attr[curr++] = 0;
86 column++;
87 } while ((column % tabstop) != 0);
88}
89
90/*
91 * Return the printing width of the start (enter) sequence
92 * for a given character attribute.
93 */
94 int
95attr_swidth(a)
96 int a;
97{
98 switch (a)
99 {
100 case BOLD: return (bo_s_width);
101 case UNDERLINE: return (ul_s_width);
102 case BLINK: return (bl_s_width);
103 }
104 return (0);
105}
106
107/*
108 * Return the printing width of the end (exit) sequence
109 * for a given character attribute.
110 */
111 int
112attr_ewidth(a)
113 int a;
114{
115 switch (a)
116 {
117 case BOLD: return (bo_e_width);
118 case UNDERLINE: return (ul_e_width);
119 case BLINK: return (bl_e_width);
120 }
121 return (0);
122}
123
124/*
125 * Return the printing width of a given character and attribute,
126 * if the character were added to the current position in the line buffer.
127 * Adding a character with a given attribute may cause an enter or exit
128 * attribute sequence to be inserted, so this must be taken into account.
129 */
130 static int
131pwidth(c, a)
132 int c;
133 int a;
134{
135 register int w;
136
137 if (c == '\b')
138 /*
139 * Backspace moves backwards one position.
140 */
141 return (-1);
142
143 if (control_char(c))
144 /*
145 * Control characters do unpredicatable things,
146 * so we don't even try to guess; say it doesn't move.
147 * This can only happen if the -r flag is in effect.
148 */
149 return (0);
150
151 /*
152 * Other characters take one space,
153 * plus the width of any attribute enter/exit sequence.
154 */
155 w = 1;
156 if (curr > 0 && attr[curr-1] != a)
157 w += attr_ewidth(attr[curr-1]);
158 if (a && (curr == 0 || attr[curr-1] != a))
159 w += attr_swidth(a);
160 return (w);
161}
162
163/*
164 * Delete the previous character in the line buffer.
165 */
166 static void
167backc()
168{
169 curr--;
170 column -= pwidth(linebuf[curr], attr[curr]);
171}
172
173/*
174 * Append a character and attribute to the line buffer.
175 */
176 static int
177storec(c, a)
178 int c;
179 int a;
180{
181 register int w;
182
183 w = pwidth(c, a);
184 if (ctldisp > 0 && column + w + attr_ewidth(a) > sc_width)
185 /*
186 * Won't fit on screen.
187 */
188 return (1);
189
190 if (curr >= sizeof(linebuf)-2)
191 /*
192 * Won't fit in line buffer.
193 */
194 return (1);
195
196 /*
197 * Special handling for "magic cookie" terminals.
198 * If an attribute enter/exit sequence has a printing width > 0,
199 * and the sequence is adjacent to a space, delete the space.
200 * We just mark the space as invisible, to avoid having too
201 * many spaces deleted.
202 * {{ Note that even if the attribute width is > 1, we
203 * delete only one space. It's not worth trying to do more.
204 * It's hardly worth doing this much. }}
205 */
206 if (curr > 0 && a != NORMAL &&
207 linebuf[curr-1] == ' ' && attr[curr-1] == NORMAL &&
208 attr_swidth(a) > 0)
209 {
210 /*
211 * We are about to append an enter-attribute sequence
212 * just after a space. Delete the space.
213 */
214 attr[curr-1] = INVIS;
215 column--;
216 } else if (curr > 0 && attr[curr-1] != NORMAL &&
217 attr[curr-1] != INVIS && c == ' ' && a == NORMAL &&
218 attr_ewidth(attr[curr-1]) > 0)
219 {
220 /*
221 * We are about to append a space just after an
222 * exit-attribute sequence. Delete the space.
223 */
224 a = INVIS;
225 column--;
226 }
227 /* End of magic cookie handling. */
228
229 linebuf[curr] = c;
230 attr[curr] = a;
231 column += w;
232 return (0);
233}
234
235/*
236 * Append a character to the line buffer.
237 * Expand tabs into spaces, handle underlining, boldfacing, etc.
238 * Returns 0 if ok, 1 if couldn't fit in buffer.
239 */
240 public int
241pappend(c)
242 register int c;
243{
244 if (pendc)
245 {
246 if (do_append(pendc))
247 /*
248 * Oops. We've probably lost the char which
249 * was in pendc, since caller won't back up.
250 */
251 return (1);
252 pendc = '\0';
253 }
254
255 if (c == '\r' && bs_mode == BS_SPECIAL)
256 {
257 /*
258 * Don't put the CR into the buffer until we see
259 * the next char. If the next char is a newline,
260 * discard the CR.
261 */
262 pendc = c;
263 return (0);
264 }
265
266 return (do_append(c));
267}
268
269 static int
270do_append(c)
271 int c;
272{
273 register char *s;
274 register int a;
275
276#define STOREC(c,a) if (storec((c),(a))) return (1); else curr++
277
278 if (overstrike)
279 {
280 /*
281 * Overstrike the character at the current position
282 * in the line buffer. This will cause either
283 * underline (if a "_" is overstruck),
284 * bold (if an identical character is overstruck),
285 * or just deletion of the character in the buffer.
286 */
287 overstrike = 0;
288 if (c == linebuf[curr])
289 STOREC(linebuf[curr], BOLD);
290 else if (c == '_')
291 STOREC(linebuf[curr], UNDERLINE);
292 else if (linebuf[curr] == '_')
293 STOREC(c, UNDERLINE);
294 else if (control_char(c))
295 goto do_control_char;
296 else
297 STOREC(c, NORMAL);
298 } else if (c == '\b')
299 {
300 switch (bs_mode)
301 {
302 case BS_NORMAL:
303 STOREC(c, NORMAL);
304 break;
305 case BS_CONTROL:
306 goto do_control_char;
307 case BS_SPECIAL:
308 if (curr == 0)
309 break;
310 backc();
311 overstrike = 1;
312 break;
313 }
314 } else if (c == '\t')
315 {
316 /*
317 * Expand a tab into spaces.
318 */
319 do
320 {
321 STOREC(' ', NORMAL);
322 } while ((column % tabstop) != 0);
323 } else if (control_char(c))
324 {
325 do_control_char:
326 if (ctldisp == 0)
327 {
328 /*
329 * Output as a normal character.
330 */
331 STOREC(c, NORMAL);
332 } else
333 {
334 /*
335 * Output in the (blinking) ^X format.
336 */
337 s = prchar(c);
338 a = binattr;
339
340 /*
341 * Make sure we can get the entire representation
342 * the character on this line.
343 */
344 if (column + strlen(s) +
345 attr_swidth(a) + attr_ewidth(a) > sc_width)
346 return (1);
347
348 for ( ; *s != 0; s++)
349 STOREC(*s, a);
350 }
351 } else
352 {
353 STOREC(c, NORMAL);
354 }
355
356 return (0);
357}
358
359/*
360 * Terminate the line in the line buffer.
361 */
362 public void
363pdone(endline)
364 int endline;
365{
366 if (pendc && (pendc != '\r' || !endline))
367 /*
368 * If we had a pending character, put it in the buffer.
369 * But discard a pending CR if we are at end of line
370 * (that is, discard the CR in a CR/LF sequence).
371 */
372 (void) do_append(pendc);
373
374 /*
375 * Add a newline if necessary,
376 * and append a '\0' to the end of the line.
377 */
378 if (column < sc_width || !auto_wrap || ignaw || ctldisp == 0)
379 {
380 linebuf[curr] = '\n';
381 attr[curr] = NORMAL;
382 curr++;
383 }
384 linebuf[curr] = '\0';
385 attr[curr] = NORMAL;
386}
387
388/*
389 * Get a character from the current line.
390 * Return the character as the function return value,
391 * and the character attribute in *ap.
392 */
393 public int
394gline(i, ap)
395 register int i;
396 register int *ap;
397{
398 if (is_null_line)
399 {
400 /*
401 * If there is no current line, we pretend the line is
402 * either "~" or "", depending on the "twiddle" flag.
403 */
404 *ap = NORMAL;
405 if (twiddle)
406 return ("~\n"[i]);
407 return ("\n"[i]);
408 }
409
410 *ap = attr[i];
411 return (linebuf[i] & 0377);
412}
413
414/*
415 * Indicate that there is no current line.
416 */
417 public void
418null_line()
419{
420 is_null_line = 1;
421}
422
423/*
424 * Analogous to forw_line(), but deals with "raw lines":
425 * lines which are not split for screen width.
426 * {{ This is supposed to be more efficient than forw_line(). }}
427 */
428 public POSITION
429forw_raw_line(curr_pos, linep)
430 POSITION curr_pos;
431 char **linep;
432{
433 register char *p;
434 register int c;
435 POSITION new_pos;
436
437 if (curr_pos == NULL_POSITION || ch_seek(curr_pos) ||
438 (c = ch_forw_get()) == EOI)
439 return (NULL_POSITION);
440
441 p = linebuf;
442
443 for (;;)
444 {
445 if (c == '\n' || c == EOI)
446 {
447 new_pos = ch_tell();
448 break;
449 }
450 if (p >= &linebuf[sizeof(linebuf)-1])
451 {
452 /*
453 * Overflowed the input buffer.
454 * Pretend the line ended here.
455 * {{ The line buffer is supposed to be big
456 * enough that this never happens. }}
457 */
458 new_pos = ch_tell() - 1;
459 break;
460 }
461 *p++ = c;
462 c = ch_forw_get();
463 }
464 *p = '\0';
465 if (linep != NULL)
466 *linep = linebuf;
467 return (new_pos);
468}
469
470/*
471 * Analogous to back_line(), but deals with "raw lines".
472 * {{ This is supposed to be more efficient than back_line(). }}
473 */
474 public POSITION
475back_raw_line(curr_pos, linep)
476 POSITION curr_pos;
477 char **linep;
478{
479 register char *p;
480 register int c;
481 POSITION new_pos;
482
483 if (curr_pos == NULL_POSITION || curr_pos <= ch_zero() ||
484 ch_seek(curr_pos-1))
485 return (NULL_POSITION);
486
487 p = &linebuf[sizeof(linebuf)];
488 *--p = '\0';
489
490 for (;;)
491 {
492 c = ch_back_get();
493 if (c == '\n')
494 {
495 /*
496 * This is the newline ending the previous line.
497 * We have hit the beginning of the line.
498 */
499 new_pos = ch_tell() + 1;
500 break;
501 }
502 if (c == EOI)
503 {
504 /*
505 * We have hit the beginning of the file.
506 * This must be the first line in the file.
507 * This must, of course, be the beginning of the line.
508 */
509 new_pos = ch_zero();
510 break;
511 }
512 if (p <= linebuf)
513 {
514 /*
515 * Overflowed the input buffer.
516 * Pretend the line ended here.
517 */
518 new_pos = ch_tell() + 1;
519 break;
520 }
521 *--p = c;
522 }
523 if (linep != NULL)
524 *linep = p;
525 return (new_pos);
526}