Commit | Line | Data |
---|---|---|
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 | ||
9 | static char linebuf[1024]; /* Buffer which holds the current output line */ | |
10 | static char attr[1024]; /* Extension of linebuf to hold attributes */ | |
11 | static int curr; /* Index into linebuf */ | |
12 | static int column; /* Printable length, accounting for | |
13 | backspaces, etc. */ | |
14 | static int overstrike; /* Next char should overstrike previous char */ | |
15 | static int is_null_line; /* There is no current line */ | |
16 | static char pendc; | |
17 | ||
18 | static int do_append(); | |
19 | ||
20 | extern int bs_mode; | |
21 | extern int tabstop; | |
22 | extern int linenums; | |
23 | extern int ctldisp; | |
24 | extern int twiddle; | |
25 | extern int binattr; | |
26 | extern int auto_wrap, ignaw; | |
27 | extern int bo_s_width, bo_e_width; | |
28 | extern int ul_s_width, ul_e_width; | |
29 | extern int bl_s_width, bl_e_width; | |
30 | extern int sc_width, sc_height; | |
31 | ||
32 | /* | |
33 | * Rewind the line buffer. | |
34 | */ | |
35 | public void | |
36 | prewind() | |
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 | |
49 | plinenum(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 | |
95 | attr_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 | |
112 | attr_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 | |
131 | pwidth(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 | |
167 | backc() | |
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 | |
177 | storec(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 | |
241 | pappend(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 | |
270 | do_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 | |
363 | pdone(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 | |
394 | gline(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 | |
418 | null_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 | |
429 | forw_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 | |
475 | back_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 | } |