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