Home | History | Annotate | Line # | Download | only in dist
linenum.c revision 1.1.1.1
      1 /*	$NetBSD	*/
      2 
      3 /*
      4  * Copyright (C) 1984-2011  Mark Nudelman
      5  *
      6  * You may distribute under the terms of either the GNU General Public
      7  * License or the Less License, as specified in the README file.
      8  *
      9  * For more information about less, or for information on how to
     10  * contact the author, see the README file.
     11  */
     12 
     13 
     14 /*
     15  * Code to handle displaying line numbers.
     16  *
     17  * Finding the line number of a given file position is rather tricky.
     18  * We don't want to just start at the beginning of the file and
     19  * count newlines, because that is slow for large files (and also
     20  * wouldn't work if we couldn't get to the start of the file; e.g.
     21  * if input is a long pipe).
     22  *
     23  * So we use the function add_lnum to cache line numbers.
     24  * We try to be very clever and keep only the more interesting
     25  * line numbers when we run out of space in our table.  A line
     26  * number is more interesting than another when it is far from
     27  * other line numbers.   For example, we'd rather keep lines
     28  * 100,200,300 than 100,101,300.  200 is more interesting than
     29  * 101 because 101 can be derived very cheaply from 100, while
     30  * 200 is more expensive to derive from 100.
     31  *
     32  * The function currline() returns the line number of a given
     33  * position in the file.  As a side effect, it calls add_lnum
     34  * to cache the line number.  Therefore currline is occasionally
     35  * called to make sure we cache line numbers often enough.
     36  */
     37 
     38 #include "less.h"
     39 
     40 /*
     41  * Structure to keep track of a line number and the associated file position.
     42  * A doubly-linked circular list of line numbers is kept ordered by line number.
     43  */
     44 struct linenum_info
     45 {
     46 	struct linenum_info *next;	/* Link to next in the list */
     47 	struct linenum_info *prev;	/* Line to previous in the list */
     48 	POSITION pos;			/* File position */
     49 	POSITION gap;			/* Gap between prev and next */
     50 	LINENUM line;			/* Line number */
     51 };
     52 /*
     53  * "gap" needs some explanation: the gap of any particular line number
     54  * is the distance between the previous one and the next one in the list.
     55  * ("Distance" means difference in file position.)  In other words, the
     56  * gap of a line number is the gap which would be introduced if this
     57  * line number were deleted.  It is used to decide which one to replace
     58  * when we have a new one to insert and the table is full.
     59  */
     60 
     61 #define	NPOOL	200			/* Size of line number pool */
     62 
     63 #define	LONGTIME	(2)		/* In seconds */
     64 
     65 static struct linenum_info anchor;	/* Anchor of the list */
     66 static struct linenum_info *freelist;	/* Anchor of the unused entries */
     67 static struct linenum_info pool[NPOOL];	/* The pool itself */
     68 static struct linenum_info *spare;		/* We always keep one spare entry */
     69 
     70 extern int linenums;
     71 extern int sigs;
     72 extern int sc_height;
     73 extern int screen_trashed;
     74 
     75 /*
     76  * Initialize the line number structures.
     77  */
     78 	public void
     79 clr_linenum()
     80 {
     81 	register struct linenum_info *p;
     82 
     83 	/*
     84 	 * Put all the entries on the free list.
     85 	 * Leave one for the "spare".
     86 	 */
     87 	for (p = pool;  p < &pool[NPOOL-2];  p++)
     88 		p->next = p+1;
     89 	pool[NPOOL-2].next = NULL;
     90 	freelist = pool;
     91 
     92 	spare = &pool[NPOOL-1];
     93 
     94 	/*
     95 	 * Initialize the anchor.
     96 	 */
     97 	anchor.next = anchor.prev = &anchor;
     98 	anchor.gap = 0;
     99 	anchor.pos = (POSITION)0;
    100 	anchor.line = 1;
    101 }
    102 
    103 /*
    104  * Calculate the gap for an entry.
    105  */
    106 	static void
    107 calcgap(p)
    108 	register struct linenum_info *p;
    109 {
    110 	/*
    111 	 * Don't bother to compute a gap for the anchor.
    112 	 * Also don't compute a gap for the last one in the list.
    113 	 * The gap for that last one should be considered infinite,
    114 	 * but we never look at it anyway.
    115 	 */
    116 	if (p == &anchor || p->next == &anchor)
    117 		return;
    118 	p->gap = p->next->pos - p->prev->pos;
    119 }
    120 
    121 /*
    122  * Add a new line number to the cache.
    123  * The specified position (pos) should be the file position of the
    124  * FIRST character in the specified line.
    125  */
    126 	public void
    127 add_lnum(linenum, pos)
    128 	LINENUM linenum;
    129 	POSITION pos;
    130 {
    131 	register struct linenum_info *p;
    132 	register struct linenum_info *new;
    133 	register struct linenum_info *nextp;
    134 	register struct linenum_info *prevp;
    135 	register POSITION mingap;
    136 
    137 	/*
    138 	 * Find the proper place in the list for the new one.
    139 	 * The entries are sorted by position.
    140 	 */
    141 	for (p = anchor.next;  p != &anchor && p->pos < pos;  p = p->next)
    142 		if (p->line == linenum)
    143 			/* We already have this one. */
    144 			return;
    145 	nextp = p;
    146 	prevp = p->prev;
    147 
    148 	if (freelist != NULL)
    149 	{
    150 		/*
    151 		 * We still have free (unused) entries.
    152 		 * Use one of them.
    153 		 */
    154 		new = freelist;
    155 		freelist = freelist->next;
    156 	} else
    157 	{
    158 		/*
    159 		 * No free entries.
    160 		 * Use the "spare" entry.
    161 		 */
    162 		new = spare;
    163 		spare = NULL;
    164 	}
    165 
    166 	/*
    167 	 * Fill in the fields of the new entry,
    168 	 * and insert it into the proper place in the list.
    169 	 */
    170 	new->next = nextp;
    171 	new->prev = prevp;
    172 	new->pos = pos;
    173 	new->line = linenum;
    174 
    175 	nextp->prev = new;
    176 	prevp->next = new;
    177 
    178 	/*
    179 	 * Recalculate gaps for the new entry and the neighboring entries.
    180 	 */
    181 	calcgap(new);
    182 	calcgap(nextp);
    183 	calcgap(prevp);
    184 
    185 	if (spare == NULL)
    186 	{
    187 		/*
    188 		 * We have used the spare entry.
    189 		 * Scan the list to find the one with the smallest
    190 		 * gap, take it out and make it the spare.
    191 		 * We should never remove the last one, so stop when
    192 		 * we get to p->next == &anchor.  This also avoids
    193 		 * looking at the gap of the last one, which is
    194 		 * not computed by calcgap.
    195 		 */
    196 		mingap = anchor.next->gap;
    197 		for (p = anchor.next;  p->next != &anchor;  p = p->next)
    198 		{
    199 			if (p->gap <= mingap)
    200 			{
    201 				spare = p;
    202 				mingap = p->gap;
    203 			}
    204 		}
    205 		spare->next->prev = spare->prev;
    206 		spare->prev->next = spare->next;
    207 	}
    208 }
    209 
    210 /*
    211  * If we get stuck in a long loop trying to figure out the
    212  * line number, print a message to tell the user what we're doing.
    213  */
    214 	static void
    215 longloopmessage()
    216 {
    217 	ierror("Calculating line numbers", NULL_PARG);
    218 }
    219 
    220 static int loopcount;
    221 #if HAVE_TIME
    222 static long startime;
    223 #endif
    224 
    225 	static void
    226 longish()
    227 {
    228 #if HAVE_TIME
    229 	if (loopcount >= 0 && ++loopcount > 100)
    230 	{
    231 		loopcount = 0;
    232 		if (get_time() >= startime + LONGTIME)
    233 		{
    234 			longloopmessage();
    235 			loopcount = -1;
    236 		}
    237 	}
    238 #else
    239 	if (loopcount >= 0 && ++loopcount > LONGLOOP)
    240 	{
    241 		longloopmessage();
    242 		loopcount = -1;
    243 	}
    244 #endif
    245 }
    246 
    247 /*
    248  * Turn off line numbers because the user has interrupted
    249  * a lengthy line number calculation.
    250  */
    251 	static void
    252 abort_long()
    253 {
    254 	if (linenums == OPT_ONPLUS)
    255 		/*
    256 		 * We were displaying line numbers, so need to repaint.
    257 		 */
    258 		screen_trashed = 1;
    259 	linenums = 0;
    260 	error("Line numbers turned off", NULL_PARG);
    261 }
    262 
    263 /*
    264  * Find the line number associated with a given position.
    265  * Return 0 if we can't figure it out.
    266  */
    267 	public LINENUM
    268 find_linenum(pos)
    269 	POSITION pos;
    270 {
    271 	register struct linenum_info *p;
    272 	register LINENUM linenum;
    273 	POSITION cpos;
    274 
    275 	if (!linenums)
    276 		/*
    277 		 * We're not using line numbers.
    278 		 */
    279 		return (0);
    280 	if (pos == NULL_POSITION)
    281 		/*
    282 		 * Caller doesn't know what he's talking about.
    283 		 */
    284 		return (0);
    285 	if (pos <= ch_zero())
    286 		/*
    287 		 * Beginning of file is always line number 1.
    288 		 */
    289 		return (1);
    290 
    291 	/*
    292 	 * Find the entry nearest to the position we want.
    293 	 */
    294 	for (p = anchor.next;  p != &anchor && p->pos < pos;  p = p->next)
    295 		continue;
    296 	if (p->pos == pos)
    297 		/* Found it exactly. */
    298 		return (p->line);
    299 
    300 	/*
    301 	 * This is the (possibly) time-consuming part.
    302 	 * We start at the line we just found and start
    303 	 * reading the file forward or backward till we
    304 	 * get to the place we want.
    305 	 *
    306 	 * First decide whether we should go forward from the
    307 	 * previous one or backwards from the next one.
    308 	 * The decision is based on which way involves
    309 	 * traversing fewer bytes in the file.
    310 	 */
    311 #if HAVE_TIME
    312 	startime = get_time();
    313 #endif
    314 	if (p == &anchor || pos - p->prev->pos < p->pos - pos)
    315 	{
    316 		/*
    317 		 * Go forward.
    318 		 */
    319 		p = p->prev;
    320 		if (ch_seek(p->pos))
    321 			return (0);
    322 		loopcount = 0;
    323 		for (linenum = p->line, cpos = p->pos;  cpos < pos;  linenum++)
    324 		{
    325 			/*
    326 			 * Allow a signal to abort this loop.
    327 			 */
    328 			cpos = forw_raw_line(cpos, (char **)NULL, (int *)NULL);
    329 			if (ABORT_SIGS()) {
    330 				abort_long();
    331 				return (0);
    332 			}
    333 			if (cpos == NULL_POSITION)
    334 				return (0);
    335 			longish();
    336 		}
    337 		/*
    338 		 * We might as well cache it.
    339 		 */
    340 		add_lnum(linenum, cpos);
    341 		/*
    342 		 * If the given position is not at the start of a line,
    343 		 * make sure we return the correct line number.
    344 		 */
    345 		if (cpos > pos)
    346 			linenum--;
    347 	} else
    348 	{
    349 		/*
    350 		 * Go backward.
    351 		 */
    352 		if (ch_seek(p->pos))
    353 			return (0);
    354 		loopcount = 0;
    355 		for (linenum = p->line, cpos = p->pos;  cpos > pos;  linenum--)
    356 		{
    357 			/*
    358 			 * Allow a signal to abort this loop.
    359 			 */
    360 			cpos = back_raw_line(cpos, (char **)NULL, (int *)NULL);
    361 			if (ABORT_SIGS()) {
    362 				abort_long();
    363 				return (0);
    364 			}
    365 			if (cpos == NULL_POSITION)
    366 				return (0);
    367 			longish();
    368 		}
    369 		/*
    370 		 * We might as well cache it.
    371 		 */
    372 		add_lnum(linenum, cpos);
    373 	}
    374 
    375 	return (linenum);
    376 }
    377 
    378 /*
    379  * Find the position of a given line number.
    380  * Return NULL_POSITION if we can't figure it out.
    381  */
    382 	public POSITION
    383 find_pos(linenum)
    384 	LINENUM linenum;
    385 {
    386 	register struct linenum_info *p;
    387 	POSITION cpos;
    388 	LINENUM clinenum;
    389 
    390 	if (linenum <= 1)
    391 		/*
    392 		 * Line number 1 is beginning of file.
    393 		 */
    394 		return (ch_zero());
    395 
    396 	/*
    397 	 * Find the entry nearest to the line number we want.
    398 	 */
    399 	for (p = anchor.next;  p != &anchor && p->line < linenum;  p = p->next)
    400 		continue;
    401 	if (p->line == linenum)
    402 		/* Found it exactly. */
    403 		return (p->pos);
    404 
    405 	if (p == &anchor || linenum - p->prev->line < p->line - linenum)
    406 	{
    407 		/*
    408 		 * Go forward.
    409 		 */
    410 		p = p->prev;
    411 		if (ch_seek(p->pos))
    412 			return (NULL_POSITION);
    413 		for (clinenum = p->line, cpos = p->pos;  clinenum < linenum;  clinenum++)
    414 		{
    415 			/*
    416 			 * Allow a signal to abort this loop.
    417 			 */
    418 			cpos = forw_raw_line(cpos, (char **)NULL, (int *)NULL);
    419 			if (ABORT_SIGS())
    420 				return (NULL_POSITION);
    421 			if (cpos == NULL_POSITION)
    422 				return (NULL_POSITION);
    423 		}
    424 	} else
    425 	{
    426 		/*
    427 		 * Go backward.
    428 		 */
    429 		if (ch_seek(p->pos))
    430 			return (NULL_POSITION);
    431 		for (clinenum = p->line, cpos = p->pos;  clinenum > linenum;  clinenum--)
    432 		{
    433 			/*
    434 			 * Allow a signal to abort this loop.
    435 			 */
    436 			cpos = back_raw_line(cpos, (char **)NULL, (int *)NULL);
    437 			if (ABORT_SIGS())
    438 				return (NULL_POSITION);
    439 			if (cpos == NULL_POSITION)
    440 				return (NULL_POSITION);
    441 		}
    442 	}
    443 	/*
    444 	 * We might as well cache it.
    445 	 */
    446 	add_lnum(clinenum, cpos);
    447 	return (cpos);
    448 }
    449 
    450 /*
    451  * Return the line number of the "current" line.
    452  * The argument "where" tells which line is to be considered
    453  * the "current" line (e.g. TOP, BOTTOM, MIDDLE, etc).
    454  */
    455 	public LINENUM
    456 currline(where)
    457 	int where;
    458 {
    459 	POSITION pos;
    460 	POSITION len;
    461 	LINENUM linenum;
    462 
    463 	pos = position(where);
    464 	len = ch_length();
    465 	while (pos == NULL_POSITION && where >= 0 && where < sc_height)
    466 		pos = position(++where);
    467 	if (pos == NULL_POSITION)
    468 		pos = len;
    469 	linenum = find_linenum(pos);
    470 	if (pos == len)
    471 		linenum--;
    472 	return (linenum);
    473 }
    474