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