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