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