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