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