1 /* $NetBSD: input.c,v 1.4 2023/10/06 05:49:49 simonb Exp $ */
2
3 /*
4 * Copyright (C) 1984-2023 Mark Nudelman
5 *
6 * You may distribute under the terms of either the GNU General Public
7 * License or the Less License, as specified in the README file.
8 *
9 * For more information, see the README file.
10 */
11
12 /*
13 * High level routines dealing with getting lines of input
14 * from the file being viewed.
15 *
16 * When we speak of "lines" here, we mean PRINTABLE lines;
17 * lines processed with respect to the screen width.
18 * We use the term "raw line" to refer to lines simply
19 * delimited by newlines; not processed with respect to screen width.
20 */
21
22 #include "less.h"
23
24 extern int squeeze;
25 extern int hshift;
26 extern int quit_if_one_screen;
27 extern int sigs;
28 extern int ignore_eoi;
29 extern int status_col;
30 extern int wordwrap;
31 extern POSITION start_attnpos;
32 extern POSITION end_attnpos;
33 #if HILITE_SEARCH
34 extern int hilite_search;
35 extern int size_linebuf;
36 extern int show_attn;
37 #endif
38
39 /*
40 * Set the status column.
41 * base Position of first char in line.
42 * disp First visible char.
43 * Different than base_pos if line is shifted.
44 * edisp Last visible char.
45 * eol End of line. Normally the newline.
46 * Different than edisp if line is chopped.
47 */
init_status_col(POSITION base_pos,POSITION disp_pos,POSITION edisp_pos,POSITION eol_pos)48 static void init_status_col(POSITION base_pos, POSITION disp_pos, POSITION edisp_pos, POSITION eol_pos)
49 {
50 int hl_before = (chop_line() && disp_pos != NULL_POSITION) ?
51 is_hilited_attr(base_pos, disp_pos, TRUE, NULL) : 0;
52 int hl_after = (chop_line()) ?
53 is_hilited_attr(edisp_pos, eol_pos, TRUE, NULL) : 0;
54 int attr;
55 char ch;
56
57 if (hl_before && hl_after)
58 {
59 attr = hl_after;
60 ch = '=';
61 } else if (hl_before)
62 {
63 attr = hl_before;
64 ch = '<';
65 } else if (hl_after)
66 {
67 attr = hl_after;
68 ch = '>';
69 } else
70 {
71 attr = is_hilited_attr(base_pos, eol_pos, TRUE, NULL);
72 ch = '*';
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,int skipeol,int rscroll,int nochop)85 public POSITION forw_line_seg(POSITION curr_pos, int skipeol, int rscroll, int nochop)
86 {
87 POSITION base_pos;
88 POSITION new_pos;
89 POSITION edisp_pos;
90 int c;
91 int blankline;
92 int endline;
93 int chopped;
94 int backchars;
95 POSITION wrap_pos;
96 int 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 + 3*size_linebuf,
115 ignore_eoi ? 1 : -1);
116 curr_pos = next_unfiltered(curr_pos);
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 if (ABORT_SIGS())
132 {
133 null_line();
134 return (NULL_POSITION);
135 }
136 c = ch_back_get();
137 if (c == EOI)
138 break;
139 if (c == '\n')
140 {
141 (void) ch_forw_get();
142 break;
143 }
144 --base_pos;
145 }
146
147 /*
148 * Read forward again to the position we should start at.
149 */
150 prewind();
151 plinestart(base_pos);
152 (void) ch_seek(base_pos);
153 new_pos = base_pos;
154 while (new_pos < curr_pos)
155 {
156 if (ABORT_SIGS())
157 {
158 null_line();
159 return (NULL_POSITION);
160 }
161 c = ch_forw_get();
162 backchars = pappend(c, new_pos);
163 new_pos++;
164 if (backchars > 0)
165 {
166 pshift_all();
167 if (wordwrap && (c == ' ' || c == '\t'))
168 {
169 do
170 {
171 new_pos++;
172 c = ch_forw_get();
173 } while (c == ' ' || c == '\t');
174 backchars = 1;
175 }
176 new_pos -= backchars;
177 while (--backchars >= 0)
178 (void) ch_back_get();
179 }
180 }
181 (void) pflushmbc();
182 pshift_all();
183
184 /*
185 * Read the first character to display.
186 */
187 c = ch_forw_get();
188 if (c == EOI)
189 {
190 null_line();
191 return (NULL_POSITION);
192 }
193 blankline = (c == '\n' || c == '\r');
194 wrap_pos = NULL_POSITION;
195 skipped_leading = FALSE;
196
197 /*
198 * Read each character in the line and append to the line buffer.
199 */
200 chopped = FALSE;
201 for (;;)
202 {
203 if (ABORT_SIGS())
204 {
205 null_line();
206 return (NULL_POSITION);
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 = 0;
226
227 /*
228 * Append the char to the line and get the next char.
229 */
230 backchars = pappend(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();
242 do
243 {
244 if (ABORT_SIGS())
245 {
246 null_line();
247 return (NULL_POSITION);
248 }
249 c = ch_forw_get();
250 } while (c != '\n' && c != EOI);
251 new_pos = ch_tell();
252 endline = TRUE;
253 quit_if_one_screen = FALSE;
254 chopped = TRUE;
255 } else
256 {
257 if (!wordwrap)
258 new_pos = ch_tell() - backchars;
259 else
260 {
261 /*
262 * We're word-wrapping, so go back to the last space.
263 * However, if it's the space itself that couldn't fit,
264 * simply ignore it and any subsequent spaces.
265 */
266 if (c == ' ' || c == '\t')
267 {
268 do
269 {
270 new_pos = ch_tell();
271 c = ch_forw_get();
272 } while (c == ' ' || c == '\t');
273 if (c == '\r')
274 c = ch_forw_get();
275 if (c == '\n')
276 new_pos = ch_tell();
277 } else if (wrap_pos == NULL_POSITION)
278 new_pos = ch_tell() - backchars;
279 else
280 {
281 new_pos = wrap_pos;
282 loadc();
283 }
284 }
285 endline = FALSE;
286 }
287 break;
288 }
289 if (wordwrap)
290 {
291 if (c == ' ' || c == '\t')
292 {
293 if (skipped_leading)
294 {
295 wrap_pos = ch_tell();
296 savec();
297 }
298 } else
299 skipped_leading = TRUE;
300 }
301 c = ch_forw_get();
302 }
303
304 #if HILITE_SEARCH
305 if (blankline && show_attn)
306 {
307 /* Add spurious space to carry possible attn hilite. */
308 pappend(' ', ch_tell()-1);
309 }
310 #endif
311 pdone(endline, rscroll && chopped, 1);
312
313 #if HILITE_SEARCH
314 if (is_filtered(base_pos))
315 {
316 /*
317 * We don't want to display this line.
318 * Get the next line.
319 */
320 curr_pos = new_pos;
321 goto get_forw_line;
322 }
323 if (status_col)
324 init_status_col(base_pos, line_position(), edisp_pos, new_pos);
325 #endif
326
327 if (squeeze && blankline)
328 {
329 /*
330 * This line is blank.
331 * Skip down to the last contiguous blank line
332 * and pretend it is the one which we are returning.
333 */
334 while ((c = ch_forw_get()) == '\n' || c == '\r')
335 if (ABORT_SIGS())
336 {
337 null_line();
338 return (NULL_POSITION);
339 }
340 if (c != EOI)
341 (void) ch_back_get();
342 new_pos = ch_tell();
343 }
344
345 return (new_pos);
346 }
347
forw_line(POSITION curr_pos)348 public POSITION forw_line(POSITION curr_pos)
349 {
350
351 return forw_line_seg(curr_pos, (chop_line() || hshift > 0), TRUE, FALSE);
352 }
353
354 /*
355 * Get the previous line.
356 * A "current" position is passed and a "new" position is returned.
357 * The current position is the position of the first character of
358 * a line. The new position is the position of the first character
359 * of the PREVIOUS line. The line obtained is the one starting at new_pos.
360 */
back_line(POSITION curr_pos)361 public POSITION back_line(POSITION curr_pos)
362 {
363 POSITION base_pos;
364 POSITION new_pos;
365 POSITION edisp_pos;
366 POSITION begin_new_pos;
367 int c;
368 int endline;
369 int chopped;
370 int backchars;
371 POSITION wrap_pos;
372 int skipped_leading;
373
374 get_back_line:
375 if (curr_pos == NULL_POSITION || curr_pos <= ch_zero())
376 {
377 null_line();
378 return (NULL_POSITION);
379 }
380 #if HILITE_SEARCH
381 if (hilite_search == OPT_ONPLUS || is_filtering() || status_col)
382 prep_hilite((curr_pos < 3*size_linebuf) ?
383 0 : curr_pos - 3*size_linebuf, curr_pos, -1);
384 #endif
385 if (ch_seek(curr_pos-1))
386 {
387 null_line();
388 return (NULL_POSITION);
389 }
390
391 if (squeeze)
392 {
393 /*
394 * Find out if the "current" line was blank.
395 */
396 (void) ch_forw_get(); /* Skip the newline */
397 c = ch_forw_get(); /* First char of "current" line */
398 (void) ch_back_get(); /* Restore our position */
399 (void) ch_back_get();
400
401 if (c == '\n' || c == '\r')
402 {
403 /*
404 * The "current" line was blank.
405 * Skip over any preceding blank lines,
406 * since we skipped them in forw_line().
407 */
408 while ((c = ch_back_get()) == '\n' || c == '\r')
409 if (ABORT_SIGS())
410 {
411 null_line();
412 return (NULL_POSITION);
413 }
414 if (c == EOI)
415 {
416 null_line();
417 return (NULL_POSITION);
418 }
419 (void) ch_forw_get();
420 }
421 }
422
423 /*
424 * Scan backwards until we hit the beginning of the line.
425 */
426 for (;;)
427 {
428 if (ABORT_SIGS())
429 {
430 null_line();
431 return (NULL_POSITION);
432 }
433 c = ch_back_get();
434 if (c == '\n')
435 {
436 /*
437 * This is the newline ending the previous line.
438 * We have hit the beginning of the line.
439 */
440 base_pos = ch_tell() + 1;
441 break;
442 }
443 if (c == EOI)
444 {
445 /*
446 * We have hit the beginning of the file.
447 * This must be the first line in the file.
448 * This must, of course, be the beginning of the line.
449 */
450 base_pos = ch_tell();
451 break;
452 }
453 }
454
455 /*
456 * Now scan forwards from the beginning of this line.
457 * We keep discarding "printable lines" (based on screen width)
458 * until we reach the curr_pos.
459 *
460 * {{ This algorithm is pretty inefficient if the lines
461 * are much longer than the screen width,
462 * but I don't know of any better way. }}
463 */
464 new_pos = base_pos;
465 if (ch_seek(new_pos))
466 {
467 null_line();
468 return (NULL_POSITION);
469 }
470 endline = FALSE;
471 prewind();
472 plinestart(new_pos);
473 loop:
474 wrap_pos = NULL_POSITION;
475 skipped_leading = FALSE;
476 begin_new_pos = new_pos;
477 (void) ch_seek(new_pos);
478 chopped = FALSE;
479
480 for (;;)
481 {
482 c = ch_forw_get();
483 if (c == EOI || ABORT_SIGS())
484 {
485 null_line();
486 return (NULL_POSITION);
487 }
488 new_pos++;
489 if (c == '\n')
490 {
491 backchars = pflushmbc();
492 if (backchars > 0 && !chop_line() && hshift == 0)
493 {
494 backchars++;
495 goto shift;
496 }
497 endline = TRUE;
498 edisp_pos = new_pos;
499 break;
500 }
501 backchars = pappend(c, ch_tell()-1);
502 if (backchars > 0)
503 {
504 /*
505 * Got a full printable line, but we haven't
506 * reached our curr_pos yet. Discard the line
507 * and start a new one.
508 */
509 if (chop_line() || hshift > 0)
510 {
511 endline = TRUE;
512 chopped = TRUE;
513 quit_if_one_screen = FALSE;
514 edisp_pos = new_pos;
515 break;
516 }
517 shift:
518 if (!wordwrap)
519 {
520 pshift_all();
521 new_pos -= backchars;
522 } else
523 {
524 if (c == ' ' || c == '\t')
525 {
526 for (;;)
527 {
528 c = ch_forw_get();
529 if (c == ' ' || c == '\t')
530 new_pos++;
531 else
532 {
533 if (c == '\r')
534 {
535 c = ch_forw_get();
536 if (c == '\n')
537 new_pos++;
538 }
539 if (c == '\n')
540 new_pos++;
541 break;
542 }
543 }
544 if (new_pos >= curr_pos)
545 break;
546 pshift_all();
547 } else
548 {
549 pshift_all();
550 if (wrap_pos == NULL_POSITION)
551 new_pos -= backchars;
552 else
553 new_pos = wrap_pos;
554 }
555 }
556 goto loop;
557 }
558 if (wordwrap)
559 {
560 if (c == ' ' || c == '\t')
561 {
562 if (skipped_leading)
563 wrap_pos = new_pos;
564 } else
565 skipped_leading = TRUE;
566 }
567 if (new_pos >= curr_pos)
568 {
569 edisp_pos = new_pos;
570 break;
571 }
572 }
573
574 pdone(endline, chopped, 0);
575
576 #if HILITE_SEARCH
577 if (is_filtered(base_pos))
578 {
579 /*
580 * We don't want to display this line.
581 * Get the previous line.
582 */
583 curr_pos = begin_new_pos;
584 goto get_back_line;
585 }
586 if (status_col)
587 init_status_col(base_pos, line_position(), edisp_pos, new_pos);
588 #endif
589
590 return (begin_new_pos);
591 }
592
593 /*
594 * Set attnpos.
595 */
set_attnpos(POSITION pos)596 public void set_attnpos(POSITION pos)
597 {
598 int c;
599
600 if (pos != NULL_POSITION)
601 {
602 if (ch_seek(pos))
603 return;
604 for (;;)
605 {
606 c = ch_forw_get();
607 if (c == EOI)
608 break;
609 if (c == '\n' || c == '\r')
610 {
611 (void) ch_back_get();
612 break;
613 }
614 pos++;
615 }
616 end_attnpos = pos;
617 for (;;)
618 {
619 c = ch_back_get();
620 if (c == EOI || c == '\n' || c == '\r')
621 break;
622 pos--;
623 }
624 }
625 start_attnpos = pos;
626 }
627