xref: /original-bsd/usr.bin/more/linenum.c (revision 76f06662)
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