Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/less/linenum.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
/*
12
* Code to handle displaying line numbers.
13
*
14
* Finding the line number of a given file position is rather tricky.
15
* We don't want to just start at the beginning of the file and
16
* count newlines, because that is slow for large files (and also
17
* wouldn't work if we couldn't get to the start of the file; e.g.
18
* if input is a long pipe).
19
*
20
* So we use the function add_lnum to cache line numbers.
21
* We try to be very clever and keep only the more interesting
22
* line numbers when we run out of space in our table. A line
23
* number is more interesting than another when it is far from
24
* other line numbers. For example, we'd rather keep lines
25
* 100,200,300 than 100,101,300. 200 is more interesting than
26
* 101 because 101 can be derived very cheaply from 100, while
27
* 200 is more expensive to derive from 100.
28
*
29
* The function currline() returns the line number of a given
30
* position in the file. As a side effect, it calls add_lnum
31
* to cache the line number. Therefore currline is occasionally
32
* called to make sure we cache line numbers often enough.
33
*/
34
35
#include "less.h"
36
37
/*
38
* Structure to keep track of a line number and the associated file position.
39
* A doubly-linked circular list of line numbers is kept ordered by line number.
40
*/
41
struct linenum_info
42
{
43
struct linenum_info *next; /* Link to next in the list */
44
struct linenum_info *prev; /* Line to previous in the list */
45
POSITION pos; /* File position */
46
POSITION gap; /* Gap between prev and next */
47
LINENUM line; /* Line number */
48
};
49
/*
50
* "gap" needs some explanation: the gap of any particular line number
51
* is the distance between the previous one and the next one in the list.
52
* ("Distance" means difference in file position.) In other words, the
53
* gap of a line number is the gap which would be introduced if this
54
* line number were deleted. It is used to decide which one to replace
55
* when we have a new one to insert and the table is full.
56
*/
57
58
#define LONGTIME (2) /* In seconds */
59
60
static struct linenum_info anchor; /* Anchor of the list */
61
static struct linenum_info *freelist; /* Anchor of the unused entries */
62
static struct linenum_info pool[LINENUM_POOL]; /* The pool itself */
63
static struct linenum_info *spare; /* We always keep one spare entry */
64
public lbool scanning_eof = FALSE;
65
66
extern int linenums;
67
extern int sigs;
68
extern int sc_height;
69
extern int header_lines;
70
extern int nonum_headers;
71
extern POSITION header_start_pos;
72
73
/*
74
* Initialize the line number structures.
75
*/
76
public void clr_linenum(void)
77
{
78
struct linenum_info *p;
79
80
/*
81
* Put all the entries on the free list.
82
* Leave one for the "spare".
83
*/
84
for (p = pool; p < &pool[LINENUM_POOL-2]; p++)
85
p->next = p+1;
86
pool[LINENUM_POOL-2].next = NULL;
87
freelist = pool;
88
89
spare = &pool[LINENUM_POOL-1];
90
91
/*
92
* Initialize the anchor.
93
*/
94
anchor.next = anchor.prev = &anchor;
95
anchor.gap = 0;
96
anchor.pos = (POSITION)0;
97
anchor.line = 1;
98
}
99
100
/*
101
* Calculate the gap for an entry.
102
*/
103
static void calcgap(struct linenum_info *p)
104
{
105
/*
106
* Don't bother to compute a gap for the anchor.
107
* Also don't compute a gap for the last one in the list.
108
* The gap for that last one should be considered infinite,
109
* but we never look at it anyway.
110
*/
111
if (p == &anchor || p->next == &anchor)
112
return;
113
p->gap = p->next->pos - p->prev->pos;
114
}
115
116
/*
117
* Add a new line number to the cache.
118
* The specified position (pos) should be the file position of the
119
* FIRST character in the specified line.
120
*/
121
public void add_lnum(LINENUM linenum, POSITION pos)
122
{
123
struct linenum_info *p;
124
struct linenum_info *new;
125
struct linenum_info *nextp;
126
struct linenum_info *prevp;
127
POSITION mingap;
128
129
/*
130
* Find the proper place in the list for the new one.
131
* The entries are sorted by position.
132
*/
133
for (p = anchor.next; p != &anchor && p->pos < pos; p = p->next)
134
if (p->line == linenum)
135
/* We already have this one. */
136
return;
137
nextp = p;
138
prevp = p->prev;
139
140
if (freelist != NULL)
141
{
142
/*
143
* We still have free (unused) entries.
144
* Use one of them.
145
*/
146
new = freelist;
147
freelist = freelist->next;
148
} else
149
{
150
/*
151
* No free entries.
152
* Use the "spare" entry.
153
*/
154
new = spare;
155
spare = NULL;
156
}
157
158
/*
159
* Fill in the fields of the new entry,
160
* and insert it into the proper place in the list.
161
*/
162
new->next = nextp;
163
new->prev = prevp;
164
new->pos = pos;
165
new->line = linenum;
166
167
nextp->prev = new;
168
prevp->next = new;
169
170
/*
171
* Recalculate gaps for the new entry and the neighboring entries.
172
*/
173
calcgap(new);
174
calcgap(nextp);
175
calcgap(prevp);
176
177
if (spare == NULL)
178
{
179
/*
180
* We have used the spare entry.
181
* Scan the list to find the one with the smallest
182
* gap, take it out and make it the spare.
183
* We should never remove the last one, so stop when
184
* we get to p->next == &anchor. This also avoids
185
* looking at the gap of the last one, which is
186
* not computed by calcgap.
187
*/
188
mingap = anchor.next->gap;
189
for (p = anchor.next; p->next != &anchor; p = p->next)
190
{
191
if (p->gap <= mingap)
192
{
193
spare = p;
194
mingap = p->gap;
195
}
196
}
197
spare->next->prev = spare->prev;
198
spare->prev->next = spare->next;
199
}
200
}
201
202
/*
203
* If we get stuck in a long loop trying to figure out the
204
* line number, print a message to tell the user what we're doing.
205
*/
206
static void longloopmessage(void)
207
{
208
ierror("Calculating line numbers", NULL_PARG);
209
}
210
211
struct delayed_msg
212
{
213
void (*message)(void);
214
int loopcount;
215
#if HAVE_TIME
216
time_type startime;
217
#endif
218
};
219
220
static void start_delayed_msg(struct delayed_msg *dmsg, void (*message)(void))
221
{
222
dmsg->loopcount = 0;
223
dmsg->message = message;
224
#if HAVE_TIME
225
dmsg->startime = get_time();
226
#endif
227
}
228
229
static void delayed_msg(struct delayed_msg *dmsg)
230
{
231
#if HAVE_TIME
232
if (dmsg->loopcount >= 0 && ++(dmsg->loopcount) > 100)
233
{
234
dmsg->loopcount = 0;
235
if (get_time() >= dmsg->startime + LONGTIME)
236
{
237
dmsg->message();
238
dmsg->loopcount = -1;
239
}
240
}
241
#else
242
if (dmsg->loopcount >= 0 && ++(dmsg->loopcount) > LONGLOOP)
243
{
244
dmsg->message();
245
dmsg->loopcount = -1;
246
}
247
#endif
248
}
249
250
/*
251
* Turn off line numbers because the user has interrupted
252
* a lengthy line number calculation.
253
*/
254
static void abort_delayed_msg(struct delayed_msg *dmsg)
255
{
256
if (dmsg->loopcount >= 0)
257
return;
258
if (linenums == OPT_ONPLUS)
259
/*
260
* We were displaying line numbers, so need to repaint.
261
*/
262
screen_trashed();
263
linenums = 0;
264
error("Line numbers turned off", NULL_PARG);
265
}
266
267
/*
268
* Find the line number associated with a given position.
269
* Return 0 if we can't figure it out.
270
*/
271
public LINENUM find_linenum(POSITION pos)
272
{
273
struct linenum_info *p;
274
LINENUM linenum;
275
POSITION cpos;
276
struct delayed_msg dmsg;
277
278
if (!linenums)
279
/*
280
* We're not using line numbers.
281
*/
282
return (0);
283
if (pos == NULL_POSITION)
284
/*
285
* Caller doesn't know what he's talking about.
286
*/
287
return (0);
288
if (pos <= ch_zero())
289
/*
290
* Beginning of file is always line number 1.
291
*/
292
return (1);
293
294
/*
295
* Find the entry nearest to the position we want.
296
*/
297
for (p = anchor.next; p != &anchor && p->pos < pos; p = p->next)
298
continue;
299
if (p->pos == pos)
300
/* Found it exactly. */
301
return (p->line);
302
303
/*
304
* This is the (possibly) time-consuming part.
305
* We start at the line we just found and start
306
* reading the file forward or backward till we
307
* get to the place we want.
308
*
309
* First decide whether we should go forward from the
310
* previous one or backwards from the next one.
311
* The decision is based on which way involves
312
* traversing fewer bytes in the file.
313
*/
314
start_delayed_msg(&dmsg, longloopmessage);
315
if (p == &anchor || pos - p->prev->pos < p->pos - pos)
316
{
317
/*
318
* Go forward.
319
*/
320
p = p->prev;
321
if (ch_seek(p->pos))
322
return (0);
323
for (linenum = p->line, cpos = p->pos; cpos < pos; linenum++)
324
{
325
/*
326
* Allow a signal to abort this loop.
327
*/
328
cpos = forw_raw_line(cpos, NULL, NULL);
329
if (ABORT_SIGS()) {
330
abort_delayed_msg(&dmsg);
331
return (0);
332
}
333
if (cpos == NULL_POSITION)
334
return (0);
335
delayed_msg(&dmsg);
336
}
337
/*
338
* We might as well cache it.
339
*/
340
add_lnum(linenum, cpos);
341
/*
342
* If the given position is not at the start of a line,
343
* make sure we return the correct line number.
344
*/
345
if (cpos > pos)
346
linenum--;
347
} else
348
{
349
/*
350
* Go backward.
351
*/
352
if (ch_seek(p->pos))
353
return (0);
354
for (linenum = p->line, cpos = p->pos; cpos > pos; linenum--)
355
{
356
/*
357
* Allow a signal to abort this loop.
358
*/
359
cpos = back_raw_line(cpos, NULL, NULL);
360
if (ABORT_SIGS()) {
361
abort_delayed_msg(&dmsg);
362
return (0);
363
}
364
if (cpos == NULL_POSITION)
365
return (0);
366
delayed_msg(&dmsg);
367
}
368
/*
369
* We might as well cache it.
370
*/
371
add_lnum(linenum, cpos);
372
}
373
return (linenum);
374
}
375
376
/*
377
* Find the position of a given line number.
378
* Return NULL_POSITION if we can't figure it out.
379
*/
380
public POSITION find_pos(LINENUM linenum)
381
{
382
struct linenum_info *p;
383
POSITION cpos;
384
LINENUM clinenum;
385
386
if (linenum <= 1)
387
/*
388
* Line number 1 is beginning of file.
389
*/
390
return (ch_zero());
391
392
/*
393
* Find the entry nearest to the line number we want.
394
*/
395
for (p = anchor.next; p != &anchor && p->line < linenum; p = p->next)
396
continue;
397
if (p->line == linenum)
398
/* Found it exactly. */
399
return (p->pos);
400
401
if (p == &anchor || linenum - p->prev->line < p->line - linenum)
402
{
403
/*
404
* Go forward.
405
*/
406
p = p->prev;
407
if (ch_seek(p->pos))
408
return (NULL_POSITION);
409
for (clinenum = p->line, cpos = p->pos; clinenum < linenum; clinenum++)
410
{
411
/*
412
* Allow a signal to abort this loop.
413
*/
414
cpos = forw_raw_line(cpos, NULL, NULL);
415
if (ABORT_SIGS())
416
return (NULL_POSITION);
417
if (cpos == NULL_POSITION)
418
return (NULL_POSITION);
419
}
420
} else
421
{
422
/*
423
* Go backward.
424
*/
425
if (ch_seek(p->pos))
426
return (NULL_POSITION);
427
for (clinenum = p->line, cpos = p->pos; clinenum > linenum; clinenum--)
428
{
429
/*
430
* Allow a signal to abort this loop.
431
*/
432
cpos = back_raw_line(cpos, NULL, NULL);
433
if (ABORT_SIGS())
434
return (NULL_POSITION);
435
if (cpos == NULL_POSITION)
436
return (NULL_POSITION);
437
}
438
}
439
/*
440
* We might as well cache it.
441
*/
442
add_lnum(clinenum, cpos);
443
return (cpos);
444
}
445
446
/*
447
* Return the line number of the "current" line.
448
* The argument "where" tells which line is to be considered
449
* the "current" line (e.g. TOP, BOTTOM, MIDDLE, etc).
450
*/
451
public LINENUM currline(int where)
452
{
453
POSITION pos;
454
POSITION len;
455
LINENUM linenum;
456
457
pos = position(where);
458
len = ch_length();
459
while (pos == NULL_POSITION && where >= 0 && where < sc_height)
460
pos = position(++where);
461
if (pos == NULL_POSITION)
462
pos = len;
463
linenum = find_linenum(pos);
464
if (pos == len)
465
linenum--;
466
return (linenum);
467
}
468
469
static void detlenmessage(void)
470
{
471
ierror("Determining length of file", NULL_PARG);
472
}
473
474
/*
475
* Scan entire file, counting line numbers.
476
*/
477
public void scan_eof(void)
478
{
479
POSITION pos = ch_zero();
480
LINENUM linenum = 0;
481
struct delayed_msg dmsg;
482
483
if (ch_seek(0))
484
return;
485
/*
486
* scanning_eof prevents the "Waiting for data" message from
487
* overwriting "Determining length of file".
488
*/
489
start_delayed_msg(&dmsg, detlenmessage);
490
scanning_eof = TRUE;
491
while (pos != NULL_POSITION)
492
{
493
/* For efficiency, only add one every 256 line numbers. */
494
if ((linenum++ % 256) == 0)
495
add_lnum(linenum, pos);
496
pos = forw_raw_line(pos, NULL, NULL);
497
if (ABORT_SIGS())
498
{
499
abort_delayed_msg(&dmsg);
500
break;
501
}
502
delayed_msg(&dmsg);
503
}
504
scanning_eof = FALSE;
505
}
506
507
/*
508
* Return a line number adjusted for display
509
* (handles the --no-number-headers option).
510
*/
511
public LINENUM vlinenum(LINENUM linenum)
512
{
513
if (nonum_headers && header_lines > 0)
514
{
515
LINENUM header_start_line = find_linenum(header_start_pos);
516
if (header_start_line != 0)
517
{
518
LINENUM header_end_line = header_start_line + header_lines; /* first line after header */
519
linenum = (linenum < header_end_line) ? 0 : linenum - header_end_line + 1;
520
}
521
}
522
return linenum;
523
}
524
525