xref: /freebsd/contrib/less/input.c (revision 058ac3e8063366dafa634d9107642e12b038bf09)
1 /*
2  * Copyright (C) 1984-2022  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 POSITION start_attnpos;
29 extern POSITION end_attnpos;
30 #if HILITE_SEARCH
31 extern int hilite_search;
32 extern int size_linebuf;
33 extern int show_attn;
34 #endif
35 
36 /*
37  * Get the next line.
38  * A "current" position is passed and a "new" position is returned.
39  * The current position is the position of the first character of
40  * a line.  The new position is the position of the first character
41  * of the NEXT line.  The line obtained is the line starting at curr_pos.
42  */
43 	public POSITION
44 forw_line_seg(curr_pos, skipeol, rscroll, nochop)
45 	POSITION curr_pos;
46 	int skipeol;
47 	int rscroll;
48 	int nochop;
49 {
50 	POSITION base_pos;
51 	POSITION new_pos;
52 	int c;
53 	int blankline;
54 	int endline;
55 	int chopped;
56 	int backchars;
57 
58 get_forw_line:
59 	if (curr_pos == NULL_POSITION)
60 	{
61 		null_line();
62 		return (NULL_POSITION);
63 	}
64 #if HILITE_SEARCH
65 	if (hilite_search == OPT_ONPLUS || is_filtering() || status_col)
66 	{
67 		/*
68 		 * If we are ignoring EOI (command F), only prepare
69 		 * one line ahead, to avoid getting stuck waiting for
70 		 * slow data without displaying the data we already have.
71 		 * If we're not ignoring EOI, we *could* do the same, but
72 		 * for efficiency we prepare several lines ahead at once.
73 		 */
74 		prep_hilite(curr_pos, curr_pos + 3*size_linebuf,
75 				ignore_eoi ? 1 : -1);
76 		curr_pos = next_unfiltered(curr_pos);
77 	}
78 #endif
79 	if (ch_seek(curr_pos))
80 	{
81 		null_line();
82 		return (NULL_POSITION);
83 	}
84 
85 	/*
86 	 * Step back to the beginning of the line.
87 	 */
88 	base_pos = curr_pos;
89 	for (;;)
90 	{
91 		if (ABORT_SIGS())
92 		{
93 			null_line();
94 			return (NULL_POSITION);
95 		}
96 		c = ch_back_get();
97 		if (c == EOI)
98 			break;
99 		if (c == '\n')
100 		{
101 			(void) ch_forw_get();
102 			break;
103 		}
104 		--base_pos;
105 	}
106 
107 	/*
108 	 * Read forward again to the position we should start at.
109 	 */
110 	prewind();
111 	plinestart(base_pos);
112 	(void) ch_seek(base_pos);
113 	new_pos = base_pos;
114 	while (new_pos < curr_pos)
115 	{
116 		if (ABORT_SIGS())
117 		{
118 			null_line();
119 			return (NULL_POSITION);
120 		}
121 		c = ch_forw_get();
122 		backchars = pappend(c, new_pos);
123 		new_pos++;
124 		if (backchars > 0)
125 		{
126 			pshift_all();
127 			new_pos -= backchars;
128 			while (--backchars >= 0)
129 				(void) ch_back_get();
130 		}
131 	}
132 	(void) pflushmbc();
133 	pshift_all();
134 
135 	/*
136 	 * Read the first character to display.
137 	 */
138 	c = ch_forw_get();
139 	if (c == EOI)
140 	{
141 		null_line();
142 		return (NULL_POSITION);
143 	}
144 	blankline = (c == '\n' || c == '\r');
145 
146 	/*
147 	 * Read each character in the line and append to the line buffer.
148 	 */
149 	chopped = FALSE;
150 	for (;;)
151 	{
152 		if (ABORT_SIGS())
153 		{
154 			null_line();
155 			return (NULL_POSITION);
156 		}
157 		if (c == '\n' || c == EOI)
158 		{
159 			/*
160 			 * End of the line.
161 			 */
162 			backchars = pflushmbc();
163 			new_pos = ch_tell();
164 			if (backchars > 0 && (nochop || !chop_line()) && hshift == 0)
165 			{
166 				new_pos -= backchars + 1;
167 				endline = FALSE;
168 			} else
169 				endline = TRUE;
170 			break;
171 		}
172 		if (c != '\r')
173 			blankline = 0;
174 
175 		/*
176 		 * Append the char to the line and get the next char.
177 		 */
178 		backchars = pappend(c, ch_tell()-1);
179 		if (backchars > 0)
180 		{
181 			/*
182 			 * The char won't fit in the line; the line
183 			 * is too long to print in the screen width.
184 			 * End the line here.
185 			 */
186 			if (skipeol)
187 			{
188 				/* Read to end of line. */
189 				do
190 				{
191 					if (ABORT_SIGS())
192 					{
193 						null_line();
194 						return (NULL_POSITION);
195 					}
196 					c = ch_forw_get();
197 				} while (c != '\n' && c != EOI);
198 				new_pos = ch_tell();
199 				endline = TRUE;
200 				quit_if_one_screen = FALSE;
201 				chopped = TRUE;
202 			} else
203 			{
204 				new_pos = ch_tell() - backchars;
205 				endline = FALSE;
206 			}
207 			break;
208 		}
209 		c = ch_forw_get();
210 	}
211 
212 #if HILITE_SEARCH
213 	if (blankline && show_attn)
214 	{
215 		/* Add spurious space to carry possible attn hilite. */
216 		pappend(' ', ch_tell()-1);
217 	}
218 #endif
219 	pdone(endline, rscroll && chopped, 1);
220 
221 #if HILITE_SEARCH
222 	if (is_filtered(base_pos))
223 	{
224 		/*
225 		 * We don't want to display this line.
226 		 * Get the next line.
227 		 */
228 		curr_pos = new_pos;
229 		goto get_forw_line;
230 	}
231 
232 	if (status_col)
233 	{
234 		int attr = is_hilited_attr(base_pos, ch_tell()-1, 1, NULL);
235 		if (attr)
236 			set_status_col('*', attr);
237 	}
238 #endif
239 
240 	if (squeeze && blankline)
241 	{
242 		/*
243 		 * This line is blank.
244 		 * Skip down to the last contiguous blank line
245 		 * and pretend it is the one which we are returning.
246 		 */
247 		while ((c = ch_forw_get()) == '\n' || c == '\r')
248 			if (ABORT_SIGS())
249 			{
250 				null_line();
251 				return (NULL_POSITION);
252 			}
253 		if (c != EOI)
254 			(void) ch_back_get();
255 		new_pos = ch_tell();
256 	}
257 
258 	return (new_pos);
259 }
260 
261 	public POSITION
262 forw_line(curr_pos)
263 	POSITION curr_pos;
264 {
265 
266 	return forw_line_seg(curr_pos, (chop_line() || hshift > 0), TRUE, FALSE);
267 }
268 
269 /*
270  * Get the previous line.
271  * A "current" position is passed and a "new" position is returned.
272  * The current position is the position of the first character of
273  * a line.  The new position is the position of the first character
274  * of the PREVIOUS line.  The line obtained is the one starting at new_pos.
275  */
276 	public POSITION
277 back_line(curr_pos)
278 	POSITION curr_pos;
279 {
280 	POSITION new_pos, begin_new_pos, base_pos;
281 	int c;
282 	int endline;
283 	int chopped;
284 	int backchars;
285 
286 get_back_line:
287 	if (curr_pos == NULL_POSITION || curr_pos <= ch_zero())
288 	{
289 		null_line();
290 		return (NULL_POSITION);
291 	}
292 #if HILITE_SEARCH
293 	if (hilite_search == OPT_ONPLUS || is_filtering() || status_col)
294 		prep_hilite((curr_pos < 3*size_linebuf) ?
295 				0 : curr_pos - 3*size_linebuf, curr_pos, -1);
296 #endif
297 	if (ch_seek(curr_pos-1))
298 	{
299 		null_line();
300 		return (NULL_POSITION);
301 	}
302 
303 	if (squeeze)
304 	{
305 		/*
306 		 * Find out if the "current" line was blank.
307 		 */
308 		(void) ch_forw_get();    /* Skip the newline */
309 		c = ch_forw_get();       /* First char of "current" line */
310 		(void) ch_back_get();    /* Restore our position */
311 		(void) ch_back_get();
312 
313 		if (c == '\n' || c == '\r')
314 		{
315 			/*
316 			 * The "current" line was blank.
317 			 * Skip over any preceding blank lines,
318 			 * since we skipped them in forw_line().
319 			 */
320 			while ((c = ch_back_get()) == '\n' || c == '\r')
321 				if (ABORT_SIGS())
322 				{
323 					null_line();
324 					return (NULL_POSITION);
325 				}
326 			if (c == EOI)
327 			{
328 				null_line();
329 				return (NULL_POSITION);
330 			}
331 			(void) ch_forw_get();
332 		}
333 	}
334 
335 	/*
336 	 * Scan backwards until we hit the beginning of the line.
337 	 */
338 	for (;;)
339 	{
340 		if (ABORT_SIGS())
341 		{
342 			null_line();
343 			return (NULL_POSITION);
344 		}
345 		c = ch_back_get();
346 		if (c == '\n')
347 		{
348 			/*
349 			 * This is the newline ending the previous line.
350 			 * We have hit the beginning of the line.
351 			 */
352 			base_pos = ch_tell() + 1;
353 			break;
354 		}
355 		if (c == EOI)
356 		{
357 			/*
358 			 * We have hit the beginning of the file.
359 			 * This must be the first line in the file.
360 			 * This must, of course, be the beginning of the line.
361 			 */
362 			base_pos = ch_tell();
363 			break;
364 		}
365 	}
366 
367 	/*
368 	 * Now scan forwards from the beginning of this line.
369 	 * We keep discarding "printable lines" (based on screen width)
370 	 * until we reach the curr_pos.
371 	 *
372 	 * {{ This algorithm is pretty inefficient if the lines
373 	 *    are much longer than the screen width,
374 	 *    but I don't know of any better way. }}
375 	 */
376 	new_pos = base_pos;
377 	if (ch_seek(new_pos))
378 	{
379 		null_line();
380 		return (NULL_POSITION);
381 	}
382 	endline = FALSE;
383 	prewind();
384 	plinestart(new_pos);
385     loop:
386 	begin_new_pos = new_pos;
387 	(void) ch_seek(new_pos);
388 	chopped = FALSE;
389 
390 	do
391 	{
392 		c = ch_forw_get();
393 		if (c == EOI || ABORT_SIGS())
394 		{
395 			null_line();
396 			return (NULL_POSITION);
397 		}
398 		new_pos++;
399 		if (c == '\n')
400 		{
401 			backchars = pflushmbc();
402 			if (backchars > 0 && !chop_line() && hshift == 0)
403 			{
404 				backchars++;
405 				goto shift;
406 			}
407 			endline = TRUE;
408 			break;
409 		}
410 		backchars = pappend(c, ch_tell()-1);
411 		if (backchars > 0)
412 		{
413 			/*
414 			 * Got a full printable line, but we haven't
415 			 * reached our curr_pos yet.  Discard the line
416 			 * and start a new one.
417 			 */
418 			if (chop_line() || hshift > 0)
419 			{
420 				endline = TRUE;
421 				chopped = TRUE;
422 				quit_if_one_screen = FALSE;
423 				break;
424 			}
425 		shift:
426 			pshift_all();
427 			while (backchars-- > 0)
428 			{
429 				(void) ch_back_get();
430 				new_pos--;
431 			}
432 			goto loop;
433 		}
434 	} while (new_pos < curr_pos);
435 
436 	pdone(endline, chopped, 0);
437 
438 #if HILITE_SEARCH
439 	if (is_filtered(base_pos))
440 	{
441 		/*
442 		 * We don't want to display this line.
443 		 * Get the previous line.
444 		 */
445 		curr_pos = begin_new_pos;
446 		goto get_back_line;
447 	}
448 
449 	if (status_col && curr_pos > 0)
450 	{
451 		int attr = is_hilited_attr(base_pos, curr_pos-1, 1, NULL);
452 		if (attr)
453 			set_status_col('*', attr);
454 	}
455 #endif
456 
457 	return (begin_new_pos);
458 }
459 
460 /*
461  * Set attnpos.
462  */
463 	public void
464 set_attnpos(pos)
465 	POSITION pos;
466 {
467 	int c;
468 
469 	if (pos != NULL_POSITION)
470 	{
471 		if (ch_seek(pos))
472 			return;
473 		for (;;)
474 		{
475 			c = ch_forw_get();
476 			if (c == EOI)
477 				break;
478 			if (c == '\n' || c == '\r')
479 			{
480 				(void) ch_back_get();
481 				break;
482 			}
483 			pos++;
484 		}
485 		end_attnpos = pos;
486 		for (;;)
487 		{
488 			c = ch_back_get();
489 			if (c == EOI || c == '\n' || c == '\r')
490 				break;
491 			pos--;
492 		}
493 	}
494 	start_attnpos = pos;
495 }
496