[BACK]Return to linenum.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / less

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: }