1273e1e22Sbostic /*
2273e1e22Sbostic * Copyright (c) 1988 Mark Nudleman
3*76f06662Sbostic * Copyright (c) 1988, 1993
4*76f06662Sbostic * The Regents of the University of California. All rights reserved.
5273e1e22Sbostic *
69918a12eSbostic * %sccs.include.redist.c%
7273e1e22Sbostic */
8273e1e22Sbostic
9273e1e22Sbostic #ifndef lint
10*76f06662Sbostic static char sccsid[] = "@(#)linenum.c 8.1 (Berkeley) 06/06/93";
11273e1e22Sbostic #endif /* not lint */
12273e1e22Sbostic
13273e1e22Sbostic /*
14273e1e22Sbostic * Code to handle displaying line numbers.
15273e1e22Sbostic *
16273e1e22Sbostic * Finding the line number of a given file position is rather tricky.
17273e1e22Sbostic * We don't want to just start at the beginning of the file and
18273e1e22Sbostic * count newlines, because that is slow for large files (and also
19273e1e22Sbostic * wouldn't work if we couldn't get to the start of the file; e.g.
20273e1e22Sbostic * if input is a long pipe).
21273e1e22Sbostic *
22273e1e22Sbostic * So we use the function add_lnum to cache line numbers.
23273e1e22Sbostic * We try to be very clever and keep only the more interesting
24273e1e22Sbostic * line numbers when we run out of space in our table. A line
25273e1e22Sbostic * number is more interesting than another when it is far from
26273e1e22Sbostic * other line numbers. For example, we'd rather keep lines
27273e1e22Sbostic * 100,200,300 than 100,101,300. 200 is more interesting than
28273e1e22Sbostic * 101 because 101 can be derived very cheaply from 100, while
29273e1e22Sbostic * 200 is more expensive to derive from 100.
30273e1e22Sbostic *
31273e1e22Sbostic * The function currline() returns the line number of a given
32273e1e22Sbostic * position in the file. As a side effect, it calls add_lnum
33273e1e22Sbostic * to cache the line number. Therefore currline is occasionally
34273e1e22Sbostic * called to make sure we cache line numbers often enough.
35273e1e22Sbostic */
36273e1e22Sbostic
37c07fef21Sbostic #include <sys/types.h>
38c07fef21Sbostic #include <stdio.h>
39c07fef21Sbostic #include <less.h>
40273e1e22Sbostic
41273e1e22Sbostic /*
42273e1e22Sbostic * Structure to keep track of a line number and the associated file position.
43273e1e22Sbostic * A doubly-linked circular list of line numbers is kept ordered by line number.
44273e1e22Sbostic */
45273e1e22Sbostic struct linenum
46273e1e22Sbostic {
47273e1e22Sbostic struct linenum *next; /* Link to next in the list */
48273e1e22Sbostic struct linenum *prev; /* Line to previous in the list */
49c07fef21Sbostic off_t pos; /* File position */
50c07fef21Sbostic off_t gap; /* Gap between prev and next */
51273e1e22Sbostic int line; /* Line number */
52273e1e22Sbostic };
53273e1e22Sbostic /*
54273e1e22Sbostic * "gap" needs some explanation: the gap of any particular line number
55273e1e22Sbostic * is the distance between the previous one and the next one in the list.
56273e1e22Sbostic * ("Distance" means difference in file position.) In other words, the
57273e1e22Sbostic * gap of a line number is the gap which would be introduced if this
58273e1e22Sbostic * line number were deleted. It is used to decide which one to replace
59273e1e22Sbostic * when we have a new one to insert and the table is full.
60273e1e22Sbostic */
61273e1e22Sbostic
62273e1e22Sbostic #define NPOOL 50 /* Size of line number pool */
63273e1e22Sbostic
64273e1e22Sbostic #define LONGTIME (2) /* In seconds */
65273e1e22Sbostic
66c07fef21Sbostic int lnloop = 0; /* Are we in the line num loop? */
67273e1e22Sbostic
68273e1e22Sbostic static struct linenum anchor; /* Anchor of the list */
69273e1e22Sbostic static struct linenum *freelist; /* Anchor of the unused entries */
70273e1e22Sbostic static struct linenum pool[NPOOL]; /* The pool itself */
71273e1e22Sbostic static struct linenum *spare; /* We always keep one spare entry */
72273e1e22Sbostic
73273e1e22Sbostic extern int linenums;
74273e1e22Sbostic extern int sigs;
75273e1e22Sbostic
76273e1e22Sbostic /*
77273e1e22Sbostic * Initialize the line number structures.
78273e1e22Sbostic */
clr_linenum()79273e1e22Sbostic clr_linenum()
80273e1e22Sbostic {
81273e1e22Sbostic register struct linenum *p;
82273e1e22Sbostic
83273e1e22Sbostic /*
84273e1e22Sbostic * Put all the entries on the free list.
85273e1e22Sbostic * Leave one for the "spare".
86273e1e22Sbostic */
87273e1e22Sbostic for (p = pool; p < &pool[NPOOL-2]; p++)
88273e1e22Sbostic p->next = p+1;
89273e1e22Sbostic pool[NPOOL-2].next = NULL;
90273e1e22Sbostic freelist = pool;
91273e1e22Sbostic
92273e1e22Sbostic spare = &pool[NPOOL-1];
93273e1e22Sbostic
94273e1e22Sbostic /*
95273e1e22Sbostic * Initialize the anchor.
96273e1e22Sbostic */
97273e1e22Sbostic anchor.next = anchor.prev = &anchor;
98273e1e22Sbostic anchor.gap = 0;
99c07fef21Sbostic anchor.pos = (off_t)0;
100273e1e22Sbostic anchor.line = 1;
101273e1e22Sbostic }
102273e1e22Sbostic
103273e1e22Sbostic /*
104273e1e22Sbostic * Calculate the gap for an entry.
105273e1e22Sbostic */
106c07fef21Sbostic static
calcgap(p)107273e1e22Sbostic calcgap(p)
108273e1e22Sbostic register struct linenum *p;
109273e1e22Sbostic {
110273e1e22Sbostic /*
111273e1e22Sbostic * Don't bother to compute a gap for the anchor.
112273e1e22Sbostic * Also don't compute a gap for the last one in the list.
113273e1e22Sbostic * The gap for that last one should be considered infinite,
114273e1e22Sbostic * but we never look at it anyway.
115273e1e22Sbostic */
116273e1e22Sbostic if (p == &anchor || p->next == &anchor)
117273e1e22Sbostic return;
118273e1e22Sbostic p->gap = p->next->pos - p->prev->pos;
119273e1e22Sbostic }
120273e1e22Sbostic
121273e1e22Sbostic /*
122273e1e22Sbostic * Add a new line number to the cache.
123273e1e22Sbostic * The specified position (pos) should be the file position of the
124273e1e22Sbostic * FIRST character in the specified line.
125273e1e22Sbostic */
add_lnum(line,pos)126273e1e22Sbostic add_lnum(line, pos)
127273e1e22Sbostic int line;
128c07fef21Sbostic off_t pos;
129273e1e22Sbostic {
130273e1e22Sbostic register struct linenum *p;
131273e1e22Sbostic register struct linenum *new;
132273e1e22Sbostic register struct linenum *nextp;
133273e1e22Sbostic register struct linenum *prevp;
134c07fef21Sbostic register off_t mingap;
135273e1e22Sbostic
136273e1e22Sbostic /*
137273e1e22Sbostic * Find the proper place in the list for the new one.
138273e1e22Sbostic * The entries are sorted by position.
139273e1e22Sbostic */
140273e1e22Sbostic for (p = anchor.next; p != &anchor && p->pos < pos; p = p->next)
141273e1e22Sbostic if (p->line == line)
142273e1e22Sbostic /* We already have this one. */
143273e1e22Sbostic return;
144273e1e22Sbostic nextp = p;
145273e1e22Sbostic prevp = p->prev;
146273e1e22Sbostic
147273e1e22Sbostic if (freelist != NULL)
148273e1e22Sbostic {
149273e1e22Sbostic /*
150273e1e22Sbostic * We still have free (unused) entries.
151273e1e22Sbostic * Use one of them.
152273e1e22Sbostic */
153273e1e22Sbostic new = freelist;
154273e1e22Sbostic freelist = freelist->next;
155273e1e22Sbostic } else
156273e1e22Sbostic {
157273e1e22Sbostic /*
158273e1e22Sbostic * No free entries.
159273e1e22Sbostic * Use the "spare" entry.
160273e1e22Sbostic */
161273e1e22Sbostic new = spare;
162273e1e22Sbostic spare = NULL;
163273e1e22Sbostic }
164273e1e22Sbostic
165273e1e22Sbostic /*
166273e1e22Sbostic * Fill in the fields of the new entry,
167273e1e22Sbostic * and insert it into the proper place in the list.
168273e1e22Sbostic */
169273e1e22Sbostic new->next = nextp;
170273e1e22Sbostic new->prev = prevp;
171273e1e22Sbostic new->pos = pos;
172273e1e22Sbostic new->line = line;
173273e1e22Sbostic
174273e1e22Sbostic nextp->prev = new;
175273e1e22Sbostic prevp->next = new;
176273e1e22Sbostic
177273e1e22Sbostic /*
178273e1e22Sbostic * Recalculate gaps for the new entry and the neighboring entries.
179273e1e22Sbostic */
180273e1e22Sbostic calcgap(new);
181273e1e22Sbostic calcgap(nextp);
182273e1e22Sbostic calcgap(prevp);
183273e1e22Sbostic
184273e1e22Sbostic if (spare == NULL)
185273e1e22Sbostic {
186273e1e22Sbostic /*
187273e1e22Sbostic * We have used the spare entry.
188273e1e22Sbostic * Scan the list to find the one with the smallest
189273e1e22Sbostic * gap, take it out and make it the spare.
190273e1e22Sbostic * We should never remove the last one, so stop when
191273e1e22Sbostic * we get to p->next == &anchor. This also avoids
192273e1e22Sbostic * looking at the gap of the last one, which is
193273e1e22Sbostic * not computed by calcgap.
194273e1e22Sbostic */
195273e1e22Sbostic mingap = anchor.next->gap;
196273e1e22Sbostic for (p = anchor.next; p->next != &anchor; p = p->next)
197273e1e22Sbostic {
198273e1e22Sbostic if (p->gap <= mingap)
199273e1e22Sbostic {
200273e1e22Sbostic spare = p;
201273e1e22Sbostic mingap = p->gap;
202273e1e22Sbostic }
203273e1e22Sbostic }
204273e1e22Sbostic spare->next->prev = spare->prev;
205273e1e22Sbostic spare->prev->next = spare->next;
206273e1e22Sbostic }
207273e1e22Sbostic }
208273e1e22Sbostic
209273e1e22Sbostic /*
210273e1e22Sbostic * If we get stuck in a long loop trying to figure out the
211273e1e22Sbostic * line number, print a message to tell the user what we're doing.
212273e1e22Sbostic */
213c07fef21Sbostic static
longloopmessage()214273e1e22Sbostic longloopmessage()
215273e1e22Sbostic {
216273e1e22Sbostic ierror("Calculating line numbers");
217273e1e22Sbostic /*
218273e1e22Sbostic * Set the lnloop flag here, so if the user interrupts while
219273e1e22Sbostic * we are calculating line numbers, the signal handler will
220273e1e22Sbostic * turn off line numbers (linenums=0).
221273e1e22Sbostic */
222273e1e22Sbostic lnloop = 1;
223273e1e22Sbostic }
224273e1e22Sbostic
225273e1e22Sbostic /*
226273e1e22Sbostic * Find the line number associated with a given position.
227273e1e22Sbostic * Return 0 if we can't figure it out.
228273e1e22Sbostic */
find_linenum(pos)229273e1e22Sbostic find_linenum(pos)
230c07fef21Sbostic off_t pos;
231273e1e22Sbostic {
232273e1e22Sbostic register struct linenum *p;
233273e1e22Sbostic register int lno;
234273e1e22Sbostic register int loopcount;
235c07fef21Sbostic off_t cpos, back_raw_line(), forw_raw_line();
236c07fef21Sbostic time_t startime, time();
237273e1e22Sbostic
238273e1e22Sbostic if (!linenums)
239273e1e22Sbostic /*
240273e1e22Sbostic * We're not using line numbers.
241273e1e22Sbostic */
242273e1e22Sbostic return (0);
243273e1e22Sbostic if (pos == NULL_POSITION)
244273e1e22Sbostic /*
245273e1e22Sbostic * Caller doesn't know what he's talking about.
246273e1e22Sbostic */
247273e1e22Sbostic return (0);
248c07fef21Sbostic if (pos == (off_t)0)
249273e1e22Sbostic /*
250273e1e22Sbostic * Beginning of file is always line number 1.
251273e1e22Sbostic */
252273e1e22Sbostic return (1);
253273e1e22Sbostic
254273e1e22Sbostic /*
255273e1e22Sbostic * Find the entry nearest to the position we want.
256273e1e22Sbostic */
257273e1e22Sbostic for (p = anchor.next; p != &anchor && p->pos < pos; p = p->next)
258273e1e22Sbostic continue;
259273e1e22Sbostic if (p->pos == pos)
260273e1e22Sbostic /* Found it exactly. */
261273e1e22Sbostic return (p->line);
262273e1e22Sbostic
263273e1e22Sbostic /*
264273e1e22Sbostic * This is the (possibly) time-consuming part.
265273e1e22Sbostic * We start at the line we just found and start
266273e1e22Sbostic * reading the file forward or backward till we
267273e1e22Sbostic * get to the place we want.
268273e1e22Sbostic *
269273e1e22Sbostic * First decide whether we should go forward from the
270273e1e22Sbostic * previous one or backwards from the next one.
271273e1e22Sbostic * The decision is based on which way involves
272273e1e22Sbostic * traversing fewer bytes in the file.
273273e1e22Sbostic */
274273e1e22Sbostic flush();
275c07fef21Sbostic (void)time(&startime);
276273e1e22Sbostic if (p == &anchor || pos - p->prev->pos < p->pos - pos)
277273e1e22Sbostic {
278273e1e22Sbostic /*
279273e1e22Sbostic * Go forward.
280273e1e22Sbostic */
281273e1e22Sbostic p = p->prev;
282273e1e22Sbostic if (ch_seek(p->pos))
283273e1e22Sbostic return (0);
284273e1e22Sbostic loopcount = 0;
285273e1e22Sbostic for (lno = p->line, cpos = p->pos; cpos < pos; lno++)
286273e1e22Sbostic {
287273e1e22Sbostic /*
288273e1e22Sbostic * Allow a signal to abort this loop.
289273e1e22Sbostic */
290273e1e22Sbostic cpos = forw_raw_line(cpos);
291273e1e22Sbostic if (sigs || cpos == NULL_POSITION)
292273e1e22Sbostic return (0);
293c07fef21Sbostic if (loopcount >= 0 && ++loopcount > 100) {
294273e1e22Sbostic loopcount = 0;
295c07fef21Sbostic if (time((time_t *)NULL)
296c07fef21Sbostic >= startime + LONGTIME) {
297273e1e22Sbostic longloopmessage();
298273e1e22Sbostic loopcount = -1;
299273e1e22Sbostic }
300273e1e22Sbostic }
301273e1e22Sbostic }
302273e1e22Sbostic lnloop = 0;
303273e1e22Sbostic /*
304273e1e22Sbostic * If the given position is not at the start of a line,
305273e1e22Sbostic * make sure we return the correct line number.
306273e1e22Sbostic */
307273e1e22Sbostic if (cpos > pos)
308273e1e22Sbostic lno--;
309273e1e22Sbostic } else
310273e1e22Sbostic {
311273e1e22Sbostic /*
312273e1e22Sbostic * Go backward.
313273e1e22Sbostic */
314273e1e22Sbostic if (ch_seek(p->pos))
315273e1e22Sbostic return (0);
316273e1e22Sbostic loopcount = 0;
317273e1e22Sbostic for (lno = p->line, cpos = p->pos; cpos > pos; lno--)
318273e1e22Sbostic {
319273e1e22Sbostic /*
320273e1e22Sbostic * Allow a signal to abort this loop.
321273e1e22Sbostic */
322273e1e22Sbostic cpos = back_raw_line(cpos);
323273e1e22Sbostic if (sigs || cpos == NULL_POSITION)
324273e1e22Sbostic return (0);
325c07fef21Sbostic if (loopcount >= 0 && ++loopcount > 100) {
326273e1e22Sbostic loopcount = 0;
327c07fef21Sbostic if (time((time_t *)NULL)
328c07fef21Sbostic >= startime + LONGTIME) {
329273e1e22Sbostic longloopmessage();
330273e1e22Sbostic loopcount = -1;
331273e1e22Sbostic }
332273e1e22Sbostic }
333273e1e22Sbostic }
334273e1e22Sbostic lnloop = 0;
335273e1e22Sbostic }
336273e1e22Sbostic
337273e1e22Sbostic /*
338273e1e22Sbostic * We might as well cache it.
339273e1e22Sbostic */
340273e1e22Sbostic add_lnum(lno, cpos);
341273e1e22Sbostic return (lno);
342273e1e22Sbostic }
343273e1e22Sbostic
344273e1e22Sbostic /*
345273e1e22Sbostic * Return the line number of the "current" line.
346273e1e22Sbostic * The argument "where" tells which line is to be considered
347273e1e22Sbostic * the "current" line (e.g. TOP, BOTTOM, MIDDLE, etc).
348273e1e22Sbostic */
currline(where)349273e1e22Sbostic currline(where)
350273e1e22Sbostic int where;
351273e1e22Sbostic {
352c07fef21Sbostic off_t pos, ch_length(), position();
353273e1e22Sbostic
354c07fef21Sbostic if ((pos = position(where)) == NULL_POSITION)
355273e1e22Sbostic pos = ch_length();
356273e1e22Sbostic return(find_linenum(pos));
357273e1e22Sbostic }
358