GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/less/less/../linenum.c Lines: 72 129 55.8 %
Date: 2017-11-13 Branches: 47 98 48.0 %

Line Branch Exec Source
1
/*
2
 * Copyright (C) 1984-2012  Mark Nudelman
3
 * Modified for use with illumos by Garrett D'Amore.
4
 * Copyright 2014 Garrett D'Amore <garrett@damore.org>
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, see the README file.
10
 */
11
12
/*
13
 * Code to handle displaying line numbers.
14
 *
15
 * Finding the line number of a given file position is rather tricky.
16
 * We don't want to just start at the beginning of the file and
17
 * count newlines, because that is slow for large files (and also
18
 * wouldn't work if we couldn't get to the start of the file; e.g.
19
 * if input is a long pipe).
20
 *
21
 * So we use the function add_lnum to cache line numbers.
22
 * We try to be very clever and keep only the more interesting
23
 * line numbers when we run out of space in our table.  A line
24
 * number is more interesting than another when it is far from
25
 * other line numbers.   For example, we'd rather keep lines
26
 * 100,200,300 than 100,101,300.  200 is more interesting than
27
 * 101 because 101 can be derived very cheaply from 100, while
28
 * 200 is more expensive to derive from 100.
29
 *
30
 * The function currline() returns the line number of a given
31
 * position in the file.  As a side effect, it calls add_lnum
32
 * to cache the line number.  Therefore currline is occasionally
33
 * called to make sure we cache line numbers often enough.
34
 */
35
36
#include "less.h"
37
38
/*
39
 * Structure to keep track of a line number and the associated file position.
40
 * A doubly-linked circular list of line numbers is kept ordered by line number.
41
 */
42
struct linenum_info {
43
	struct linenum_info *next;	/* Link to next in the list */
44
	struct linenum_info *prev;	/* Line to previous in the list */
45
	off_t pos;			/* File position */
46
	off_t gap;			/* Gap between prev and next */
47
	off_t line;			/* Line number */
48
};
49
/*
50
 * "gap" needs some explanation: the gap of any particular line number
51
 * is the distance between the previous one and the next one in the list.
52
 * ("Distance" means difference in file position.)  In other words, the
53
 * gap of a line number is the gap which would be introduced if this
54
 * line number were deleted.  It is used to decide which one to replace
55
 * when we have a new one to insert and the table is full.
56
 */
57
58
#define	NPOOL	200			/* Size of line number pool */
59
60
#define	LONGTIME	(2)		/* In seconds */
61
62
static struct linenum_info anchor;	/* Anchor of the list */
63
static struct linenum_info *freelist;	/* Anchor of the unused entries */
64
static struct linenum_info pool[NPOOL];	/* The pool itself */
65
static struct linenum_info *spare;	/* We always keep one spare entry */
66
67
extern int linenums;
68
extern volatile sig_atomic_t sigs;
69
extern int sc_height;
70
extern int screen_trashed;
71
72
/*
73
 * Initialize the line number structures.
74
 */
75
void
76
clr_linenum(void)
77
{
78
	struct linenum_info *p;
79
80
	/*
81
	 * Put all the entries on the free list.
82
	 * Leave one for the "spare".
83
	 */
84
3192
	for (p = pool; p < &pool[NPOOL-2]; p++)
85
1584
		p->next = p+1;
86
8
	pool[NPOOL-2].next = NULL;
87
8
	freelist = pool;
88
89
8
	spare = &pool[NPOOL-1];
90
91
	/*
92
	 * Initialize the anchor.
93
	 */
94
8
	anchor.next = anchor.prev = &anchor;
95
8
	anchor.gap = 0;
96
8
	anchor.pos = 0;
97
8
	anchor.line = 1;
98
8
}
99
100
/*
101
 * Calculate the gap for an entry.
102
 */
103
static void
104
calcgap(struct linenum_info *p)
105
{
106
	/*
107
	 * Don't bother to compute a gap for the anchor.
108
	 * Also don't compute a gap for the last one in the list.
109
	 * The gap for that last one should be considered infinite,
110
	 * but we never look at it anyway.
111
	 */
112

193
	if (p == &anchor || p->next == &anchor)
113
		return;
114
30
	p->gap = p->next->pos - p->prev->pos;
115
102
}
116
117
/*
118
 * Add a new line number to the cache.
119
 * The specified position (pos) should be the file position of the
120
 * FIRST character in the specified line.
121
 */
122
void
123
add_lnum(off_t linenum, off_t pos)
124
{
125
	struct linenum_info *p;
126
	struct linenum_info *new;
127
	struct linenum_info *nextp;
128
	struct linenum_info *prevp;
129
	off_t mingap;
130
131
	/*
132
	 * Find the proper place in the list for the new one.
133
	 * The entries are sorted by position.
134
	 */
135

289
	for (p = anchor.next; p != &anchor && p->pos < pos; p = p->next)
136
46
		if (p->line == linenum)
137
			/* We already have this one. */
138
			return;
139
	nextp = p;
140
24
	prevp = p->prev;
141
142
24
	if (freelist != NULL) {
143
		/*
144
		 * We still have free (unused) entries.
145
		 * Use one of them.
146
		 */
147
		new = freelist;
148
24
		freelist = freelist->next;
149
24
	} else {
150
		/*
151
		 * No free entries.
152
		 * Use the "spare" entry.
153
		 */
154
		new = spare;
155
		spare = NULL;
156
	}
157
158
	/*
159
	 * Fill in the fields of the new entry,
160
	 * and insert it into the proper place in the list.
161
	 */
162
24
	new->next = nextp;
163
24
	new->prev = prevp;
164
24
	new->pos = pos;
165
24
	new->line = linenum;
166
167
24
	nextp->prev = new;
168
24
	prevp->next = new;
169
170
	/*
171
	 * Recalculate gaps for the new entry and the neighboring entries.
172
	 */
173
24
	calcgap(new);
174
24
	calcgap(nextp);
175
24
	calcgap(prevp);
176
177
24
	if (spare == NULL) {
178
		/*
179
		 * We have used the spare entry.
180
		 * Scan the list to find the one with the smallest
181
		 * gap, take it out and make it the spare.
182
		 * We should never remove the last one, so stop when
183
		 * we get to p->next == &anchor.  This also avoids
184
		 * looking at the gap of the last one, which is
185
		 * not computed by calcgap.
186
		 */
187
		mingap = anchor.next->gap;
188
		for (p = anchor.next; p->next != &anchor; p = p->next) {
189
			if (p->gap <= mingap) {
190
				spare = p;
191
				mingap = p->gap;
192
			}
193
		}
194
		spare->next->prev = spare->prev;
195
		spare->prev->next = spare->next;
196
	}
197
48
}
198
199
static int loopcount;
200
static time_t startime;
201
202
static void
203
longish(void)
204
{
205

2424
	if (loopcount >= 0 && ++loopcount > 100) {
206
1
		loopcount = 0;
207
1
		if (time(NULL) >= startime + LONGTIME) {
208
			ierror("Calculating line numbers", NULL);
209
			loopcount = -1;
210
		}
211
	}
212
808
}
213
214
/*
215
 * Turn off line numbers because the user has interrupted
216
 * a lengthy line number calculation.
217
 */
218
static void
219
abort_long(void)
220
{
221
	if (linenums == OPT_ONPLUS)
222
		/*
223
		 * We were displaying line numbers, so need to repaint.
224
		 */
225
		screen_trashed = 1;
226
	linenums = 0;
227
	error("Line numbers turned off", NULL);
228
}
229
230
/*
231
 * Find the line number associated with a given position.
232
 * Return 0 if we can't figure it out.
233
 */
234
off_t
235
find_linenum(off_t pos)
236
{
237
	struct linenum_info *p;
238
	off_t linenum;
239
	off_t cpos;
240
241
664
	if (!linenums)
242
		/*
243
		 * We're not using line numbers.
244
		 */
245
		return (0);
246
332
	if (pos == -1)
247
		/*
248
		 * Caller doesn't know what he's talking about.
249
		 */
250
		return (0);
251
332
	if (pos <= ch_zero())
252
		/*
253
		 * Beginning of file is always line number 1.
254
		 */
255
9
		return (1);
256
257
	/*
258
	 * Find the entry nearest to the position we want.
259
	 */
260

6462
	for (p = anchor.next; p != &anchor && p->pos < pos; p = p->next)
261
		continue;
262
323
	if (p->pos == pos)
263
		/* Found it exactly. */
264
302
		return (p->line);
265
266
	/*
267
	 * This is the (possibly) time-consuming part.
268
	 * We start at the line we just found and start
269
	 * reading the file forward or backward till we
270
	 * get to the place we want.
271
	 *
272
	 * First decide whether we should go forward from the
273
	 * previous one or backwards from the next one.
274
	 * The decision is based on which way involves
275
	 * traversing fewer bytes in the file.
276
	 */
277
21
	startime = time(NULL);
278

30
	if (p == &anchor || pos - p->prev->pos < p->pos - pos) {
279
		/*
280
		 * Go forward.
281
		 */
282
17
		p = p->prev;
283
17
		if (ch_seek(p->pos))
284
			return (0);
285
17
		loopcount = 0;
286
1426
		for (linenum = p->line, cpos = p->pos; cpos < pos; linenum++) {
287
			/*
288
			 * Allow a signal to abort this loop.
289
			 */
290
696
			cpos = forw_raw_line(cpos, NULL, NULL);
291
696
			if (ABORT_SIGS()) {
292
				abort_long();
293
				return (0);
294
			}
295
696
			if (cpos == -1)
296
				return (0);
297
696
			longish();
298
		}
299
		/*
300
		 * We might as well cache it.
301
		 */
302
17
		add_lnum(linenum, cpos);
303
		/*
304
		 * If the given position is not at the start of a line,
305
		 * make sure we return the correct line number.
306
		 */
307
17
		if (cpos > pos)
308
			linenum--;
309
	} else {
310
		/*
311
		 * Go backward.
312
		 */
313
4
		if (ch_seek(p->pos))
314
			return (0);
315
4
		loopcount = 0;
316
232
		for (linenum = p->line, cpos = p->pos; cpos > pos; linenum--) {
317
			/*
318
			 * Allow a signal to abort this loop.
319
			 */
320
112
			cpos = back_raw_line(cpos, NULL, NULL);
321
112
			if (ABORT_SIGS()) {
322
				abort_long();
323
				return (0);
324
			}
325
112
			if (cpos == -1)
326
				return (0);
327
112
			longish();
328
		}
329
		/*
330
		 * We might as well cache it.
331
		 */
332
4
		add_lnum(linenum, cpos);
333
	}
334
335
21
	return (linenum);
336
332
}
337
338
/*
339
 * Find the position of a given line number.
340
 * Return -1 if we can't figure it out.
341
 */
342
off_t
343
find_pos(off_t linenum)
344
{
345
	struct linenum_info *p;
346
	off_t cpos;
347
	off_t clinenum;
348
349
	if (linenum <= 1)
350
		/*
351
		 * Line number 1 is beginning of file.
352
		 */
353
		return (ch_zero());
354
355
	/*
356
	 * Find the entry nearest to the line number we want.
357
	 */
358
	for (p = anchor.next; p != &anchor && p->line < linenum; p = p->next)
359
		continue;
360
	if (p->line == linenum)
361
		/* Found it exactly. */
362
		return (p->pos);
363
364
	if (p == &anchor || linenum - p->prev->line < p->line - linenum) {
365
		/*
366
		 * Go forward.
367
		 */
368
		p = p->prev;
369
		if (ch_seek(p->pos))
370
			return (-1);
371
		for (clinenum = p->line, cpos = p->pos;
372
		    clinenum < linenum;
373
		    clinenum++) {
374
			/*
375
			 * Allow a signal to abort this loop.
376
			 */
377
			cpos = forw_raw_line(cpos, NULL, NULL);
378
			if (ABORT_SIGS())
379
				return (-1);
380
			if (cpos == -1)
381
				return (-1);
382
		}
383
	} else {
384
		/*
385
		 * Go backward.
386
		 */
387
		if (ch_seek(p->pos))
388
			return (-1);
389
		for (clinenum = p->line, cpos = p->pos;
390
		    clinenum > linenum;
391
		    clinenum--) {
392
			/*
393
			 * Allow a signal to abort this loop.
394
			 */
395
			cpos = back_raw_line(cpos, (char **)NULL, (int *)NULL);
396
			if (ABORT_SIGS())
397
				return (-1);
398
			if (cpos == -1)
399
				return (-1);
400
		}
401
	}
402
	/*
403
	 * We might as well cache it.
404
	 */
405
	add_lnum(clinenum, cpos);
406
	return (cpos);
407
}
408
409
/*
410
 * Return the line number of the "current" line.
411
 * The argument "where" tells which line is to be considered
412
 * the "current" line (e.g. TOP, BOTTOM, MIDDLE, etc).
413
 */
414
off_t
415
currline(int where)
416
{
417
	off_t pos;
418
	off_t len;
419
	off_t linenum;
420
421
70
	pos = position(where);
422
35
	len = ch_length();
423

70
	while (pos == -1 && where >= 0 && where < sc_height)
424
		pos = position(++where);
425
35
	if (pos == -1)
426
2
		pos = len;
427
35
	linenum = find_linenum(pos);
428
35
	if (pos == len)
429
2
		linenum--;
430
35
	return (linenum);
431
}