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