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