1 |
|
|
/* |
2 |
|
|
* Copyright (C) 1984-2012 Mark Nudelman |
3 |
|
|
* Modified for use with illumos by Garrett D'Amore. |
4 |
|
|
* Copyright 2014 Garrett D'Amore <garrett@damore.org> |
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 |
|
|
* Code to handle displaying line numbers. |
14 |
|
|
* |
15 |
|
|
* Finding the line number of a given file position is rather tricky. |
16 |
|
|
* We don't want to just start at the beginning of the file and |
17 |
|
|
* count newlines, because that is slow for large files (and also |
18 |
|
|
* wouldn't work if we couldn't get to the start of the file; e.g. |
19 |
|
|
* if input is a long pipe). |
20 |
|
|
* |
21 |
|
|
* So we use the function add_lnum to cache line numbers. |
22 |
|
|
* We try to be very clever and keep only the more interesting |
23 |
|
|
* line numbers when we run out of space in our table. A line |
24 |
|
|
* number is more interesting than another when it is far from |
25 |
|
|
* other line numbers. For example, we'd rather keep lines |
26 |
|
|
* 100,200,300 than 100,101,300. 200 is more interesting than |
27 |
|
|
* 101 because 101 can be derived very cheaply from 100, while |
28 |
|
|
* 200 is more expensive to derive from 100. |
29 |
|
|
* |
30 |
|
|
* The function currline() returns the line number of a given |
31 |
|
|
* position in the file. As a side effect, it calls add_lnum |
32 |
|
|
* to cache the line number. Therefore currline is occasionally |
33 |
|
|
* called to make sure we cache line numbers often enough. |
34 |
|
|
*/ |
35 |
|
|
|
36 |
|
|
#include "less.h" |
37 |
|
|
|
38 |
|
|
/* |
39 |
|
|
* Structure to keep track of a line number and the associated file position. |
40 |
|
|
* A doubly-linked circular list of line numbers is kept ordered by line number. |
41 |
|
|
*/ |
42 |
|
|
struct linenum_info { |
43 |
|
|
struct linenum_info *next; /* Link to next in the list */ |
44 |
|
|
struct linenum_info *prev; /* Line to previous in the list */ |
45 |
|
|
off_t pos; /* File position */ |
46 |
|
|
off_t gap; /* Gap between prev and next */ |
47 |
|
|
off_t 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 NPOOL 200 /* Size of line number pool */ |
59 |
|
|
|
60 |
|
|
#define LONGTIME (2) /* In seconds */ |
61 |
|
|
|
62 |
|
|
static struct linenum_info anchor; /* Anchor of the list */ |
63 |
|
|
static struct linenum_info *freelist; /* Anchor of the unused entries */ |
64 |
|
|
static struct linenum_info pool[NPOOL]; /* The pool itself */ |
65 |
|
|
static struct linenum_info *spare; /* We always keep one spare entry */ |
66 |
|
|
|
67 |
|
|
extern int linenums; |
68 |
|
|
extern volatile sig_atomic_t sigs; |
69 |
|
|
extern int sc_height; |
70 |
|
|
extern int screen_trashed; |
71 |
|
|
|
72 |
|
|
/* |
73 |
|
|
* Initialize the line number structures. |
74 |
|
|
*/ |
75 |
|
|
void |
76 |
|
|
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 |
✓✓ |
3192 |
for (p = pool; p < &pool[NPOOL-2]; p++) |
85 |
|
1584 |
p->next = p+1; |
86 |
|
8 |
pool[NPOOL-2].next = NULL; |
87 |
|
8 |
freelist = pool; |
88 |
|
|
|
89 |
|
8 |
spare = &pool[NPOOL-1]; |
90 |
|
|
|
91 |
|
|
/* |
92 |
|
|
* Initialize the anchor. |
93 |
|
|
*/ |
94 |
|
8 |
anchor.next = anchor.prev = &anchor; |
95 |
|
8 |
anchor.gap = 0; |
96 |
|
8 |
anchor.pos = 0; |
97 |
|
8 |
anchor.line = 1; |
98 |
|
8 |
} |
99 |
|
|
|
100 |
|
|
/* |
101 |
|
|
* Calculate the gap for an entry. |
102 |
|
|
*/ |
103 |
|
|
static void |
104 |
|
|
calcgap(struct linenum_info *p) |
105 |
|
|
{ |
106 |
|
|
/* |
107 |
|
|
* Don't bother to compute a gap for the anchor. |
108 |
|
|
* Also don't compute a gap for the last one in the list. |
109 |
|
|
* The gap for that last one should be considered infinite, |
110 |
|
|
* but we never look at it anyway. |
111 |
|
|
*/ |
112 |
✓✓✓✓
|
193 |
if (p == &anchor || p->next == &anchor) |
113 |
|
|
return; |
114 |
|
30 |
p->gap = p->next->pos - p->prev->pos; |
115 |
|
102 |
} |
116 |
|
|
|
117 |
|
|
/* |
118 |
|
|
* Add a new line number to the cache. |
119 |
|
|
* The specified position (pos) should be the file position of the |
120 |
|
|
* FIRST character in the specified line. |
121 |
|
|
*/ |
122 |
|
|
void |
123 |
|
|
add_lnum(off_t linenum, off_t pos) |
124 |
|
|
{ |
125 |
|
|
struct linenum_info *p; |
126 |
|
|
struct linenum_info *new; |
127 |
|
|
struct linenum_info *nextp; |
128 |
|
|
struct linenum_info *prevp; |
129 |
|
|
off_t mingap; |
130 |
|
|
|
131 |
|
|
/* |
132 |
|
|
* Find the proper place in the list for the new one. |
133 |
|
|
* The entries are sorted by position. |
134 |
|
|
*/ |
135 |
✓✓✓✓
|
289 |
for (p = anchor.next; p != &anchor && p->pos < pos; p = p->next) |
136 |
✗✓ |
46 |
if (p->line == linenum) |
137 |
|
|
/* We already have this one. */ |
138 |
|
|
return; |
139 |
|
|
nextp = p; |
140 |
|
24 |
prevp = p->prev; |
141 |
|
|
|
142 |
✓✗ |
24 |
if (freelist != NULL) { |
143 |
|
|
/* |
144 |
|
|
* We still have free (unused) entries. |
145 |
|
|
* Use one of them. |
146 |
|
|
*/ |
147 |
|
|
new = freelist; |
148 |
|
24 |
freelist = freelist->next; |
149 |
|
24 |
} else { |
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 |
|
24 |
new->next = nextp; |
163 |
|
24 |
new->prev = prevp; |
164 |
|
24 |
new->pos = pos; |
165 |
|
24 |
new->line = linenum; |
166 |
|
|
|
167 |
|
24 |
nextp->prev = new; |
168 |
|
24 |
prevp->next = new; |
169 |
|
|
|
170 |
|
|
/* |
171 |
|
|
* Recalculate gaps for the new entry and the neighboring entries. |
172 |
|
|
*/ |
173 |
|
24 |
calcgap(new); |
174 |
|
24 |
calcgap(nextp); |
175 |
|
24 |
calcgap(prevp); |
176 |
|
|
|
177 |
✗✓ |
24 |
if (spare == NULL) { |
178 |
|
|
/* |
179 |
|
|
* We have used the spare entry. |
180 |
|
|
* Scan the list to find the one with the smallest |
181 |
|
|
* gap, take it out and make it the spare. |
182 |
|
|
* We should never remove the last one, so stop when |
183 |
|
|
* we get to p->next == &anchor. This also avoids |
184 |
|
|
* looking at the gap of the last one, which is |
185 |
|
|
* not computed by calcgap. |
186 |
|
|
*/ |
187 |
|
|
mingap = anchor.next->gap; |
188 |
|
|
for (p = anchor.next; p->next != &anchor; p = p->next) { |
189 |
|
|
if (p->gap <= mingap) { |
190 |
|
|
spare = p; |
191 |
|
|
mingap = p->gap; |
192 |
|
|
} |
193 |
|
|
} |
194 |
|
|
spare->next->prev = spare->prev; |
195 |
|
|
spare->prev->next = spare->next; |
196 |
|
|
} |
197 |
|
48 |
} |
198 |
|
|
|
199 |
|
|
static int loopcount; |
200 |
|
|
static time_t startime; |
201 |
|
|
|
202 |
|
|
static void |
203 |
|
|
longish(void) |
204 |
|
|
{ |
205 |
✓✗✓✓
|
2424 |
if (loopcount >= 0 && ++loopcount > 100) { |
206 |
|
1 |
loopcount = 0; |
207 |
✗✓ |
1 |
if (time(NULL) >= startime + LONGTIME) { |
208 |
|
|
ierror("Calculating line numbers", NULL); |
209 |
|
|
loopcount = -1; |
210 |
|
|
} |
211 |
|
|
} |
212 |
|
808 |
} |
213 |
|
|
|
214 |
|
|
/* |
215 |
|
|
* Turn off line numbers because the user has interrupted |
216 |
|
|
* a lengthy line number calculation. |
217 |
|
|
*/ |
218 |
|
|
static void |
219 |
|
|
abort_long(void) |
220 |
|
|
{ |
221 |
|
|
if (linenums == OPT_ONPLUS) |
222 |
|
|
/* |
223 |
|
|
* We were displaying line numbers, so need to repaint. |
224 |
|
|
*/ |
225 |
|
|
screen_trashed = 1; |
226 |
|
|
linenums = 0; |
227 |
|
|
error("Line numbers turned off", NULL); |
228 |
|
|
} |
229 |
|
|
|
230 |
|
|
/* |
231 |
|
|
* Find the line number associated with a given position. |
232 |
|
|
* Return 0 if we can't figure it out. |
233 |
|
|
*/ |
234 |
|
|
off_t |
235 |
|
|
find_linenum(off_t pos) |
236 |
|
|
{ |
237 |
|
|
struct linenum_info *p; |
238 |
|
|
off_t linenum; |
239 |
|
|
off_t cpos; |
240 |
|
|
|
241 |
✗✓ |
664 |
if (!linenums) |
242 |
|
|
/* |
243 |
|
|
* We're not using line numbers. |
244 |
|
|
*/ |
245 |
|
|
return (0); |
246 |
✗✓ |
332 |
if (pos == -1) |
247 |
|
|
/* |
248 |
|
|
* Caller doesn't know what he's talking about. |
249 |
|
|
*/ |
250 |
|
|
return (0); |
251 |
✓✓ |
332 |
if (pos <= ch_zero()) |
252 |
|
|
/* |
253 |
|
|
* Beginning of file is always line number 1. |
254 |
|
|
*/ |
255 |
|
9 |
return (1); |
256 |
|
|
|
257 |
|
|
/* |
258 |
|
|
* Find the entry nearest to the position we want. |
259 |
|
|
*/ |
260 |
✓✓✓✓
|
6462 |
for (p = anchor.next; p != &anchor && p->pos < pos; p = p->next) |
261 |
|
|
continue; |
262 |
✓✓ |
323 |
if (p->pos == pos) |
263 |
|
|
/* Found it exactly. */ |
264 |
|
302 |
return (p->line); |
265 |
|
|
|
266 |
|
|
/* |
267 |
|
|
* This is the (possibly) time-consuming part. |
268 |
|
|
* We start at the line we just found and start |
269 |
|
|
* reading the file forward or backward till we |
270 |
|
|
* get to the place we want. |
271 |
|
|
* |
272 |
|
|
* First decide whether we should go forward from the |
273 |
|
|
* previous one or backwards from the next one. |
274 |
|
|
* The decision is based on which way involves |
275 |
|
|
* traversing fewer bytes in the file. |
276 |
|
|
*/ |
277 |
|
21 |
startime = time(NULL); |
278 |
✓✓✓✓
|
30 |
if (p == &anchor || pos - p->prev->pos < p->pos - pos) { |
279 |
|
|
/* |
280 |
|
|
* Go forward. |
281 |
|
|
*/ |
282 |
|
17 |
p = p->prev; |
283 |
✗✓ |
17 |
if (ch_seek(p->pos)) |
284 |
|
|
return (0); |
285 |
|
17 |
loopcount = 0; |
286 |
✓✓ |
1426 |
for (linenum = p->line, cpos = p->pos; cpos < pos; linenum++) { |
287 |
|
|
/* |
288 |
|
|
* Allow a signal to abort this loop. |
289 |
|
|
*/ |
290 |
|
696 |
cpos = forw_raw_line(cpos, NULL, NULL); |
291 |
✗✓ |
696 |
if (ABORT_SIGS()) { |
292 |
|
|
abort_long(); |
293 |
|
|
return (0); |
294 |
|
|
} |
295 |
✗✓ |
696 |
if (cpos == -1) |
296 |
|
|
return (0); |
297 |
|
696 |
longish(); |
298 |
|
|
} |
299 |
|
|
/* |
300 |
|
|
* We might as well cache it. |
301 |
|
|
*/ |
302 |
|
17 |
add_lnum(linenum, cpos); |
303 |
|
|
/* |
304 |
|
|
* If the given position is not at the start of a line, |
305 |
|
|
* make sure we return the correct line number. |
306 |
|
|
*/ |
307 |
✗✓ |
17 |
if (cpos > pos) |
308 |
|
|
linenum--; |
309 |
|
|
} else { |
310 |
|
|
/* |
311 |
|
|
* Go backward. |
312 |
|
|
*/ |
313 |
✗✓ |
4 |
if (ch_seek(p->pos)) |
314 |
|
|
return (0); |
315 |
|
4 |
loopcount = 0; |
316 |
✓✓ |
232 |
for (linenum = p->line, cpos = p->pos; cpos > pos; linenum--) { |
317 |
|
|
/* |
318 |
|
|
* Allow a signal to abort this loop. |
319 |
|
|
*/ |
320 |
|
112 |
cpos = back_raw_line(cpos, NULL, NULL); |
321 |
✗✓ |
112 |
if (ABORT_SIGS()) { |
322 |
|
|
abort_long(); |
323 |
|
|
return (0); |
324 |
|
|
} |
325 |
✗✓ |
112 |
if (cpos == -1) |
326 |
|
|
return (0); |
327 |
|
112 |
longish(); |
328 |
|
|
} |
329 |
|
|
/* |
330 |
|
|
* We might as well cache it. |
331 |
|
|
*/ |
332 |
|
4 |
add_lnum(linenum, cpos); |
333 |
|
|
} |
334 |
|
|
|
335 |
|
21 |
return (linenum); |
336 |
|
332 |
} |
337 |
|
|
|
338 |
|
|
/* |
339 |
|
|
* Find the position of a given line number. |
340 |
|
|
* Return -1 if we can't figure it out. |
341 |
|
|
*/ |
342 |
|
|
off_t |
343 |
|
|
find_pos(off_t linenum) |
344 |
|
|
{ |
345 |
|
|
struct linenum_info *p; |
346 |
|
|
off_t cpos; |
347 |
|
|
off_t clinenum; |
348 |
|
|
|
349 |
|
|
if (linenum <= 1) |
350 |
|
|
/* |
351 |
|
|
* Line number 1 is beginning of file. |
352 |
|
|
*/ |
353 |
|
|
return (ch_zero()); |
354 |
|
|
|
355 |
|
|
/* |
356 |
|
|
* Find the entry nearest to the line number we want. |
357 |
|
|
*/ |
358 |
|
|
for (p = anchor.next; p != &anchor && p->line < linenum; p = p->next) |
359 |
|
|
continue; |
360 |
|
|
if (p->line == linenum) |
361 |
|
|
/* Found it exactly. */ |
362 |
|
|
return (p->pos); |
363 |
|
|
|
364 |
|
|
if (p == &anchor || linenum - p->prev->line < p->line - linenum) { |
365 |
|
|
/* |
366 |
|
|
* Go forward. |
367 |
|
|
*/ |
368 |
|
|
p = p->prev; |
369 |
|
|
if (ch_seek(p->pos)) |
370 |
|
|
return (-1); |
371 |
|
|
for (clinenum = p->line, cpos = p->pos; |
372 |
|
|
clinenum < linenum; |
373 |
|
|
clinenum++) { |
374 |
|
|
/* |
375 |
|
|
* Allow a signal to abort this loop. |
376 |
|
|
*/ |
377 |
|
|
cpos = forw_raw_line(cpos, NULL, NULL); |
378 |
|
|
if (ABORT_SIGS()) |
379 |
|
|
return (-1); |
380 |
|
|
if (cpos == -1) |
381 |
|
|
return (-1); |
382 |
|
|
} |
383 |
|
|
} else { |
384 |
|
|
/* |
385 |
|
|
* Go backward. |
386 |
|
|
*/ |
387 |
|
|
if (ch_seek(p->pos)) |
388 |
|
|
return (-1); |
389 |
|
|
for (clinenum = p->line, cpos = p->pos; |
390 |
|
|
clinenum > linenum; |
391 |
|
|
clinenum--) { |
392 |
|
|
/* |
393 |
|
|
* Allow a signal to abort this loop. |
394 |
|
|
*/ |
395 |
|
|
cpos = back_raw_line(cpos, (char **)NULL, (int *)NULL); |
396 |
|
|
if (ABORT_SIGS()) |
397 |
|
|
return (-1); |
398 |
|
|
if (cpos == -1) |
399 |
|
|
return (-1); |
400 |
|
|
} |
401 |
|
|
} |
402 |
|
|
/* |
403 |
|
|
* We might as well cache it. |
404 |
|
|
*/ |
405 |
|
|
add_lnum(clinenum, cpos); |
406 |
|
|
return (cpos); |
407 |
|
|
} |
408 |
|
|
|
409 |
|
|
/* |
410 |
|
|
* Return the line number of the "current" line. |
411 |
|
|
* The argument "where" tells which line is to be considered |
412 |
|
|
* the "current" line (e.g. TOP, BOTTOM, MIDDLE, etc). |
413 |
|
|
*/ |
414 |
|
|
off_t |
415 |
|
|
currline(int where) |
416 |
|
|
{ |
417 |
|
|
off_t pos; |
418 |
|
|
off_t len; |
419 |
|
|
off_t linenum; |
420 |
|
|
|
421 |
|
70 |
pos = position(where); |
422 |
|
35 |
len = ch_length(); |
423 |
✗✓✗✗
|
70 |
while (pos == -1 && where >= 0 && where < sc_height) |
424 |
|
|
pos = position(++where); |
425 |
✓✓ |
35 |
if (pos == -1) |
426 |
|
2 |
pos = len; |
427 |
|
35 |
linenum = find_linenum(pos); |
428 |
✓✓ |
35 |
if (pos == len) |
429 |
|
2 |
linenum--; |
430 |
|
35 |
return (linenum); |
431 |
|
|
} |