xref: /csrg-svn/usr.bin/more/linenum.c (revision 62131)
135209Sbostic /*
235209Sbostic  * Copyright (c) 1988 Mark Nudleman
3*62131Sbostic  * Copyright (c) 1988, 1993
4*62131Sbostic  *	The Regents of the University of California.  All rights reserved.
535209Sbostic  *
642742Sbostic  * %sccs.include.redist.c%
735209Sbostic  */
835209Sbostic 
935209Sbostic #ifndef lint
10*62131Sbostic static char sccsid[] = "@(#)linenum.c	8.1 (Berkeley) 06/06/93";
1135209Sbostic #endif /* not lint */
1235209Sbostic 
1335209Sbostic /*
1435209Sbostic  * Code to handle displaying line numbers.
1535209Sbostic  *
1635209Sbostic  * Finding the line number of a given file position is rather tricky.
1735209Sbostic  * We don't want to just start at the beginning of the file and
1835209Sbostic  * count newlines, because that is slow for large files (and also
1935209Sbostic  * wouldn't work if we couldn't get to the start of the file; e.g.
2035209Sbostic  * if input is a long pipe).
2135209Sbostic  *
2235209Sbostic  * So we use the function add_lnum to cache line numbers.
2335209Sbostic  * We try to be very clever and keep only the more interesting
2435209Sbostic  * line numbers when we run out of space in our table.  A line
2535209Sbostic  * number is more interesting than another when it is far from
2635209Sbostic  * other line numbers.   For example, we'd rather keep lines
2735209Sbostic  * 100,200,300 than 100,101,300.  200 is more interesting than
2835209Sbostic  * 101 because 101 can be derived very cheaply from 100, while
2935209Sbostic  * 200 is more expensive to derive from 100.
3035209Sbostic  *
3135209Sbostic  * The function currline() returns the line number of a given
3235209Sbostic  * position in the file.  As a side effect, it calls add_lnum
3335209Sbostic  * to cache the line number.  Therefore currline is occasionally
3435209Sbostic  * called to make sure we cache line numbers often enough.
3535209Sbostic  */
3635209Sbostic 
3736251Sbostic #include <sys/types.h>
3836251Sbostic #include <stdio.h>
3936251Sbostic #include <less.h>
4035209Sbostic 
4135209Sbostic /*
4235209Sbostic  * Structure to keep track of a line number and the associated file position.
4335209Sbostic  * A doubly-linked circular list of line numbers is kept ordered by line number.
4435209Sbostic  */
4535209Sbostic struct linenum
4635209Sbostic {
4735209Sbostic 	struct linenum *next;		/* Link to next in the list */
4835209Sbostic 	struct linenum *prev;		/* Line to previous in the list */
4936251Sbostic 	off_t pos;			/* File position */
5036251Sbostic 	off_t gap;			/* Gap between prev and next */
5135209Sbostic 	int line;			/* Line number */
5235209Sbostic };
5335209Sbostic /*
5435209Sbostic  * "gap" needs some explanation: the gap of any particular line number
5535209Sbostic  * is the distance between the previous one and the next one in the list.
5635209Sbostic  * ("Distance" means difference in file position.)  In other words, the
5735209Sbostic  * gap of a line number is the gap which would be introduced if this
5835209Sbostic  * line number were deleted.  It is used to decide which one to replace
5935209Sbostic  * when we have a new one to insert and the table is full.
6035209Sbostic  */
6135209Sbostic 
6235209Sbostic #define	NPOOL	50			/* Size of line number pool */
6335209Sbostic 
6435209Sbostic #define	LONGTIME	(2)		/* In seconds */
6535209Sbostic 
6636251Sbostic int lnloop = 0;				/* Are we in the line num loop? */
6735209Sbostic 
6835209Sbostic static struct linenum anchor;		/* Anchor of the list */
6935209Sbostic static struct linenum *freelist;	/* Anchor of the unused entries */
7035209Sbostic static struct linenum pool[NPOOL];	/* The pool itself */
7135209Sbostic static struct linenum *spare;		/* We always keep one spare entry */
7235209Sbostic 
7335209Sbostic extern int linenums;
7435209Sbostic extern int sigs;
7535209Sbostic 
7635209Sbostic /*
7735209Sbostic  * Initialize the line number structures.
7835209Sbostic  */
clr_linenum()7935209Sbostic clr_linenum()
8035209Sbostic {
8135209Sbostic 	register struct linenum *p;
8235209Sbostic 
8335209Sbostic 	/*
8435209Sbostic 	 * Put all the entries on the free list.
8535209Sbostic 	 * Leave one for the "spare".
8635209Sbostic 	 */
8735209Sbostic 	for (p = pool;  p < &pool[NPOOL-2];  p++)
8835209Sbostic 		p->next = p+1;
8935209Sbostic 	pool[NPOOL-2].next = NULL;
9035209Sbostic 	freelist = pool;
9135209Sbostic 
9235209Sbostic 	spare = &pool[NPOOL-1];
9335209Sbostic 
9435209Sbostic 	/*
9535209Sbostic 	 * Initialize the anchor.
9635209Sbostic 	 */
9735209Sbostic 	anchor.next = anchor.prev = &anchor;
9835209Sbostic 	anchor.gap = 0;
9936251Sbostic 	anchor.pos = (off_t)0;
10035209Sbostic 	anchor.line = 1;
10135209Sbostic }
10235209Sbostic 
10335209Sbostic /*
10435209Sbostic  * Calculate the gap for an entry.
10535209Sbostic  */
10636251Sbostic static
calcgap(p)10735209Sbostic calcgap(p)
10835209Sbostic 	register struct linenum *p;
10935209Sbostic {
11035209Sbostic 	/*
11135209Sbostic 	 * Don't bother to compute a gap for the anchor.
11235209Sbostic 	 * Also don't compute a gap for the last one in the list.
11335209Sbostic 	 * The gap for that last one should be considered infinite,
11435209Sbostic 	 * but we never look at it anyway.
11535209Sbostic 	 */
11635209Sbostic 	if (p == &anchor || p->next == &anchor)
11735209Sbostic 		return;
11835209Sbostic 	p->gap = p->next->pos - p->prev->pos;
11935209Sbostic }
12035209Sbostic 
12135209Sbostic /*
12235209Sbostic  * Add a new line number to the cache.
12335209Sbostic  * The specified position (pos) should be the file position of the
12435209Sbostic  * FIRST character in the specified line.
12535209Sbostic  */
add_lnum(line,pos)12635209Sbostic add_lnum(line, pos)
12735209Sbostic 	int line;
12836251Sbostic 	off_t pos;
12935209Sbostic {
13035209Sbostic 	register struct linenum *p;
13135209Sbostic 	register struct linenum *new;
13235209Sbostic 	register struct linenum *nextp;
13335209Sbostic 	register struct linenum *prevp;
13436251Sbostic 	register off_t mingap;
13535209Sbostic 
13635209Sbostic 	/*
13735209Sbostic 	 * Find the proper place in the list for the new one.
13835209Sbostic 	 * The entries are sorted by position.
13935209Sbostic 	 */
14035209Sbostic 	for (p = anchor.next;  p != &anchor && p->pos < pos;  p = p->next)
14135209Sbostic 		if (p->line == line)
14235209Sbostic 			/* We already have this one. */
14335209Sbostic 			return;
14435209Sbostic 	nextp = p;
14535209Sbostic 	prevp = p->prev;
14635209Sbostic 
14735209Sbostic 	if (freelist != NULL)
14835209Sbostic 	{
14935209Sbostic 		/*
15035209Sbostic 		 * We still have free (unused) entries.
15135209Sbostic 		 * Use one of them.
15235209Sbostic 		 */
15335209Sbostic 		new = freelist;
15435209Sbostic 		freelist = freelist->next;
15535209Sbostic 	} else
15635209Sbostic 	{
15735209Sbostic 		/*
15835209Sbostic 		 * No free entries.
15935209Sbostic 		 * Use the "spare" entry.
16035209Sbostic 		 */
16135209Sbostic 		new = spare;
16235209Sbostic 		spare = NULL;
16335209Sbostic 	}
16435209Sbostic 
16535209Sbostic 	/*
16635209Sbostic 	 * Fill in the fields of the new entry,
16735209Sbostic 	 * and insert it into the proper place in the list.
16835209Sbostic 	 */
16935209Sbostic 	new->next = nextp;
17035209Sbostic 	new->prev = prevp;
17135209Sbostic 	new->pos = pos;
17235209Sbostic 	new->line = line;
17335209Sbostic 
17435209Sbostic 	nextp->prev = new;
17535209Sbostic 	prevp->next = new;
17635209Sbostic 
17735209Sbostic 	/*
17835209Sbostic 	 * Recalculate gaps for the new entry and the neighboring entries.
17935209Sbostic 	 */
18035209Sbostic 	calcgap(new);
18135209Sbostic 	calcgap(nextp);
18235209Sbostic 	calcgap(prevp);
18335209Sbostic 
18435209Sbostic 	if (spare == NULL)
18535209Sbostic 	{
18635209Sbostic 		/*
18735209Sbostic 		 * We have used the spare entry.
18835209Sbostic 		 * Scan the list to find the one with the smallest
18935209Sbostic 		 * gap, take it out and make it the spare.
19035209Sbostic 		 * We should never remove the last one, so stop when
19135209Sbostic 		 * we get to p->next == &anchor.  This also avoids
19235209Sbostic 		 * looking at the gap of the last one, which is
19335209Sbostic 		 * not computed by calcgap.
19435209Sbostic 		 */
19535209Sbostic 		mingap = anchor.next->gap;
19635209Sbostic 		for (p = anchor.next;  p->next != &anchor;  p = p->next)
19735209Sbostic 		{
19835209Sbostic 			if (p->gap <= mingap)
19935209Sbostic 			{
20035209Sbostic 				spare = p;
20135209Sbostic 				mingap = p->gap;
20235209Sbostic 			}
20335209Sbostic 		}
20435209Sbostic 		spare->next->prev = spare->prev;
20535209Sbostic 		spare->prev->next = spare->next;
20635209Sbostic 	}
20735209Sbostic }
20835209Sbostic 
20935209Sbostic /*
21035209Sbostic  * If we get stuck in a long loop trying to figure out the
21135209Sbostic  * line number, print a message to tell the user what we're doing.
21235209Sbostic  */
21336251Sbostic static
longloopmessage()21435209Sbostic longloopmessage()
21535209Sbostic {
21635209Sbostic 	ierror("Calculating line numbers");
21735209Sbostic 	/*
21835209Sbostic 	 * Set the lnloop flag here, so if the user interrupts while
21935209Sbostic 	 * we are calculating line numbers, the signal handler will
22035209Sbostic 	 * turn off line numbers (linenums=0).
22135209Sbostic 	 */
22235209Sbostic 	lnloop = 1;
22335209Sbostic }
22435209Sbostic 
22535209Sbostic /*
22635209Sbostic  * Find the line number associated with a given position.
22735209Sbostic  * Return 0 if we can't figure it out.
22835209Sbostic  */
find_linenum(pos)22935209Sbostic find_linenum(pos)
23036251Sbostic 	off_t pos;
23135209Sbostic {
23235209Sbostic 	register struct linenum *p;
23335209Sbostic 	register int lno;
23435209Sbostic 	register int loopcount;
23536251Sbostic 	off_t cpos, back_raw_line(), forw_raw_line();
23636251Sbostic 	time_t startime, time();
23735209Sbostic 
23835209Sbostic 	if (!linenums)
23935209Sbostic 		/*
24035209Sbostic 		 * We're not using line numbers.
24135209Sbostic 		 */
24235209Sbostic 		return (0);
24335209Sbostic 	if (pos == NULL_POSITION)
24435209Sbostic 		/*
24535209Sbostic 		 * Caller doesn't know what he's talking about.
24635209Sbostic 		 */
24735209Sbostic 		return (0);
24836251Sbostic 	if (pos == (off_t)0)
24935209Sbostic 		/*
25035209Sbostic 		 * Beginning of file is always line number 1.
25135209Sbostic 		 */
25235209Sbostic 		return (1);
25335209Sbostic 
25435209Sbostic 	/*
25535209Sbostic 	 * Find the entry nearest to the position we want.
25635209Sbostic 	 */
25735209Sbostic 	for (p = anchor.next;  p != &anchor && p->pos < pos;  p = p->next)
25835209Sbostic 		continue;
25935209Sbostic 	if (p->pos == pos)
26035209Sbostic 		/* Found it exactly. */
26135209Sbostic 		return (p->line);
26235209Sbostic 
26335209Sbostic 	/*
26435209Sbostic 	 * This is the (possibly) time-consuming part.
26535209Sbostic 	 * We start at the line we just found and start
26635209Sbostic 	 * reading the file forward or backward till we
26735209Sbostic 	 * get to the place we want.
26835209Sbostic 	 *
26935209Sbostic 	 * First decide whether we should go forward from the
27035209Sbostic 	 * previous one or backwards from the next one.
27135209Sbostic 	 * The decision is based on which way involves
27235209Sbostic 	 * traversing fewer bytes in the file.
27335209Sbostic 	 */
27435209Sbostic 	flush();
27536251Sbostic 	(void)time(&startime);
27635209Sbostic 	if (p == &anchor || pos - p->prev->pos < p->pos - pos)
27735209Sbostic 	{
27835209Sbostic 		/*
27935209Sbostic 		 * Go forward.
28035209Sbostic 		 */
28135209Sbostic 		p = p->prev;
28235209Sbostic 		if (ch_seek(p->pos))
28335209Sbostic 			return (0);
28435209Sbostic 		loopcount = 0;
28535209Sbostic 		for (lno = p->line, cpos = p->pos;  cpos < pos;  lno++)
28635209Sbostic 		{
28735209Sbostic 			/*
28835209Sbostic 			 * Allow a signal to abort this loop.
28935209Sbostic 			 */
29035209Sbostic 			cpos = forw_raw_line(cpos);
29135209Sbostic 			if (sigs || cpos == NULL_POSITION)
29235209Sbostic 				return (0);
29336251Sbostic 			if (loopcount >= 0 && ++loopcount > 100) {
29435209Sbostic 				loopcount = 0;
29536251Sbostic 				if (time((time_t *)NULL)
29636251Sbostic 				    >= startime + LONGTIME) {
29735209Sbostic 					longloopmessage();
29835209Sbostic 					loopcount = -1;
29935209Sbostic 				}
30035209Sbostic 			}
30135209Sbostic 		}
30235209Sbostic 		lnloop = 0;
30335209Sbostic 		/*
30435209Sbostic 		 * If the given position is not at the start of a line,
30535209Sbostic 		 * make sure we return the correct line number.
30635209Sbostic 		 */
30735209Sbostic 		if (cpos > pos)
30835209Sbostic 			lno--;
30935209Sbostic 	} else
31035209Sbostic 	{
31135209Sbostic 		/*
31235209Sbostic 		 * Go backward.
31335209Sbostic 		 */
31435209Sbostic 		if (ch_seek(p->pos))
31535209Sbostic 			return (0);
31635209Sbostic 		loopcount = 0;
31735209Sbostic 		for (lno = p->line, cpos = p->pos;  cpos > pos;  lno--)
31835209Sbostic 		{
31935209Sbostic 			/*
32035209Sbostic 			 * Allow a signal to abort this loop.
32135209Sbostic 			 */
32235209Sbostic 			cpos = back_raw_line(cpos);
32335209Sbostic 			if (sigs || cpos == NULL_POSITION)
32435209Sbostic 				return (0);
32536251Sbostic 			if (loopcount >= 0 && ++loopcount > 100) {
32635209Sbostic 				loopcount = 0;
32736251Sbostic 				if (time((time_t *)NULL)
32836251Sbostic 				    >= startime + LONGTIME) {
32935209Sbostic 					longloopmessage();
33035209Sbostic 					loopcount = -1;
33135209Sbostic 				}
33235209Sbostic 			}
33335209Sbostic 		}
33435209Sbostic 		lnloop = 0;
33535209Sbostic 	}
33635209Sbostic 
33735209Sbostic 	/*
33835209Sbostic 	 * We might as well cache it.
33935209Sbostic 	 */
34035209Sbostic 	add_lnum(lno, cpos);
34135209Sbostic 	return (lno);
34235209Sbostic }
34335209Sbostic 
34435209Sbostic /*
34535209Sbostic  * Return the line number of the "current" line.
34635209Sbostic  * The argument "where" tells which line is to be considered
34735209Sbostic  * the "current" line (e.g. TOP, BOTTOM, MIDDLE, etc).
34835209Sbostic  */
currline(where)34935209Sbostic currline(where)
35035209Sbostic 	int where;
35135209Sbostic {
35236251Sbostic 	off_t pos, ch_length(), position();
35335209Sbostic 
35436251Sbostic 	if ((pos = position(where)) == NULL_POSITION)
35535209Sbostic 		pos = ch_length();
35636251Sbostic 	return(find_linenum(pos));
35735209Sbostic }
358