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