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