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
22 extern int squeeze;
23 extern int hshift;
24 extern int quit_if_one_screen;
25 extern int sigs;
26 extern int ignore_eoi;
27 extern int status_col;
28 extern int wordwrap;
29 extern POSITION start_attnpos;
30 extern POSITION end_attnpos;
31 #if HILITE_SEARCH
32 extern int hilite_search;
33 extern int size_linebuf;
34 extern 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 */
init_status_col(POSITION base_pos,POSITION disp_pos,POSITION edisp_pos,POSITION eol_pos)46 static 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 */
forw_line_seg(POSITION curr_pos,int skipeol,int rscroll,int nochop)83 public 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
96 get_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
forw_line(POSITION curr_pos)346 public 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 */
back_line(POSITION curr_pos)359 public 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
372 get_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 */
set_attnpos(POSITION pos)594 public 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