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
22extern int squeeze;
23extern int chopline;
24extern int hshift;
25extern int quit_if_one_screen;
26extern int sigs;
27extern int ignore_eoi;
28extern int status_col;
29extern POSITION start_attnpos;
30extern POSITION end_attnpos;
31#if HILITE_SEARCH
32extern int hilite_search;
33extern int size_linebuf;
34extern 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
45forw_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
57get_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
261forw_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
275back_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
284get_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
462set_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