Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/less/input.c
39476 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 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
*/
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
*/
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
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
*/
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
*/
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
613