linenum.c revision 1.4 1 /* $NetBSD: linenum.c,v 1.4 2003/08/07 09:28:00 agc Exp $ */
2
3 /*
4 * Copyright (c) 1988, 1993
5 * The Regents of the University of California. All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32 /*
33 * Copyright (c) 1988 Mark Nudleman
34 *
35 * Redistribution and use in source and binary forms, with or without
36 * modification, are permitted provided that the following conditions
37 * are met:
38 * 1. Redistributions of source code must retain the above copyright
39 * notice, this list of conditions and the following disclaimer.
40 * 2. Redistributions in binary form must reproduce the above copyright
41 * notice, this list of conditions and the following disclaimer in the
42 * documentation and/or other materials provided with the distribution.
43 * 3. All advertising materials mentioning features or use of this software
44 * must display the following acknowledgement:
45 * This product includes software developed by the University of
46 * California, Berkeley and its contributors.
47 * 4. Neither the name of the University nor the names of its contributors
48 * may be used to endorse or promote products derived from this software
49 * without specific prior written permission.
50 *
51 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
52 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
53 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
54 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
55 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
56 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
57 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
58 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
59 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
60 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
61 * SUCH DAMAGE.
62 */
63
64 #include <sys/cdefs.h>
65 #ifndef lint
66 #if 0
67 static char sccsid[] = "@(#)linenum.c 8.1 (Berkeley) 6/6/93";
68 #else
69 __RCSID("$NetBSD: linenum.c,v 1.4 2003/08/07 09:28:00 agc Exp $");
70 #endif
71 #endif /* not lint */
72
73 /*
74 * Code to handle displaying line numbers.
75 *
76 * Finding the line number of a given file position is rather tricky.
77 * We don't want to just start at the beginning of the file and
78 * count newlines, because that is slow for large files (and also
79 * wouldn't work if we couldn't get to the start of the file; e.g.
80 * if input is a long pipe).
81 *
82 * So we use the function add_lnum to cache line numbers.
83 * We try to be very clever and keep only the more interesting
84 * line numbers when we run out of space in our table. A line
85 * number is more interesting than another when it is far from
86 * other line numbers. For example, we'd rather keep lines
87 * 100,200,300 than 100,101,300. 200 is more interesting than
88 * 101 because 101 can be derived very cheaply from 100, while
89 * 200 is more expensive to derive from 100.
90 *
91 * The function currline() returns the line number of a given
92 * position in the file. As a side effect, it calls add_lnum
93 * to cache the line number. Therefore currline is occasionally
94 * called to make sure we cache line numbers often enough.
95 */
96
97 #include <sys/types.h>
98 #include <stdio.h>
99 #include <time.h>
100
101 #include "less.h"
102 #include "extern.h"
103
104 /*
105 * Structure to keep track of a line number and the associated file position.
106 * A doubly-linked circular list of line numbers is kept ordered by line number.
107 */
108 struct linenum
109 {
110 struct linenum *next; /* Link to next in the list */
111 struct linenum *prev; /* Line to previous in the list */
112 off_t pos; /* File position */
113 off_t gap; /* Gap between prev and next */
114 int line; /* Line number */
115 };
116 /*
117 * "gap" needs some explanation: the gap of any particular line number
118 * is the distance between the previous one and the next one in the list.
119 * ("Distance" means difference in file position.) In other words, the
120 * gap of a line number is the gap which would be introduced if this
121 * line number were deleted. It is used to decide which one to replace
122 * when we have a new one to insert and the table is full.
123 */
124
125 #define NPOOL 50 /* Size of line number pool */
126
127 #define LONGTIME (2) /* In seconds */
128
129 int lnloop = 0; /* Are we in the line num loop? */
130
131 static struct linenum anchor; /* Anchor of the list */
132 static struct linenum *freelist; /* Anchor of the unused entries */
133 static struct linenum pool[NPOOL]; /* The pool itself */
134 static struct linenum *spare; /* We always keep one spare entry */
135
136 static void calcgap __P((struct linenum *));
137 static void longloopmessage __P((void));
138 /*
139 * Initialize the line number structures.
140 */
141 void
142 clr_linenum()
143 {
144 struct linenum *p;
145
146 /*
147 * Put all the entries on the free list.
148 * Leave one for the "spare".
149 */
150 for (p = pool; p < &pool[NPOOL-2]; p++)
151 p->next = p+1;
152 pool[NPOOL-2].next = NULL;
153 freelist = pool;
154
155 spare = &pool[NPOOL-1];
156
157 /*
158 * Initialize the anchor.
159 */
160 anchor.next = anchor.prev = &anchor;
161 anchor.gap = 0;
162 anchor.pos = (off_t)0;
163 anchor.line = 1;
164 }
165
166 /*
167 * Calculate the gap for an entry.
168 */
169 static void
170 calcgap(p)
171 struct linenum *p;
172 {
173 /*
174 * Don't bother to compute a gap for the anchor.
175 * Also don't compute a gap for the last one in the list.
176 * The gap for that last one should be considered infinite,
177 * but we never look at it anyway.
178 */
179 if (p == &anchor || p->next == &anchor)
180 return;
181 p->gap = p->next->pos - p->prev->pos;
182 }
183
184 /*
185 * Add a new line number to the cache.
186 * The specified position (pos) should be the file position of the
187 * FIRST character in the specified line.
188 */
189 void
190 add_lnum(line, pos)
191 int line;
192 off_t pos;
193 {
194 struct linenum *p;
195 struct linenum *new;
196 struct linenum *nextp;
197 struct linenum *prevp;
198 off_t mingap;
199
200 /*
201 * Find the proper place in the list for the new one.
202 * The entries are sorted by position.
203 */
204 for (p = anchor.next; p != &anchor && p->pos < pos; p = p->next)
205 if (p->line == line)
206 /* We already have this one. */
207 return;
208 nextp = p;
209 prevp = p->prev;
210
211 if (freelist != NULL)
212 {
213 /*
214 * We still have free (unused) entries.
215 * Use one of them.
216 */
217 new = freelist;
218 freelist = freelist->next;
219 } else
220 {
221 /*
222 * No free entries.
223 * Use the "spare" entry.
224 */
225 new = spare;
226 spare = NULL;
227 }
228
229 /*
230 * Fill in the fields of the new entry,
231 * and insert it into the proper place in the list.
232 */
233 new->next = nextp;
234 new->prev = prevp;
235 new->pos = pos;
236 new->line = line;
237
238 nextp->prev = new;
239 prevp->next = new;
240
241 /*
242 * Recalculate gaps for the new entry and the neighboring entries.
243 */
244 calcgap(new);
245 calcgap(nextp);
246 calcgap(prevp);
247
248 if (spare == NULL)
249 {
250 /*
251 * We have used the spare entry.
252 * Scan the list to find the one with the smallest
253 * gap, take it out and make it the spare.
254 * We should never remove the last one, so stop when
255 * we get to p->next == &anchor. This also avoids
256 * looking at the gap of the last one, which is
257 * not computed by calcgap.
258 */
259 mingap = anchor.next->gap;
260 for (p = anchor.next; p->next != &anchor; p = p->next)
261 {
262 if (p->gap <= mingap)
263 {
264 spare = p;
265 mingap = p->gap;
266 }
267 }
268 spare->next->prev = spare->prev;
269 spare->prev->next = spare->next;
270 }
271 }
272
273 /*
274 * If we get stuck in a long loop trying to figure out the
275 * line number, print a message to tell the user what we're doing.
276 */
277 static void
278 longloopmessage()
279 {
280 ierror("Calculating line numbers");
281 /*
282 * Set the lnloop flag here, so if the user interrupts while
283 * we are calculating line numbers, the signal handler will
284 * turn off line numbers (linenums=0).
285 */
286 lnloop = 1;
287 }
288
289 /*
290 * Find the line number associated with a given position.
291 * Return 0 if we can't figure it out.
292 */
293 int
294 find_linenum(pos)
295 off_t pos;
296 {
297 struct linenum *p;
298 int lno;
299 int loopcount;
300 off_t cpos;
301 time_t startime;
302
303 if (!linenums)
304 /*
305 * We're not using line numbers.
306 */
307 return (0);
308 if (pos == NULL_POSITION)
309 /*
310 * Caller doesn't know what he's talking about.
311 */
312 return (0);
313 if (pos == (off_t)0)
314 /*
315 * Beginning of file is always line number 1.
316 */
317 return (1);
318
319 /*
320 * Find the entry nearest to the position we want.
321 */
322 for (p = anchor.next; p != &anchor && p->pos < pos; p = p->next)
323 continue;
324 if (p->pos == pos)
325 /* Found it exactly. */
326 return (p->line);
327
328 /*
329 * This is the (possibly) time-consuming part.
330 * We start at the line we just found and start
331 * reading the file forward or backward till we
332 * get to the place we want.
333 *
334 * First decide whether we should go forward from the
335 * previous one or backwards from the next one.
336 * The decision is based on which way involves
337 * traversing fewer bytes in the file.
338 */
339 flush();
340 (void)time(&startime);
341 if (p == &anchor || pos - p->prev->pos < p->pos - pos)
342 {
343 /*
344 * Go forward.
345 */
346 p = p->prev;
347 if (ch_seek(p->pos))
348 return (0);
349 loopcount = 0;
350 for (lno = p->line, cpos = p->pos; cpos < pos; lno++)
351 {
352 /*
353 * Allow a signal to abort this loop.
354 */
355 cpos = forw_raw_line(cpos);
356 if (sigs || cpos == NULL_POSITION)
357 return (0);
358 if (loopcount >= 0 && ++loopcount > 100) {
359 loopcount = 0;
360 if (time((time_t *)NULL)
361 >= startime + LONGTIME) {
362 longloopmessage();
363 loopcount = -1;
364 }
365 }
366 }
367 lnloop = 0;
368 /*
369 * If the given position is not at the start of a line,
370 * make sure we return the correct line number.
371 */
372 if (cpos > pos)
373 lno--;
374 } else
375 {
376 /*
377 * Go backward.
378 */
379 if (ch_seek(p->pos))
380 return (0);
381 loopcount = 0;
382 for (lno = p->line, cpos = p->pos; cpos > pos; lno--)
383 {
384 /*
385 * Allow a signal to abort this loop.
386 */
387 cpos = back_raw_line(cpos);
388 if (sigs || cpos == NULL_POSITION)
389 return (0);
390 if (loopcount >= 0 && ++loopcount > 100) {
391 loopcount = 0;
392 if (time((time_t *)NULL)
393 >= startime + LONGTIME) {
394 longloopmessage();
395 loopcount = -1;
396 }
397 }
398 }
399 lnloop = 0;
400 }
401
402 /*
403 * We might as well cache it.
404 */
405 add_lnum(lno, cpos);
406 return (lno);
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 int
415 currline(where)
416 int where;
417 {
418 off_t pos;
419
420 if ((pos = position(where)) == NULL_POSITION)
421 pos = ch_length();
422 return(find_linenum(pos));
423 }
424