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