xref: /netbsd-src/external/bsd/less/dist/linenum.c (revision a536ee5124e62c9a0051a252f7833dc8f50f44c9)
1 /*	$NetBSD: linenum.c,v 1.3 2011/07/03 20:14:13 tron Exp $	*/
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 static void calcgap __P((struct linenum_info *));
76 static void longloopmessage __P((void));
77 static void longish __P((void));
78 
79 /*
80  * Initialize the line number structures.
81  */
82 	public void
83 clr_linenum()
84 {
85 	register struct linenum_info *p;
86 
87 	/*
88 	 * Put all the entries on the free list.
89 	 * Leave one for the "spare".
90 	 */
91 	for (p = pool;  p < &pool[NPOOL-2];  p++)
92 		p->next = p+1;
93 	pool[NPOOL-2].next = NULL;
94 	freelist = pool;
95 
96 	spare = &pool[NPOOL-1];
97 
98 	/*
99 	 * Initialize the anchor.
100 	 */
101 	anchor.next = anchor.prev = &anchor;
102 	anchor.gap = 0;
103 	anchor.pos = (POSITION)0;
104 	anchor.line = 1;
105 }
106 
107 /*
108  * Calculate the gap for an entry.
109  */
110 	static void
111 calcgap(p)
112 	register struct linenum_info *p;
113 {
114 	/*
115 	 * Don't bother to compute a gap for the anchor.
116 	 * Also don't compute a gap for the last one in the list.
117 	 * The gap for that last one should be considered infinite,
118 	 * but we never look at it anyway.
119 	 */
120 	if (p == &anchor || p->next == &anchor)
121 		return;
122 	p->gap = p->next->pos - p->prev->pos;
123 }
124 
125 /*
126  * Add a new line number to the cache.
127  * The specified position (pos) should be the file position of the
128  * FIRST character in the specified line.
129  */
130 	public void
131 add_lnum(linenum, pos)
132 	LINENUM linenum;
133 	POSITION pos;
134 {
135 	register struct linenum_info *p;
136 	register struct linenum_info *new;
137 	register struct linenum_info *nextp;
138 	register struct linenum_info *prevp;
139 	register POSITION mingap;
140 
141 	/*
142 	 * Find the proper place in the list for the new one.
143 	 * The entries are sorted by position.
144 	 */
145 	for (p = anchor.next;  p != &anchor && p->pos < pos;  p = p->next)
146 		if (p->line == linenum)
147 			/* We already have this one. */
148 			return;
149 	nextp = p;
150 	prevp = p->prev;
151 
152 	if (freelist != NULL)
153 	{
154 		/*
155 		 * We still have free (unused) entries.
156 		 * Use one of them.
157 		 */
158 		new = freelist;
159 		freelist = freelist->next;
160 	} else
161 	{
162 		/*
163 		 * No free entries.
164 		 * Use the "spare" entry.
165 		 */
166 		new = spare;
167 		spare = NULL;
168 	}
169 
170 	/*
171 	 * Fill in the fields of the new entry,
172 	 * and insert it into the proper place in the list.
173 	 */
174 	new->next = nextp;
175 	new->prev = prevp;
176 	new->pos = pos;
177 	new->line = linenum;
178 
179 	nextp->prev = new;
180 	prevp->next = new;
181 
182 	/*
183 	 * Recalculate gaps for the new entry and the neighboring entries.
184 	 */
185 	calcgap(new);
186 	calcgap(nextp);
187 	calcgap(prevp);
188 
189 	if (spare == NULL)
190 	{
191 		/*
192 		 * We have used the spare entry.
193 		 * Scan the list to find the one with the smallest
194 		 * gap, take it out and make it the spare.
195 		 * We should never remove the last one, so stop when
196 		 * we get to p->next == &anchor.  This also avoids
197 		 * looking at the gap of the last one, which is
198 		 * not computed by calcgap.
199 		 */
200 		mingap = anchor.next->gap;
201 		for (p = anchor.next;  p->next != &anchor;  p = p->next)
202 		{
203 			if (p->gap <= mingap)
204 			{
205 				spare = p;
206 				mingap = p->gap;
207 			}
208 		}
209 		spare->next->prev = spare->prev;
210 		spare->prev->next = spare->next;
211 	}
212 }
213 
214 /*
215  * If we get stuck in a long loop trying to figure out the
216  * line number, print a message to tell the user what we're doing.
217  */
218 	static void
219 longloopmessage()
220 {
221 	ierror("Calculating line numbers", NULL_PARG);
222 }
223 
224 static int loopcount;
225 #if HAVE_TIME
226 static long startime;
227 #endif
228 
229 	static void
230 longish()
231 {
232 #if HAVE_TIME
233 	if (loopcount >= 0 && ++loopcount > 100)
234 	{
235 		loopcount = 0;
236 		if (get_time() >= startime + LONGTIME)
237 		{
238 			longloopmessage();
239 			loopcount = -1;
240 		}
241 	}
242 #else
243 	if (loopcount >= 0 && ++loopcount > LONGLOOP)
244 	{
245 		longloopmessage();
246 		loopcount = -1;
247 	}
248 #endif
249 }
250 
251 /*
252  * Turn off line numbers because the user has interrupted
253  * a lengthy line number calculation.
254  */
255 	static void
256 abort_long()
257 {
258 	if (linenums == OPT_ONPLUS)
259 		/*
260 		 * We were displaying line numbers, so need to repaint.
261 		 */
262 		screen_trashed = 1;
263 	linenums = 0;
264 	error("Line numbers turned off", NULL_PARG);
265 }
266 
267 /*
268  * Find the line number associated with a given position.
269  * Return 0 if we can't figure it out.
270  */
271 	public LINENUM
272 find_linenum(pos)
273 	POSITION pos;
274 {
275 	register struct linenum_info *p;
276 	register LINENUM linenum;
277 	POSITION cpos;
278 
279 	if (!linenums)
280 		/*
281 		 * We're not using line numbers.
282 		 */
283 		return (0);
284 	if (pos == NULL_POSITION)
285 		/*
286 		 * Caller doesn't know what he's talking about.
287 		 */
288 		return (0);
289 	if (pos <= ch_zero())
290 		/*
291 		 * Beginning of file is always line number 1.
292 		 */
293 		return (1);
294 
295 	/*
296 	 * Find the entry nearest to the position we want.
297 	 */
298 	for (p = anchor.next;  p != &anchor && p->pos < pos;  p = p->next)
299 		continue;
300 	if (p->pos == pos)
301 		/* Found it exactly. */
302 		return (p->line);
303 
304 	/*
305 	 * This is the (possibly) time-consuming part.
306 	 * We start at the line we just found and start
307 	 * reading the file forward or backward till we
308 	 * get to the place we want.
309 	 *
310 	 * First decide whether we should go forward from the
311 	 * previous one or backwards from the next one.
312 	 * The decision is based on which way involves
313 	 * traversing fewer bytes in the file.
314 	 */
315 #if HAVE_TIME
316 	startime = get_time();
317 #endif
318 	if (p == &anchor || pos - p->prev->pos < p->pos - pos)
319 	{
320 		/*
321 		 * Go forward.
322 		 */
323 		p = p->prev;
324 		if (ch_seek(p->pos))
325 			return (0);
326 		loopcount = 0;
327 		for (linenum = p->line, cpos = p->pos;  cpos < pos;  linenum++)
328 		{
329 			/*
330 			 * Allow a signal to abort this loop.
331 			 */
332 			cpos = forw_raw_line(cpos, (char **)NULL, (int *)NULL);
333 			if (ABORT_SIGS()) {
334 				abort_long();
335 				return (0);
336 			}
337 			if (cpos == NULL_POSITION)
338 				return (0);
339 			longish();
340 		}
341 		/*
342 		 * We might as well cache it.
343 		 */
344 		add_lnum(linenum, cpos);
345 		/*
346 		 * If the given position is not at the start of a line,
347 		 * make sure we return the correct line number.
348 		 */
349 		if (cpos > pos)
350 			linenum--;
351 	} else
352 	{
353 		/*
354 		 * Go backward.
355 		 */
356 		if (ch_seek(p->pos))
357 			return (0);
358 		loopcount = 0;
359 		for (linenum = p->line, cpos = p->pos;  cpos > pos;  linenum--)
360 		{
361 			/*
362 			 * Allow a signal to abort this loop.
363 			 */
364 			cpos = back_raw_line(cpos, (char **)NULL, (int *)NULL);
365 			if (ABORT_SIGS()) {
366 				abort_long();
367 				return (0);
368 			}
369 			if (cpos == NULL_POSITION)
370 				return (0);
371 			longish();
372 		}
373 		/*
374 		 * We might as well cache it.
375 		 */
376 		add_lnum(linenum, cpos);
377 	}
378 
379 	return (linenum);
380 }
381 
382 /*
383  * Find the position of a given line number.
384  * Return NULL_POSITION if we can't figure it out.
385  */
386 	public POSITION
387 find_pos(linenum)
388 	LINENUM linenum;
389 {
390 	register struct linenum_info *p;
391 	POSITION cpos;
392 	LINENUM clinenum;
393 
394 	if (linenum <= 1)
395 		/*
396 		 * Line number 1 is beginning of file.
397 		 */
398 		return (ch_zero());
399 
400 	/*
401 	 * Find the entry nearest to the line number we want.
402 	 */
403 	for (p = anchor.next;  p != &anchor && p->line < linenum;  p = p->next)
404 		continue;
405 	if (p->line == linenum)
406 		/* Found it exactly. */
407 		return (p->pos);
408 
409 	if (p == &anchor || linenum - p->prev->line < p->line - linenum)
410 	{
411 		/*
412 		 * Go forward.
413 		 */
414 		p = p->prev;
415 		if (ch_seek(p->pos))
416 			return (NULL_POSITION);
417 		for (clinenum = p->line, cpos = p->pos;  clinenum < linenum;  clinenum++)
418 		{
419 			/*
420 			 * Allow a signal to abort this loop.
421 			 */
422 			cpos = forw_raw_line(cpos, (char **)NULL, (int *)NULL);
423 			if (ABORT_SIGS())
424 				return (NULL_POSITION);
425 			if (cpos == NULL_POSITION)
426 				return (NULL_POSITION);
427 		}
428 	} else
429 	{
430 		/*
431 		 * Go backward.
432 		 */
433 		if (ch_seek(p->pos))
434 			return (NULL_POSITION);
435 		for (clinenum = p->line, cpos = p->pos;  clinenum > linenum;  clinenum--)
436 		{
437 			/*
438 			 * Allow a signal to abort this loop.
439 			 */
440 			cpos = back_raw_line(cpos, (char **)NULL, (int *)NULL);
441 			if (ABORT_SIGS())
442 				return (NULL_POSITION);
443 			if (cpos == NULL_POSITION)
444 				return (NULL_POSITION);
445 		}
446 	}
447 	/*
448 	 * We might as well cache it.
449 	 */
450 	add_lnum(clinenum, cpos);
451 	return (cpos);
452 }
453 
454 /*
455  * Return the line number of the "current" line.
456  * The argument "where" tells which line is to be considered
457  * the "current" line (e.g. TOP, BOTTOM, MIDDLE, etc).
458  */
459 	public LINENUM
460 currline(where)
461 	int where;
462 {
463 	POSITION pos;
464 	POSITION len;
465 	LINENUM linenum;
466 
467 	pos = position(where);
468 	len = ch_length();
469 	while (pos == NULL_POSITION && where >= 0 && where < sc_height)
470 		pos = position(++where);
471 	if (pos == NULL_POSITION)
472 		pos = len;
473 	linenum = find_linenum(pos);
474 	if (pos == len)
475 		linenum--;
476 	return (linenum);
477 }
478