BSD 4_4 release
[unix-history] / usr / src / usr.bin / more / linenum.c
CommitLineData
bfe13c81
KB
1/*
2 * Copyright (c) 1988 Mark Nudleman
ad787160
C
3 * Copyright (c) 1988, 1993
4 * The Regents of the University of California. All rights reserved.
bfe13c81 5 *
ad787160
C
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. All advertising materials mentioning features or use of this software
15 * must display the following acknowledgement:
16 * This product includes software developed by the University of
17 * California, Berkeley and its contributors.
18 * 4. Neither the name of the University nor the names of its contributors
19 * may be used to endorse or promote products derived from this software
20 * without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
bfe13c81
KB
33 */
34
35#ifndef lint
ad787160 36static char sccsid[] = "@(#)linenum.c 8.1 (Berkeley) 6/6/93";
bfe13c81
KB
37#endif /* not lint */
38
39/*
40 * Code to handle displaying line numbers.
41 *
42 * Finding the line number of a given file position is rather tricky.
43 * We don't want to just start at the beginning of the file and
44 * count newlines, because that is slow for large files (and also
45 * wouldn't work if we couldn't get to the start of the file; e.g.
46 * if input is a long pipe).
47 *
48 * So we use the function add_lnum to cache line numbers.
49 * We try to be very clever and keep only the more interesting
50 * line numbers when we run out of space in our table. A line
51 * number is more interesting than another when it is far from
52 * other line numbers. For example, we'd rather keep lines
53 * 100,200,300 than 100,101,300. 200 is more interesting than
54 * 101 because 101 can be derived very cheaply from 100, while
55 * 200 is more expensive to derive from 100.
56 *
57 * The function currline() returns the line number of a given
58 * position in the file. As a side effect, it calls add_lnum
59 * to cache the line number. Therefore currline is occasionally
60 * called to make sure we cache line numbers often enough.
61 */
62
bc258617
KB
63#include <sys/types.h>
64#include <stdio.h>
65#include <less.h>
bfe13c81
KB
66
67/*
68 * Structure to keep track of a line number and the associated file position.
69 * A doubly-linked circular list of line numbers is kept ordered by line number.
70 */
71struct linenum
72{
73 struct linenum *next; /* Link to next in the list */
74 struct linenum *prev; /* Line to previous in the list */
bc258617
KB
75 off_t pos; /* File position */
76 off_t gap; /* Gap between prev and next */
bfe13c81
KB
77 int line; /* Line number */
78};
79/*
80 * "gap" needs some explanation: the gap of any particular line number
81 * is the distance between the previous one and the next one in the list.
82 * ("Distance" means difference in file position.) In other words, the
83 * gap of a line number is the gap which would be introduced if this
84 * line number were deleted. It is used to decide which one to replace
85 * when we have a new one to insert and the table is full.
86 */
87
88#define NPOOL 50 /* Size of line number pool */
89
90#define LONGTIME (2) /* In seconds */
91
bc258617 92int lnloop = 0; /* Are we in the line num loop? */
bfe13c81
KB
93
94static struct linenum anchor; /* Anchor of the list */
95static struct linenum *freelist; /* Anchor of the unused entries */
96static struct linenum pool[NPOOL]; /* The pool itself */
97static struct linenum *spare; /* We always keep one spare entry */
98
99extern int linenums;
100extern int sigs;
101
102/*
103 * Initialize the line number structures.
104 */
bfe13c81
KB
105clr_linenum()
106{
107 register struct linenum *p;
108
109 /*
110 * Put all the entries on the free list.
111 * Leave one for the "spare".
112 */
113 for (p = pool; p < &pool[NPOOL-2]; p++)
114 p->next = p+1;
115 pool[NPOOL-2].next = NULL;
116 freelist = pool;
117
118 spare = &pool[NPOOL-1];
119
120 /*
121 * Initialize the anchor.
122 */
123 anchor.next = anchor.prev = &anchor;
124 anchor.gap = 0;
bc258617 125 anchor.pos = (off_t)0;
bfe13c81
KB
126 anchor.line = 1;
127}
128
129/*
130 * Calculate the gap for an entry.
131 */
bc258617 132static
bfe13c81
KB
133calcgap(p)
134 register struct linenum *p;
135{
136 /*
137 * Don't bother to compute a gap for the anchor.
138 * Also don't compute a gap for the last one in the list.
139 * The gap for that last one should be considered infinite,
140 * but we never look at it anyway.
141 */
142 if (p == &anchor || p->next == &anchor)
143 return;
144 p->gap = p->next->pos - p->prev->pos;
145}
146
147/*
148 * Add a new line number to the cache.
149 * The specified position (pos) should be the file position of the
150 * FIRST character in the specified line.
151 */
bfe13c81
KB
152add_lnum(line, pos)
153 int line;
bc258617 154 off_t pos;
bfe13c81
KB
155{
156 register struct linenum *p;
157 register struct linenum *new;
158 register struct linenum *nextp;
159 register struct linenum *prevp;
bc258617 160 register off_t mingap;
bfe13c81
KB
161
162 /*
163 * Find the proper place in the list for the new one.
164 * The entries are sorted by position.
165 */
166 for (p = anchor.next; p != &anchor && p->pos < pos; p = p->next)
167 if (p->line == line)
168 /* We already have this one. */
169 return;
170 nextp = p;
171 prevp = p->prev;
172
173 if (freelist != NULL)
174 {
175 /*
176 * We still have free (unused) entries.
177 * Use one of them.
178 */
179 new = freelist;
180 freelist = freelist->next;
181 } else
182 {
183 /*
184 * No free entries.
185 * Use the "spare" entry.
186 */
187 new = spare;
188 spare = NULL;
189 }
190
191 /*
192 * Fill in the fields of the new entry,
193 * and insert it into the proper place in the list.
194 */
195 new->next = nextp;
196 new->prev = prevp;
197 new->pos = pos;
198 new->line = line;
199
200 nextp->prev = new;
201 prevp->next = new;
202
203 /*
204 * Recalculate gaps for the new entry and the neighboring entries.
205 */
206 calcgap(new);
207 calcgap(nextp);
208 calcgap(prevp);
209
210 if (spare == NULL)
211 {
212 /*
213 * We have used the spare entry.
214 * Scan the list to find the one with the smallest
215 * gap, take it out and make it the spare.
216 * We should never remove the last one, so stop when
217 * we get to p->next == &anchor. This also avoids
218 * looking at the gap of the last one, which is
219 * not computed by calcgap.
220 */
221 mingap = anchor.next->gap;
222 for (p = anchor.next; p->next != &anchor; p = p->next)
223 {
224 if (p->gap <= mingap)
225 {
226 spare = p;
227 mingap = p->gap;
228 }
229 }
230 spare->next->prev = spare->prev;
231 spare->prev->next = spare->next;
232 }
233}
234
235/*
236 * If we get stuck in a long loop trying to figure out the
237 * line number, print a message to tell the user what we're doing.
238 */
bc258617 239static
bfe13c81
KB
240longloopmessage()
241{
242 ierror("Calculating line numbers");
243 /*
244 * Set the lnloop flag here, so if the user interrupts while
245 * we are calculating line numbers, the signal handler will
246 * turn off line numbers (linenums=0).
247 */
248 lnloop = 1;
249}
250
251/*
252 * Find the line number associated with a given position.
253 * Return 0 if we can't figure it out.
254 */
bfe13c81 255find_linenum(pos)
bc258617 256 off_t pos;
bfe13c81
KB
257{
258 register struct linenum *p;
259 register int lno;
260 register int loopcount;
bc258617
KB
261 off_t cpos, back_raw_line(), forw_raw_line();
262 time_t startime, time();
bfe13c81
KB
263
264 if (!linenums)
265 /*
266 * We're not using line numbers.
267 */
268 return (0);
269 if (pos == NULL_POSITION)
270 /*
271 * Caller doesn't know what he's talking about.
272 */
273 return (0);
bc258617 274 if (pos == (off_t)0)
bfe13c81
KB
275 /*
276 * Beginning of file is always line number 1.
277 */
278 return (1);
279
280 /*
281 * Find the entry nearest to the position we want.
282 */
283 for (p = anchor.next; p != &anchor && p->pos < pos; p = p->next)
284 continue;
285 if (p->pos == pos)
286 /* Found it exactly. */
287 return (p->line);
288
289 /*
290 * This is the (possibly) time-consuming part.
291 * We start at the line we just found and start
292 * reading the file forward or backward till we
293 * get to the place we want.
294 *
295 * First decide whether we should go forward from the
296 * previous one or backwards from the next one.
297 * The decision is based on which way involves
298 * traversing fewer bytes in the file.
299 */
300 flush();
bc258617 301 (void)time(&startime);
bfe13c81
KB
302 if (p == &anchor || pos - p->prev->pos < p->pos - pos)
303 {
304 /*
305 * Go forward.
306 */
307 p = p->prev;
308 if (ch_seek(p->pos))
309 return (0);
310 loopcount = 0;
311 for (lno = p->line, cpos = p->pos; cpos < pos; lno++)
312 {
313 /*
314 * Allow a signal to abort this loop.
315 */
316 cpos = forw_raw_line(cpos);
317 if (sigs || cpos == NULL_POSITION)
318 return (0);
bc258617 319 if (loopcount >= 0 && ++loopcount > 100) {
bfe13c81 320 loopcount = 0;
bc258617
KB
321 if (time((time_t *)NULL)
322 >= startime + LONGTIME) {
bfe13c81
KB
323 longloopmessage();
324 loopcount = -1;
325 }
326 }
bfe13c81
KB
327 }
328 lnloop = 0;
329 /*
330 * If the given position is not at the start of a line,
331 * make sure we return the correct line number.
332 */
333 if (cpos > pos)
334 lno--;
335 } else
336 {
337 /*
338 * Go backward.
339 */
340 if (ch_seek(p->pos))
341 return (0);
342 loopcount = 0;
343 for (lno = p->line, cpos = p->pos; cpos > pos; lno--)
344 {
345 /*
346 * Allow a signal to abort this loop.
347 */
348 cpos = back_raw_line(cpos);
349 if (sigs || cpos == NULL_POSITION)
350 return (0);
bc258617 351 if (loopcount >= 0 && ++loopcount > 100) {
bfe13c81 352 loopcount = 0;
bc258617
KB
353 if (time((time_t *)NULL)
354 >= startime + LONGTIME) {
bfe13c81
KB
355 longloopmessage();
356 loopcount = -1;
357 }
358 }
bfe13c81
KB
359 }
360 lnloop = 0;
361 }
362
363 /*
364 * We might as well cache it.
365 */
366 add_lnum(lno, cpos);
367 return (lno);
368}
369
370/*
371 * Return the line number of the "current" line.
372 * The argument "where" tells which line is to be considered
373 * the "current" line (e.g. TOP, BOTTOM, MIDDLE, etc).
374 */
bfe13c81
KB
375currline(where)
376 int where;
377{
bc258617 378 off_t pos, ch_length(), position();
bfe13c81 379
bc258617 380 if ((pos = position(where)) == NULL_POSITION)
bfe13c81 381 pos = ch_length();
bc258617 382 return(find_linenum(pos));
bfe13c81 383}