* Copyright (c) 1988 Mark Nudleman
* Copyright (c) 1988 Regents of the University of California.
* Redistribution and use in source and binary forms are permitted
* provided that the above copyright notice and this paragraph are
* duplicated in all such forms and that any documentation,
* advertising materials, and other materials related to such
* distribution and use acknowledge that the software was developed
* by Mark Nudleman and the University of California, Berkeley. The
* name of Mark Nudleman or the
* University may not be used to endorse or promote products derived
* from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
static char sccsid
[] = "@(#)linenum.c 5.5 (Berkeley) %G%";
* Code to handle displaying line numbers.
* Finding the line number of a given file position is rather tricky.
* We don't want to just start at the beginning of the file and
* count newlines, because that is slow for large files (and also
* wouldn't work if we couldn't get to the start of the file; e.g.
* if input is a long pipe).
* So we use the function add_lnum to cache line numbers.
* We try to be very clever and keep only the more interesting
* line numbers when we run out of space in our table. A line
* number is more interesting than another when it is far from
* other line numbers. For example, we'd rather keep lines
* 100,200,300 than 100,101,300. 200 is more interesting than
* 101 because 101 can be derived very cheaply from 100, while
* 200 is more expensive to derive from 100.
* The function currline() returns the line number of a given
* position in the file. As a side effect, it calls add_lnum
* to cache the line number. Therefore currline is occasionally
* called to make sure we cache line numbers often enough.
* Structure to keep track of a line number and the associated file position.
* A doubly-linked circular list of line numbers is kept ordered by line number.
struct linenum
*next
; /* Link to next in the list */
struct linenum
*prev
; /* Line to previous in the list */
off_t pos
; /* File position */
off_t gap
; /* Gap between prev and next */
int line
; /* Line number */
* "gap" needs some explanation: the gap of any particular line number
* is the distance between the previous one and the next one in the list.
* ("Distance" means difference in file position.) In other words, the
* gap of a line number is the gap which would be introduced if this
* line number were deleted. It is used to decide which one to replace
* when we have a new one to insert and the table is full.
#define NPOOL 50 /* Size of line number pool */
#define LONGTIME (2) /* In seconds */
int lnloop
= 0; /* Are we in the line num loop? */
static struct linenum anchor
; /* Anchor of the list */
static struct linenum
*freelist
; /* Anchor of the unused entries */
static struct linenum pool
[NPOOL
]; /* The pool itself */
static struct linenum
*spare
; /* We always keep one spare entry */
* Initialize the line number structures.
register struct linenum
*p
;
* Put all the entries on the free list.
* Leave one for the "spare".
for (p
= pool
; p
< &pool
[NPOOL
-2]; p
++)
pool
[NPOOL
-2].next
= NULL
;
anchor
.next
= anchor
.prev
= &anchor
;
* Calculate the gap for an entry.
register struct linenum
*p
;
* Don't bother to compute a gap for the anchor.
* Also don't compute a gap for the last one in the list.
* The gap for that last one should be considered infinite,
* but we never look at it anyway.
if (p
== &anchor
|| p
->next
== &anchor
)
p
->gap
= p
->next
->pos
- p
->prev
->pos
;
* Add a new line number to the cache.
* The specified position (pos) should be the file position of the
* FIRST character in the specified line.
register struct linenum
*p
;
register struct linenum
*new;
register struct linenum
*nextp
;
register struct linenum
*prevp
;
* Find the proper place in the list for the new one.
* The entries are sorted by position.
for (p
= anchor
.next
; p
!= &anchor
&& p
->pos
< pos
; p
= p
->next
)
/* We already have this one. */
* We still have free (unused) entries.
freelist
= freelist
->next
;
* Fill in the fields of the new entry,
* and insert it into the proper place in the list.
* Recalculate gaps for the new entry and the neighboring entries.
* We have used the spare entry.
* Scan the list to find the one with the smallest
* gap, take it out and make it the spare.
* We should never remove the last one, so stop when
* we get to p->next == &anchor. This also avoids
* looking at the gap of the last one, which is
* not computed by calcgap.
mingap
= anchor
.next
->gap
;
for (p
= anchor
.next
; p
->next
!= &anchor
; p
= p
->next
)
spare
->next
->prev
= spare
->prev
;
spare
->prev
->next
= spare
->next
;
* If we get stuck in a long loop trying to figure out the
* line number, print a message to tell the user what we're doing.
ierror("Calculating line numbers");
* Set the lnloop flag here, so if the user interrupts while
* we are calculating line numbers, the signal handler will
* turn off line numbers (linenums=0).
* Find the line number associated with a given position.
* Return 0 if we can't figure it out.
register struct linenum
*p
;
off_t cpos
, back_raw_line(), forw_raw_line();
* We're not using line numbers.
if (pos
== NULL_POSITION
)
* Caller doesn't know what he's talking about.
* Beginning of file is always line number 1.
* Find the entry nearest to the position we want.
for (p
= anchor
.next
; p
!= &anchor
&& p
->pos
< pos
; p
= p
->next
)
* This is the (possibly) time-consuming part.
* We start at the line we just found and start
* reading the file forward or backward till we
* get to the place we want.
* First decide whether we should go forward from the
* previous one or backwards from the next one.
* The decision is based on which way involves
* traversing fewer bytes in the file.
if (p
== &anchor
|| pos
- p
->prev
->pos
< p
->pos
- pos
)
for (lno
= p
->line
, cpos
= p
->pos
; cpos
< pos
; lno
++)
* Allow a signal to abort this loop.
cpos
= forw_raw_line(cpos
);
if (sigs
|| cpos
== NULL_POSITION
)
if (loopcount
>= 0 && ++loopcount
> 100) {
>= startime
+ LONGTIME
) {
* If the given position is not at the start of a line,
* make sure we return the correct line number.
for (lno
= p
->line
, cpos
= p
->pos
; cpos
> pos
; lno
--)
* Allow a signal to abort this loop.
cpos
= back_raw_line(cpos
);
if (sigs
|| cpos
== NULL_POSITION
)
if (loopcount
>= 0 && ++loopcount
> 100) {
>= startime
+ LONGTIME
) {
* We might as well cache it.
* Return the line number of the "current" line.
* The argument "where" tells which line is to be considered
* the "current" line (e.g. TOP, BOTTOM, MIDDLE, etc).
off_t pos
, ch_length(), position();
if ((pos
= position(where
)) == NULL_POSITION
)
return(find_linenum(pos
));