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