xref: /freebsd/contrib/less/input.c (revision b3e7694832e81d7a904a10f525f8797b753bf0d3)
1 /*
2  * Copyright (C) 1984-2023  Mark Nudelman
3  *
4  * You may distribute under the terms of either the GNU General Public
5  * License or the Less License, as specified in the README file.
6  *
7  * For more information, see the README file.
8  */
9 
10 /*
11  * High level routines dealing with getting lines of input
12  * from the file being viewed.
13  *
14  * When we speak of "lines" here, we mean PRINTABLE lines;
15  * lines processed with respect to the screen width.
16  * We use the term "raw line" to refer to lines simply
17  * delimited by newlines; not processed with respect to screen width.
18  */
19 
20 #include "less.h"
21 
22 extern int squeeze;
23 extern int hshift;
24 extern int quit_if_one_screen;
25 extern int sigs;
26 extern int ignore_eoi;
27 extern int status_col;
28 extern int wordwrap;
29 extern POSITION start_attnpos;
30 extern POSITION end_attnpos;
31 #if HILITE_SEARCH
32 extern int hilite_search;
33 extern int size_linebuf;
34 extern int show_attn;
35 #endif
36 
37 /*
38  * Set the status column.
39  *  base  Position of first char in line.
40  *  disp  First visible char.
41  *        Different than base_pos if line is shifted.
42  *  edisp Last visible char.
43  *  eol   End of line. Normally the newline.
44  *        Different than edisp if line is chopped.
45  */
46 static void init_status_col(POSITION base_pos, POSITION disp_pos, POSITION edisp_pos, POSITION eol_pos)
47 {
48 	int hl_before = (chop_line() && disp_pos != NULL_POSITION) ?
49 	    is_hilited_attr(base_pos, disp_pos, TRUE, NULL) : 0;
50 	int hl_after = (chop_line()) ?
51 	    is_hilited_attr(edisp_pos, eol_pos, TRUE, NULL) : 0;
52 	int attr;
53 	char ch;
54 
55 	if (hl_before && hl_after)
56 	{
57 		attr = hl_after;
58 		ch = '=';
59 	} else if (hl_before)
60 	{
61 		attr = hl_before;
62 		ch = '<';
63 	} else if (hl_after)
64 	{
65 		attr = hl_after;
66 		ch = '>';
67 	} else
68 	{
69 		attr = is_hilited_attr(base_pos, eol_pos, TRUE, NULL);
70 		ch = '*';
71 	}
72 	if (attr)
73 		set_status_col(ch, attr);
74 }
75 
76 /*
77  * Get the next line.
78  * A "current" position is passed and a "new" position is returned.
79  * The current position is the position of the first character of
80  * a line.  The new position is the position of the first character
81  * of the NEXT line.  The line obtained is the line starting at curr_pos.
82  */
83 public POSITION forw_line_seg(POSITION curr_pos, int skipeol, int rscroll, int nochop)
84 {
85 	POSITION base_pos;
86 	POSITION new_pos;
87 	POSITION edisp_pos;
88 	int c;
89 	int blankline;
90 	int endline;
91 	int chopped;
92 	int backchars;
93 	POSITION wrap_pos;
94 	int skipped_leading;
95 
96 get_forw_line:
97 	if (curr_pos == NULL_POSITION)
98 	{
99 		null_line();
100 		return (NULL_POSITION);
101 	}
102 #if HILITE_SEARCH
103 	if (hilite_search == OPT_ONPLUS || is_filtering() || status_col)
104 	{
105 		/*
106 		 * If we are ignoring EOI (command F), only prepare
107 		 * one line ahead, to avoid getting stuck waiting for
108 		 * slow data without displaying the data we already have.
109 		 * If we're not ignoring EOI, we *could* do the same, but
110 		 * for efficiency we prepare several lines ahead at once.
111 		 */
112 		prep_hilite(curr_pos, curr_pos + 3*size_linebuf,
113 				ignore_eoi ? 1 : -1);
114 		curr_pos = next_unfiltered(curr_pos);
115 	}
116 #endif
117 	if (ch_seek(curr_pos))
118 	{
119 		null_line();
120 		return (NULL_POSITION);
121 	}
122 
123 	/*
124 	 * Step back to the beginning of the line.
125 	 */
126 	base_pos = curr_pos;
127 	for (;;)
128 	{
129 		if (ABORT_SIGS())
130 		{
131 			null_line();
132 			return (NULL_POSITION);
133 		}
134 		c = ch_back_get();
135 		if (c == EOI)
136 			break;
137 		if (c == '\n')
138 		{
139 			(void) ch_forw_get();
140 			break;
141 		}
142 		--base_pos;
143 	}
144 
145 	/*
146 	 * Read forward again to the position we should start at.
147 	 */
148 	prewind();
149 	plinestart(base_pos);
150 	(void) ch_seek(base_pos);
151 	new_pos = base_pos;
152 	while (new_pos < curr_pos)
153 	{
154 		if (ABORT_SIGS())
155 		{
156 			null_line();
157 			return (NULL_POSITION);
158 		}
159 		c = ch_forw_get();
160 		backchars = pappend(c, new_pos);
161 		new_pos++;
162 		if (backchars > 0)
163 		{
164 			pshift_all();
165 			if (wordwrap && (c == ' ' || c == '\t'))
166 			{
167 				do
168 				{
169 					new_pos++;
170 					c = ch_forw_get();
171 				} while (c == ' ' || c == '\t');
172 				backchars = 1;
173 			}
174 			new_pos -= backchars;
175 			while (--backchars >= 0)
176 				(void) ch_back_get();
177 		}
178 	}
179 	(void) pflushmbc();
180 	pshift_all();
181 
182 	/*
183 	 * Read the first character to display.
184 	 */
185 	c = ch_forw_get();
186 	if (c == EOI)
187 	{
188 		null_line();
189 		return (NULL_POSITION);
190 	}
191 	blankline = (c == '\n' || c == '\r');
192 	wrap_pos = NULL_POSITION;
193 	skipped_leading = FALSE;
194 
195 	/*
196 	 * Read each character in the line and append to the line buffer.
197 	 */
198 	chopped = FALSE;
199 	for (;;)
200 	{
201 		if (ABORT_SIGS())
202 		{
203 			null_line();
204 			return (NULL_POSITION);
205 		}
206 		if (c == '\n' || c == EOI)
207 		{
208 			/*
209 			 * End of the line.
210 			 */
211 			backchars = pflushmbc();
212 			new_pos = ch_tell();
213 			if (backchars > 0 && (nochop || !chop_line()) && hshift == 0)
214 			{
215 				new_pos -= backchars + 1;
216 				endline = FALSE;
217 			} else
218 				endline = TRUE;
219 			edisp_pos = new_pos;
220 			break;
221 		}
222 		if (c != '\r')
223 			blankline = 0;
224 
225 		/*
226 		 * Append the char to the line and get the next char.
227 		 */
228 		backchars = pappend(c, ch_tell()-1);
229 		if (backchars > 0)
230 		{
231 			/*
232 			 * The char won't fit in the line; the line
233 			 * is too long to print in the screen width.
234 			 * End the line here.
235 			 */
236 			if (skipeol)
237 			{
238 				/* Read to end of line. */
239 				edisp_pos = ch_tell();
240 				do
241 				{
242 					if (ABORT_SIGS())
243 					{
244 						null_line();
245 						return (NULL_POSITION);
246 					}
247 					c = ch_forw_get();
248 				} while (c != '\n' && c != EOI);
249 				new_pos = ch_tell();
250 				endline = TRUE;
251 				quit_if_one_screen = FALSE;
252 				chopped = TRUE;
253 			} else
254 			{
255 				if (!wordwrap)
256 					new_pos = ch_tell() - backchars;
257 				else
258 				{
259 					/*
260 					 * We're word-wrapping, so go back to the last space.
261 					 * However, if it's the space itself that couldn't fit,
262 					 * simply ignore it and any subsequent spaces.
263 					 */
264 					if (c == ' ' || c == '\t')
265 					{
266 						do
267 						{
268 							new_pos = ch_tell();
269 							c = ch_forw_get();
270 						} while (c == ' ' || c == '\t');
271 						if (c == '\r')
272 							c = ch_forw_get();
273 						if (c == '\n')
274 							new_pos = ch_tell();
275 					} else if (wrap_pos == NULL_POSITION)
276 						new_pos = ch_tell() - backchars;
277 					else
278 					{
279 						new_pos = wrap_pos;
280 						loadc();
281 					}
282 				}
283 				endline = FALSE;
284 			}
285 			break;
286 		}
287 		if (wordwrap)
288 		{
289 			if (c == ' ' || c == '\t')
290 			{
291 				if (skipped_leading)
292 				{
293 					wrap_pos = ch_tell();
294 					savec();
295 				}
296 			} else
297 				skipped_leading = TRUE;
298 		}
299 		c = ch_forw_get();
300 	}
301 
302 #if HILITE_SEARCH
303 	if (blankline && show_attn)
304 	{
305 		/* Add spurious space to carry possible attn hilite. */
306 		pappend(' ', ch_tell()-1);
307 	}
308 #endif
309 	pdone(endline, rscroll && chopped, 1);
310 
311 #if HILITE_SEARCH
312 	if (is_filtered(base_pos))
313 	{
314 		/*
315 		 * We don't want to display this line.
316 		 * Get the next line.
317 		 */
318 		curr_pos = new_pos;
319 		goto get_forw_line;
320 	}
321 	if (status_col)
322 		init_status_col(base_pos, line_position(), edisp_pos, new_pos);
323 #endif
324 
325 	if (squeeze && blankline)
326 	{
327 		/*
328 		 * This line is blank.
329 		 * Skip down to the last contiguous blank line
330 		 * and pretend it is the one which we are returning.
331 		 */
332 		while ((c = ch_forw_get()) == '\n' || c == '\r')
333 			if (ABORT_SIGS())
334 			{
335 				null_line();
336 				return (NULL_POSITION);
337 			}
338 		if (c != EOI)
339 			(void) ch_back_get();
340 		new_pos = ch_tell();
341 	}
342 
343 	return (new_pos);
344 }
345 
346 public POSITION forw_line(POSITION curr_pos)
347 {
348 
349 	return forw_line_seg(curr_pos, (chop_line() || hshift > 0), TRUE, FALSE);
350 }
351 
352 /*
353  * Get the previous line.
354  * A "current" position is passed and a "new" position is returned.
355  * The current position is the position of the first character of
356  * a line.  The new position is the position of the first character
357  * of the PREVIOUS line.  The line obtained is the one starting at new_pos.
358  */
359 public POSITION back_line(POSITION curr_pos)
360 {
361 	POSITION base_pos;
362 	POSITION new_pos;
363 	POSITION edisp_pos;
364 	POSITION begin_new_pos;
365 	int c;
366 	int endline;
367 	int chopped;
368 	int backchars;
369 	POSITION wrap_pos;
370 	int skipped_leading;
371 
372 get_back_line:
373 	if (curr_pos == NULL_POSITION || curr_pos <= ch_zero())
374 	{
375 		null_line();
376 		return (NULL_POSITION);
377 	}
378 #if HILITE_SEARCH
379 	if (hilite_search == OPT_ONPLUS || is_filtering() || status_col)
380 		prep_hilite((curr_pos < 3*size_linebuf) ?
381 				0 : curr_pos - 3*size_linebuf, curr_pos, -1);
382 #endif
383 	if (ch_seek(curr_pos-1))
384 	{
385 		null_line();
386 		return (NULL_POSITION);
387 	}
388 
389 	if (squeeze)
390 	{
391 		/*
392 		 * Find out if the "current" line was blank.
393 		 */
394 		(void) ch_forw_get();    /* Skip the newline */
395 		c = ch_forw_get();       /* First char of "current" line */
396 		(void) ch_back_get();    /* Restore our position */
397 		(void) ch_back_get();
398 
399 		if (c == '\n' || c == '\r')
400 		{
401 			/*
402 			 * The "current" line was blank.
403 			 * Skip over any preceding blank lines,
404 			 * since we skipped them in forw_line().
405 			 */
406 			while ((c = ch_back_get()) == '\n' || c == '\r')
407 				if (ABORT_SIGS())
408 				{
409 					null_line();
410 					return (NULL_POSITION);
411 				}
412 			if (c == EOI)
413 			{
414 				null_line();
415 				return (NULL_POSITION);
416 			}
417 			(void) ch_forw_get();
418 		}
419 	}
420 
421 	/*
422 	 * Scan backwards until we hit the beginning of the line.
423 	 */
424 	for (;;)
425 	{
426 		if (ABORT_SIGS())
427 		{
428 			null_line();
429 			return (NULL_POSITION);
430 		}
431 		c = ch_back_get();
432 		if (c == '\n')
433 		{
434 			/*
435 			 * This is the newline ending the previous line.
436 			 * We have hit the beginning of the line.
437 			 */
438 			base_pos = ch_tell() + 1;
439 			break;
440 		}
441 		if (c == EOI)
442 		{
443 			/*
444 			 * We have hit the beginning of the file.
445 			 * This must be the first line in the file.
446 			 * This must, of course, be the beginning of the line.
447 			 */
448 			base_pos = ch_tell();
449 			break;
450 		}
451 	}
452 
453 	/*
454 	 * Now scan forwards from the beginning of this line.
455 	 * We keep discarding "printable lines" (based on screen width)
456 	 * until we reach the curr_pos.
457 	 *
458 	 * {{ This algorithm is pretty inefficient if the lines
459 	 *    are much longer than the screen width,
460 	 *    but I don't know of any better way. }}
461 	 */
462 	new_pos = base_pos;
463 	if (ch_seek(new_pos))
464 	{
465 		null_line();
466 		return (NULL_POSITION);
467 	}
468 	endline = FALSE;
469 	prewind();
470 	plinestart(new_pos);
471     loop:
472 	wrap_pos = NULL_POSITION;
473 	skipped_leading = FALSE;
474 	begin_new_pos = new_pos;
475 	(void) ch_seek(new_pos);
476 	chopped = FALSE;
477 
478 	for (;;)
479 	{
480 		c = ch_forw_get();
481 		if (c == EOI || ABORT_SIGS())
482 		{
483 			null_line();
484 			return (NULL_POSITION);
485 		}
486 		new_pos++;
487 		if (c == '\n')
488 		{
489 			backchars = pflushmbc();
490 			if (backchars > 0 && !chop_line() && hshift == 0)
491 			{
492 				backchars++;
493 				goto shift;
494 			}
495 			endline = TRUE;
496 			edisp_pos = new_pos;
497 			break;
498 		}
499 		backchars = pappend(c, ch_tell()-1);
500 		if (backchars > 0)
501 		{
502 			/*
503 			 * Got a full printable line, but we haven't
504 			 * reached our curr_pos yet.  Discard the line
505 			 * and start a new one.
506 			 */
507 			if (chop_line() || hshift > 0)
508 			{
509 				endline = TRUE;
510 				chopped = TRUE;
511 				quit_if_one_screen = FALSE;
512 				edisp_pos = new_pos;
513 				break;
514 			}
515 		shift:
516 			if (!wordwrap)
517 			{
518 				pshift_all();
519 				new_pos -= backchars;
520 			} else
521 			{
522 				if (c == ' ' || c == '\t')
523 				{
524 					for (;;)
525 					{
526 						c = ch_forw_get();
527 						if (c == ' ' || c == '\t')
528 							new_pos++;
529 						else
530 						{
531 							if (c == '\r')
532 							{
533 								c = ch_forw_get();
534 								if (c == '\n')
535 									new_pos++;
536 							}
537 							if (c == '\n')
538 								new_pos++;
539 							break;
540 						}
541 					}
542 					if (new_pos >= curr_pos)
543 						break;
544 					pshift_all();
545 				} else
546 				{
547 					pshift_all();
548 					if (wrap_pos == NULL_POSITION)
549 						new_pos -= backchars;
550 					else
551 						new_pos = wrap_pos;
552 				}
553 			}
554 			goto loop;
555 		}
556 		if (wordwrap)
557 		{
558 			if (c == ' ' || c == '\t')
559 			{
560 				if (skipped_leading)
561 					wrap_pos = new_pos;
562 			} else
563 				skipped_leading = TRUE;
564 		}
565 		if (new_pos >= curr_pos)
566 		{
567 			edisp_pos = new_pos;
568 			break;
569 		}
570 	}
571 
572 	pdone(endline, chopped, 0);
573 
574 #if HILITE_SEARCH
575 	if (is_filtered(base_pos))
576 	{
577 		/*
578 		 * We don't want to display this line.
579 		 * Get the previous line.
580 		 */
581 		curr_pos = begin_new_pos;
582 		goto get_back_line;
583 	}
584 	if (status_col)
585 		init_status_col(base_pos, line_position(), edisp_pos, new_pos);
586 #endif
587 
588 	return (begin_new_pos);
589 }
590 
591 /*
592  * Set attnpos.
593  */
594 public void set_attnpos(POSITION pos)
595 {
596 	int c;
597 
598 	if (pos != NULL_POSITION)
599 	{
600 		if (ch_seek(pos))
601 			return;
602 		for (;;)
603 		{
604 			c = ch_forw_get();
605 			if (c == EOI)
606 				break;
607 			if (c == '\n' || c == '\r')
608 			{
609 				(void) ch_back_get();
610 				break;
611 			}
612 			pos++;
613 		}
614 		end_attnpos = pos;
615 		for (;;)
616 		{
617 			c = ch_back_get();
618 			if (c == EOI || c == '\n' || c == '\r')
619 				break;
620 			pos--;
621 		}
622 	}
623 	start_attnpos = pos;
624 }
625