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