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