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