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

Annotation of src/usr.bin/less/line.c, Revision 1.9

1.1       etheisen    1: /*
1.5       millert     2:  * Copyright (C) 1984-2002  Mark Nudelman
1.1       etheisen    3:  *
1.5       millert     4:  * You may distribute under the terms of either the GNU General Public
                      5:  * License or the Less License, as specified in the README file.
1.1       etheisen    6:  *
1.5       millert     7:  * For more information about less, or for information on how to
                      8:  * contact the author, see the README file.
1.1       etheisen    9:  */
                     10:
                     11:
                     12: /*
                     13:  * Routines to manipulate the "line buffer".
                     14:  * The line buffer holds a line of output as it is being built
                     15:  * in preparation for output to the screen.
                     16:  */
                     17:
                     18: #include "less.h"
                     19:
1.5       millert    20: #define IS_CONT(c)  (((c) & 0xC0) == 0x80)
                     21:
                     22: public char *linebuf = NULL;   /* Buffer which holds the current output line */
                     23: static char *attr = NULL;      /* Extension of linebuf to hold attributes */
                     24: public int size_linebuf = 0;   /* Size of line buffer (and attr buffer) */
                     25:
                     26: public int cshift;             /* Current left-shift of output line buffer */
                     27: public int hshift;             /* Desired left-shift of output line buffer */
                     28: public int tabstops[TABSTOP_MAX] = { 0 }; /* Custom tabstops */
                     29: public int ntabstops = 1;      /* Number of tabstops */
                     30: public int tabdefault = 8;     /* Default repeated tabstops */
1.1       etheisen   31:
                     32: static int curr;               /* Index into linebuf */
                     33: static int column;             /* Printable length, accounting for
                     34:                                   backspaces, etc. */
                     35: static int overstrike;         /* Next char should overstrike previous char */
1.5       millert    36: static int last_overstrike = AT_NORMAL;
1.1       etheisen   37: static int is_null_line;       /* There is no current line */
1.5       millert    38: static int lmargin;            /* Left margin */
                     39: static int hilites;            /* Number of hilites in this line */
1.1       etheisen   40: static char pendc;
                     41: static POSITION pendpos;
1.5       millert    42: static char *end_ansi_chars;
1.1       etheisen   43:
                     44: static int do_append();
                     45:
                     46: extern int bs_mode;
                     47: extern int linenums;
                     48: extern int ctldisp;
                     49: extern int twiddle;
                     50: extern int binattr;
1.5       millert    51: extern int status_col;
1.1       etheisen   52: extern int auto_wrap, ignaw;
                     53: extern int bo_s_width, bo_e_width;
                     54: extern int ul_s_width, ul_e_width;
                     55: extern int bl_s_width, bl_e_width;
                     56: extern int so_s_width, so_e_width;
                     57: extern int sc_width, sc_height;
1.5       millert    58: extern int utf_mode;
                     59: extern POSITION start_attnpos;
                     60: extern POSITION end_attnpos;
                     61:
                     62: /*
                     63:  * Initialize from environment variables.
                     64:  */
                     65:        public void
                     66: init_line()
                     67: {
                     68:        end_ansi_chars = lgetenv("LESSANSIENDCHARS");
                     69:        if (end_ansi_chars == NULL || *end_ansi_chars == '\0')
                     70:                end_ansi_chars = "m";
                     71:        linebuf = (char *) ecalloc(LINEBUF_SIZE, sizeof(char));
                     72:        attr = (char *) ecalloc(LINEBUF_SIZE, sizeof(char));
                     73:        size_linebuf = LINEBUF_SIZE;
                     74: }
                     75:
                     76: /*
                     77:  * Expand the line buffer.
                     78:  */
                     79:        static int
                     80: expand_linebuf()
                     81: {
1.7       millert    82:        int new_size = size_linebuf * 2;
1.9     ! otto       83:        char *new_buf;
        !            84:        char *new_attr;
        !            85:
        !            86:        new_buf = realloc(linebuf, new_size);
        !            87:        if (new_buf == NULL)
        !            88:                return 1;
        !            89:        new_attr = realloc(attr, new_size);
        !            90:        if (new_attr == NULL) {
        !            91:                /* realloc linebuf back to original size */
        !            92:                linebuf = realloc(new_buf, size_linebuf);
        !            93:                if (linebuf == NULL)
        !            94:                        err(1, NULL);
1.5       millert    95:                return 1;
                     96:        }
1.7       millert    97:        memset(new_buf + size_linebuf, 0, new_size - size_linebuf);
                     98:        memset(new_attr + size_linebuf, 0, new_size - size_linebuf);
1.5       millert    99:        linebuf = new_buf;
                    100:        attr = new_attr;
                    101:        size_linebuf = new_size;
                    102:        return 0;
                    103: }
1.1       etheisen  104:
                    105: /*
                    106:  * Rewind the line buffer.
                    107:  */
                    108:        public void
                    109: prewind()
                    110: {
                    111:        curr = 0;
                    112:        column = 0;
                    113:        overstrike = 0;
                    114:        is_null_line = 0;
                    115:        pendc = '\0';
1.5       millert   116:        lmargin = 0;
                    117:        if (status_col)
                    118:                lmargin += 1;
                    119: #if HILITE_SEARCH
                    120:        hilites = 0;
                    121: #endif
1.1       etheisen  122: }
                    123:
                    124: /*
                    125:  * Insert the line number (of the given position) into the line buffer.
                    126:  */
                    127:        public void
                    128: plinenum(pos)
                    129:        POSITION pos;
                    130: {
1.5       millert   131:        register LINENUM linenum = 0;
                    132:        register int i;
                    133:
                    134:        if (linenums == OPT_ONPLUS)
                    135:        {
                    136:                /*
                    137:                 * Get the line number and put it in the current line.
                    138:                 * {{ Note: since find_linenum calls forw_raw_line,
                    139:                 *    it may seek in the input file, requiring the caller
                    140:                 *    of plinenum to re-seek if necessary. }}
                    141:                 * {{ Since forw_raw_line modifies linebuf, we must
                    142:                 *    do this first, before storing anything in linebuf. }}
                    143:                 */
                    144:                linenum = find_linenum(pos);
                    145:        }
1.1       etheisen  146:
                    147:        /*
1.5       millert   148:         * Display a status column if the -J option is set.
1.1       etheisen  149:         */
1.5       millert   150:        if (status_col)
                    151:        {
                    152:                linebuf[curr] = ' ';
                    153:                if (start_attnpos != NULL_POSITION &&
                    154:                    pos >= start_attnpos && pos < end_attnpos)
                    155:                        attr[curr] = AT_STANDOUT;
                    156:                else
                    157:                        attr[curr] = 0;
                    158:                curr++;
                    159:                column++;
                    160:        }
1.1       etheisen  161:        /*
1.5       millert   162:         * Display the line number at the start of each line
                    163:         * if the -N option is set.
1.1       etheisen  164:         */
1.5       millert   165:        if (linenums == OPT_ONPLUS)
                    166:        {
                    167:                char buf[INT_STRLEN_BOUND(pos) + 2];
                    168:                int n;
1.1       etheisen  169:
1.5       millert   170:                linenumtoa(linenum, buf, sizeof(buf));
                    171:                n = strlen(buf);
                    172:                if (n < MIN_LINENUM_WIDTH)
                    173:                        n = MIN_LINENUM_WIDTH;
1.6       millert   174:                snprintf(linebuf+curr, size_linebuf-curr, "%*s ", n, buf);
1.5       millert   175:                n++;  /* One space after the line number. */
                    176:                for (i = 0; i < n; i++)
                    177:                        attr[curr+i] = AT_NORMAL;
                    178:                curr += n;
                    179:                column += n;
                    180:                lmargin += n;
                    181:        }
1.1       etheisen  182:
                    183:        /*
1.5       millert   184:         * Append enough spaces to bring us to the lmargin.
1.1       etheisen  185:         */
1.5       millert   186:        while (column < lmargin)
1.1       etheisen  187:        {
                    188:                linebuf[curr] = ' ';
                    189:                attr[curr++] = AT_NORMAL;
                    190:                column++;
1.5       millert   191:        }
                    192: }
                    193:
                    194: /*
                    195:  * Determine how many characters are required to shift N columns.
                    196:  */
                    197:        static int
                    198: shift_chars(s, len)
                    199:        char *s;
                    200:        int len;
                    201: {
                    202:        char *p = s;
                    203:
                    204:        /*
                    205:         * Each char counts for one column, except ANSI color escape
                    206:         * sequences use no columns since they don't move the cursor.
                    207:         */
                    208:        while (*p != '\0' && len > 0)
                    209:        {
                    210:                if (*p++ != ESC)
                    211:                {
                    212:                        len--;
                    213:                } else
                    214:                {
                    215:                        while (*p != '\0')
                    216:                        {
                    217:                                if (is_ansi_end(*p++))
                    218:                                        break;
                    219:                        }
                    220:                }
                    221:        }
                    222:        return (p - s);
                    223: }
                    224:
                    225: /*
                    226:  * Determine how many characters are required to shift N columns (UTF version).
                    227:  * {{ FIXME: what about color escape sequences in UTF mode? }}
                    228:  */
                    229:        static int
                    230: utf_shift_chars(s, len)
                    231:        char *s;
                    232:        int len;
                    233: {
                    234:        int ulen = 0;
                    235:
                    236:        while (*s != '\0' && len > 0)
                    237:        {
                    238:                if (!IS_CONT(*s))
                    239:                        len--;
                    240:                s++;
                    241:                ulen++;
                    242:        }
                    243:        while (IS_CONT(*s))
                    244:        {
                    245:                s++;
                    246:                ulen++;
                    247:        }
                    248:        return (ulen);
                    249: }
                    250:
                    251: /*
                    252:  * Shift the input line left.
                    253:  * This means discarding N printable chars at the start of the buffer.
                    254:  */
                    255:        static void
                    256: pshift(shift)
                    257:        int shift;
                    258: {
                    259:        int i;
                    260:        int nchars;
                    261:
                    262:        if (shift > column - lmargin)
                    263:                shift = column - lmargin;
                    264:        if (shift > curr - lmargin)
                    265:                shift = curr - lmargin;
                    266:
                    267:        if (utf_mode)
                    268:                nchars = utf_shift_chars(linebuf + lmargin, shift);
                    269:        else
                    270:                nchars = shift_chars(linebuf + lmargin, shift);
                    271:        if (nchars > curr)
                    272:                nchars = curr;
                    273:        for (i = 0;  i < curr - nchars;  i++)
                    274:        {
                    275:                linebuf[lmargin + i] = linebuf[lmargin + i + nchars];
                    276:                attr[lmargin + i] = attr[lmargin + i + nchars];
                    277:        }
                    278:        curr -= nchars;
                    279:        column -= shift;
                    280:        cshift += shift;
1.1       etheisen  281: }
                    282:
                    283: /*
                    284:  * Return the printing width of the start (enter) sequence
                    285:  * for a given character attribute.
                    286:  */
1.5       millert   287:        static int
1.1       etheisen  288: attr_swidth(a)
                    289:        int a;
                    290: {
                    291:        switch (a)
                    292:        {
                    293:        case AT_BOLD:           return (bo_s_width);
                    294:        case AT_UNDERLINE:      return (ul_s_width);
                    295:        case AT_BLINK:          return (bl_s_width);
                    296:        case AT_STANDOUT:       return (so_s_width);
                    297:        }
                    298:        return (0);
                    299: }
                    300:
                    301: /*
                    302:  * Return the printing width of the end (exit) sequence
                    303:  * for a given character attribute.
                    304:  */
1.5       millert   305:        static int
1.1       etheisen  306: attr_ewidth(a)
                    307:        int a;
                    308: {
                    309:        switch (a)
                    310:        {
                    311:        case AT_BOLD:           return (bo_e_width);
                    312:        case AT_UNDERLINE:      return (ul_e_width);
                    313:        case AT_BLINK:          return (bl_e_width);
                    314:        case AT_STANDOUT:       return (so_e_width);
                    315:        }
                    316:        return (0);
                    317: }
                    318:
                    319: /*
                    320:  * Return the printing width of a given character and attribute,
                    321:  * if the character were added to the current position in the line buffer.
                    322:  * Adding a character with a given attribute may cause an enter or exit
                    323:  * attribute sequence to be inserted, so this must be taken into account.
                    324:  */
                    325:        static int
                    326: pwidth(c, a)
                    327:        int c;
                    328:        int a;
                    329: {
1.5       millert   330:        register int w;
                    331:
                    332:        if (utf_mode && IS_CONT(c))
                    333:                return (0);
1.1       etheisen  334:
                    335:        if (c == '\b')
                    336:                /*
                    337:                 * Backspace moves backwards one position.
                    338:                 */
                    339:                return (-1);
                    340:
                    341:        if (control_char(c))
                    342:                /*
                    343:                 * Control characters do unpredicatable things,
                    344:                 * so we don't even try to guess; say it doesn't move.
                    345:                 * This can only happen if the -r flag is in effect.
                    346:                 */
                    347:                return (0);
                    348:
                    349:        /*
                    350:         * Other characters take one space,
                    351:         * plus the width of any attribute enter/exit sequence.
                    352:         */
                    353:        w = 1;
                    354:        if (curr > 0 && attr[curr-1] != a)
                    355:                w += attr_ewidth(attr[curr-1]);
                    356:        if (a && (curr == 0 || attr[curr-1] != a))
                    357:                w += attr_swidth(a);
                    358:        return (w);
                    359: }
                    360:
                    361: /*
                    362:  * Delete the previous character in the line buffer.
                    363:  */
                    364:        static void
                    365: backc()
                    366: {
                    367:        curr--;
                    368:        column -= pwidth(linebuf[curr], attr[curr]);
                    369: }
                    370:
                    371: /*
1.5       millert   372:  * Are we currently within a recognized ANSI escape sequence?
                    373:  */
                    374:        static int
                    375: in_ansi_esc_seq()
                    376: {
                    377:        int i;
                    378:
                    379:        /*
                    380:         * Search backwards for either an ESC (which means we ARE in a seq);
                    381:         * or an end char (which means we're NOT in a seq).
                    382:         */
                    383:        for (i = curr-1;  i >= 0;  i--)
                    384:        {
                    385:                if (linebuf[i] == ESC)
                    386:                        return (1);
                    387:                if (is_ansi_end(linebuf[i]))
                    388:                        return (0);
                    389:        }
                    390:        return (0);
                    391: }
                    392:
                    393: /*
                    394:  * Is a character the end of an ANSI escape sequence?
                    395:  */
                    396:        public int
                    397: is_ansi_end(c)
                    398:        char c;
                    399: {
                    400:        return (strchr(end_ansi_chars, c) != NULL);
                    401: }
                    402:
                    403: /*
1.1       etheisen  404:  * Append a character and attribute to the line buffer.
                    405:  */
1.5       millert   406: #define        STORE_CHAR(c,a,pos) \
                    407:        do { if (store_char((c),(a),(pos))) return (1); else curr++; } while (0)
                    408:
1.1       etheisen  409:        static int
1.5       millert   410: store_char(c, a, pos)
1.1       etheisen  411:        int c;
                    412:        int a;
                    413:        POSITION pos;
                    414: {
1.5       millert   415:        register int w;
1.1       etheisen  416:
1.5       millert   417:        if (a != AT_NORMAL)
                    418:                last_overstrike = a;
1.1       etheisen  419: #if HILITE_SEARCH
                    420:        if (is_hilited(pos, pos+1, 0))
1.5       millert   421:        {
1.1       etheisen  422:                /*
                    423:                 * This character should be highlighted.
                    424:                 * Override the attribute passed in.
                    425:                 */
                    426:                a = AT_STANDOUT;
1.5       millert   427:                hilites++;
                    428:        }
1.1       etheisen  429: #endif
1.5       millert   430:        if (ctldisp == OPT_ONPLUS && in_ansi_esc_seq())
                    431:                w = 0;
                    432:        else
                    433:                w = pwidth(c, a);
                    434:        if (ctldisp != OPT_ON && column + w + attr_ewidth(a) > sc_width)
1.1       etheisen  435:                /*
                    436:                 * Won't fit on screen.
                    437:                 */
                    438:                return (1);
                    439:
1.5       millert   440:        if (curr >= size_linebuf-2)
                    441:        {
1.1       etheisen  442:                /*
                    443:                 * Won't fit in line buffer.
1.5       millert   444:                 * Try to expand it.
1.1       etheisen  445:                 */
1.5       millert   446:                if (expand_linebuf())
                    447:                        return (1);
                    448:        }
1.1       etheisen  449:
                    450:        /*
                    451:         * Special handling for "magic cookie" terminals.
                    452:         * If an attribute enter/exit sequence has a printing width > 0,
                    453:         * and the sequence is adjacent to a space, delete the space.
                    454:         * We just mark the space as invisible, to avoid having too
                    455:         * many spaces deleted.
                    456:         * {{ Note that even if the attribute width is > 1, we
                    457:         *    delete only one space.  It's not worth trying to do more.
                    458:         *    It's hardly worth doing this much. }}
                    459:         */
                    460:        if (curr > 0 && a != AT_NORMAL &&
                    461:                linebuf[curr-1] == ' ' && attr[curr-1] == AT_NORMAL &&
                    462:                attr_swidth(a) > 0)
                    463:        {
                    464:                /*
                    465:                 * We are about to append an enter-attribute sequence
                    466:                 * just after a space.  Delete the space.
                    467:                 */
                    468:                attr[curr-1] = AT_INVIS;
                    469:                column--;
                    470:        } else if (curr > 0 && attr[curr-1] != AT_NORMAL &&
                    471:                attr[curr-1] != AT_INVIS && c == ' ' && a == AT_NORMAL &&
                    472:                attr_ewidth(attr[curr-1]) > 0)
                    473:        {
                    474:                /*
                    475:                 * We are about to append a space just after an
                    476:                 * exit-attribute sequence.  Delete the space.
                    477:                 */
                    478:                a = AT_INVIS;
                    479:                column--;
                    480:        }
                    481:        /* End of magic cookie handling. */
                    482:
                    483:        linebuf[curr] = c;
                    484:        attr[curr] = a;
                    485:        column += w;
                    486:        return (0);
                    487: }
                    488:
                    489: /*
1.5       millert   490:  * Append a tab to the line buffer.
                    491:  * Store spaces to represent the tab.
                    492:  */
                    493: #define        STORE_TAB(a,pos) \
                    494:        do { if (store_tab((a),(pos))) return (1); } while (0)
                    495:
                    496:        static int
                    497: store_tab(attr, pos)
                    498:        int attr;
                    499:        POSITION pos;
                    500: {
                    501:        int to_tab = column + cshift - lmargin;
                    502:        int i;
                    503:
                    504:        if (ntabstops < 2 || to_tab >= tabstops[ntabstops-1])
                    505:                to_tab = tabdefault -
                    506:                     ((to_tab - tabstops[ntabstops-1]) % tabdefault);
                    507:        else
                    508:        {
                    509:                for (i = ntabstops - 2;  i >= 0;  i--)
                    510:                        if (to_tab >= tabstops[i])
                    511:                                break;
                    512:                to_tab = tabstops[i+1] - to_tab;
                    513:        }
                    514:
                    515:        do {
                    516:                STORE_CHAR(' ', attr, pos);
                    517:        } while (--to_tab > 0);
                    518:        return 0;
                    519: }
                    520:
                    521: /*
1.1       etheisen  522:  * Append a character to the line buffer.
                    523:  * Expand tabs into spaces, handle underlining, boldfacing, etc.
                    524:  * Returns 0 if ok, 1 if couldn't fit in buffer.
                    525:  */
                    526:        public int
                    527: pappend(c, pos)
1.5       millert   528:        register int c;
1.1       etheisen  529:        POSITION pos;
                    530: {
1.5       millert   531:        int r;
                    532:
1.1       etheisen  533:        if (pendc)
                    534:        {
                    535:                if (do_append(pendc, pendpos))
                    536:                        /*
                    537:                         * Oops.  We've probably lost the char which
                    538:                         * was in pendc, since caller won't back up.
                    539:                         */
                    540:                        return (1);
                    541:                pendc = '\0';
                    542:        }
                    543:
                    544:        if (c == '\r' && bs_mode == BS_SPECIAL)
                    545:        {
                    546:                /*
                    547:                 * Don't put the CR into the buffer until we see
                    548:                 * the next char.  If the next char is a newline,
                    549:                 * discard the CR.
                    550:                 */
                    551:                pendc = c;
                    552:                pendpos = pos;
                    553:                return (0);
                    554:        }
                    555:
1.5       millert   556:        r = do_append(c, pos);
                    557:        /*
                    558:         * If we need to shift the line, do it.
                    559:         * But wait until we get to at least the middle of the screen,
                    560:         * so shifting it doesn't affect the chars we're currently
                    561:         * pappending.  (Bold & underline can get messed up otherwise.)
                    562:         */
                    563:        if (cshift < hshift && column > sc_width / 2)
                    564:        {
                    565:                linebuf[curr] = '\0';
                    566:                pshift(hshift - cshift);
                    567:        }
                    568:        return (r);
1.1       etheisen  569: }
                    570:
1.5       millert   571: #define IS_UTF8_4BYTE(c) ( ((c) & 0xf8) == 0xf0 )
                    572: #define IS_UTF8_3BYTE(c) ( ((c) & 0xf0) == 0xe0 )
                    573: #define IS_UTF8_2BYTE(c) ( ((c) & 0xe0) == 0xc0 )
                    574: #define IS_UTF8_TRAIL(c) ( ((c) & 0xc0) == 0x80 )
                    575:
1.1       etheisen  576:        static int
                    577: do_append(c, pos)
                    578:        int c;
                    579:        POSITION pos;
                    580: {
1.5       millert   581:        register char *s;
                    582:        register int a;
1.1       etheisen  583:
1.5       millert   584: #define STOREC(c,a) \
                    585:        if ((c) == '\t') STORE_TAB((a),pos); else STORE_CHAR((c),(a),pos)
1.1       etheisen  586:
1.5       millert   587:        if (c == '\b')
                    588:        {
                    589:                switch (bs_mode)
                    590:                {
                    591:                case BS_NORMAL:
                    592:                        STORE_CHAR(c, AT_NORMAL, pos);
                    593:                        break;
                    594:                case BS_CONTROL:
                    595:                        goto do_control_char;
                    596:                case BS_SPECIAL:
                    597:                        if (curr == 0)
                    598:                                break;
                    599:                        backc();
                    600:                        overstrike = 1;
                    601:                        break;
                    602:                }
                    603:        } else if (overstrike)
1.1       etheisen  604:        {
                    605:                /*
                    606:                 * Overstrike the character at the current position
                    607:                 * in the line buffer.  This will cause either
                    608:                 * underline (if a "_" is overstruck),
                    609:                 * bold (if an identical character is overstruck),
                    610:                 * or just deletion of the character in the buffer.
                    611:                 */
1.5       millert   612:                overstrike--;
                    613:                if (utf_mode && IS_UTF8_4BYTE(c) && curr > 2 && (char)c == linebuf[curr-3])
                    614:                {
                    615:                        backc();
                    616:                        backc();
                    617:                        backc();
                    618:                        STORE_CHAR(linebuf[curr], AT_BOLD, pos);
                    619:                        overstrike = 3;
                    620:                } else if (utf_mode && (IS_UTF8_3BYTE(c) || (overstrike==2 && IS_UTF8_TRAIL(c))) && curr > 1 && (char)c == linebuf[curr-2])
                    621:                {
                    622:                        backc();
                    623:                        backc();
                    624:                        STORE_CHAR(linebuf[curr], AT_BOLD, pos);
                    625:                        overstrike = 2;
                    626:                } else if (utf_mode && curr > 0 && (IS_UTF8_2BYTE(c) || (overstrike==1 && IS_UTF8_TRAIL(c))) && (char)c == linebuf[curr-1])
                    627:                {
                    628:                        backc();
                    629:                        STORE_CHAR(linebuf[curr], AT_BOLD, pos);
                    630:                        overstrike = 1;
                    631:                } else if (utf_mode && curr > 0 && IS_UTF8_TRAIL(c) && attr[curr-1] == AT_UNDERLINE)
                    632:                {
                    633:                        STOREC(c, AT_UNDERLINE);
                    634:                } else if ((char)c == linebuf[curr])
                    635:                {
                    636:                        /*
                    637:                         * Overstriking a char with itself means make it bold.
                    638:                         * But overstriking an underscore with itself is
                    639:                         * ambiguous.  It could mean make it bold, or
                    640:                         * it could mean make it underlined.
                    641:                         * Use the previous overstrike to resolve it.
                    642:                         */
                    643:                        if (c == '_' && last_overstrike != AT_NORMAL)
                    644:                                STOREC(c, last_overstrike);
                    645:                        else
                    646:                                STOREC(c, AT_BOLD);
                    647:                } else if (c == '_')
                    648:                {
                    649:                        if (utf_mode)
                    650:                        {
                    651:                                int i;
                    652:                                for (i = 0;  i < 5;  i++)
                    653:                                {
                    654:                                        if (curr <= i || !IS_CONT(linebuf[curr-i]))
                    655:                                                break;
                    656:                                        attr[curr-i-1] = AT_UNDERLINE;
                    657:                                }
                    658:                        }
1.1       etheisen  659:                        STOREC(linebuf[curr], AT_UNDERLINE);
1.5       millert   660:                } else if (linebuf[curr] == '_')
                    661:                {
                    662:                        if (utf_mode)
                    663:                        {
                    664:                                if (IS_UTF8_2BYTE(c))
                    665:                                        overstrike = 1;
                    666:                                else if (IS_UTF8_3BYTE(c))
                    667:                                        overstrike = 2;
                    668:                                else if (IS_UTF8_4BYTE(c))
                    669:                                        overstrike = 3;
                    670:                        }
1.1       etheisen  671:                        STOREC(c, AT_UNDERLINE);
1.5       millert   672:                } else if (control_char(c))
1.1       etheisen  673:                        goto do_control_char;
                    674:                else
                    675:                        STOREC(c, AT_NORMAL);
1.5       millert   676:        } else if (c == '\t')
1.1       etheisen  677:        {
1.5       millert   678:                /*
                    679:                 * Expand a tab into spaces.
                    680:                 */
1.1       etheisen  681:                switch (bs_mode)
                    682:                {
                    683:                case BS_CONTROL:
                    684:                        goto do_control_char;
1.5       millert   685:                case BS_NORMAL:
1.1       etheisen  686:                case BS_SPECIAL:
1.5       millert   687:                        STORE_TAB(AT_NORMAL, pos);
1.1       etheisen  688:                        break;
                    689:                }
                    690:        } else if (control_char(c))
                    691:        {
                    692:        do_control_char:
1.5       millert   693:                if (ctldisp == OPT_ON || (ctldisp == OPT_ONPLUS && c == ESC))
1.1       etheisen  694:                {
                    695:                        /*
                    696:                         * Output as a normal character.
                    697:                         */
1.5       millert   698:                        STORE_CHAR(c, AT_NORMAL, pos);
1.1       etheisen  699:                } else
                    700:                {
                    701:                        /*
                    702:                         * Convert to printable representation.
                    703:                         */
                    704:                        s = prchar(c);
                    705:                        a = binattr;
                    706:
                    707:                        /*
                    708:                         * Make sure we can get the entire representation
                    709:                         * of the character on this line.
                    710:                         */
                    711:                        if (column + (int) strlen(s) +
                    712:                            attr_swidth(a) + attr_ewidth(a) > sc_width)
                    713:                                return (1);
                    714:
                    715:                        for ( ;  *s != 0;  s++)
1.5       millert   716:                                STORE_CHAR(*s, a, pos);
1.1       etheisen  717:                }
                    718:        } else
                    719:        {
                    720:                STOREC(c, AT_NORMAL);
                    721:        }
                    722:
                    723:        return (0);
                    724: }
                    725:
                    726: /*
                    727:  * Terminate the line in the line buffer.
                    728:  */
                    729:        public void
                    730: pdone(endline)
                    731:        int endline;
                    732: {
                    733:        if (pendc && (pendc != '\r' || !endline))
                    734:                /*
                    735:                 * If we had a pending character, put it in the buffer.
                    736:                 * But discard a pending CR if we are at end of line
                    737:                 * (that is, discard the CR in a CR/LF sequence).
                    738:                 */
                    739:                (void) do_append(pendc, pendpos);
                    740:
                    741:        /*
1.5       millert   742:         * Make sure we've shifted the line, if we need to.
                    743:         */
                    744:        if (cshift < hshift)
                    745:                pshift(hshift - cshift);
                    746:
                    747:        /*
1.1       etheisen  748:         * Add a newline if necessary,
                    749:         * and append a '\0' to the end of the line.
                    750:         */
1.5       millert   751:        if (column < sc_width || !auto_wrap || ignaw || ctldisp == OPT_ON)
1.1       etheisen  752:        {
                    753:                linebuf[curr] = '\n';
                    754:                attr[curr] = AT_NORMAL;
                    755:                curr++;
                    756:        }
                    757:        linebuf[curr] = '\0';
                    758:        attr[curr] = AT_NORMAL;
1.5       millert   759:
                    760: #if HILITE_SEARCH
                    761:        if (status_col && hilites > 0)
                    762:        {
                    763:                linebuf[0] = '*';
                    764:                attr[0] = AT_STANDOUT;
                    765:        }
                    766: #endif
                    767:        /*
                    768:         * If we are done with this line, reset the current shift.
                    769:         */
                    770:        if (endline)
                    771:                cshift = 0;
1.1       etheisen  772: }
                    773:
                    774: /*
                    775:  * Get a character from the current line.
                    776:  * Return the character as the function return value,
                    777:  * and the character attribute in *ap.
                    778:  */
                    779:        public int
                    780: gline(i, ap)
1.5       millert   781:        register int i;
                    782:        register int *ap;
1.1       etheisen  783: {
                    784:        char *s;
                    785:
                    786:        if (is_null_line)
                    787:        {
                    788:                /*
                    789:                 * If there is no current line, we pretend the line is
                    790:                 * either "~" or "", depending on the "twiddle" flag.
                    791:                 */
1.5       millert   792:                *ap = AT_BOLD;
1.1       etheisen  793:                s = (twiddle) ? "~\n" : "\n";
                    794:                return (s[i]);
                    795:        }
                    796:
                    797:        *ap = attr[i];
                    798:        return (linebuf[i] & 0377);
                    799: }
                    800:
                    801: /*
                    802:  * Indicate that there is no current line.
                    803:  */
                    804:        public void
                    805: null_line()
                    806: {
                    807:        is_null_line = 1;
1.5       millert   808:        cshift = 0;
1.1       etheisen  809: }
                    810:
                    811: /*
                    812:  * Analogous to forw_line(), but deals with "raw lines":
                    813:  * lines which are not split for screen width.
                    814:  * {{ This is supposed to be more efficient than forw_line(). }}
                    815:  */
                    816:        public POSITION
                    817: forw_raw_line(curr_pos, linep)
                    818:        POSITION curr_pos;
                    819:        char **linep;
                    820: {
1.5       millert   821:        register int n;
                    822:        register int c;
1.1       etheisen  823:        POSITION new_pos;
                    824:
                    825:        if (curr_pos == NULL_POSITION || ch_seek(curr_pos) ||
                    826:                (c = ch_forw_get()) == EOI)
                    827:                return (NULL_POSITION);
                    828:
1.5       millert   829:        n = 0;
1.1       etheisen  830:        for (;;)
                    831:        {
                    832:                if (c == '\n' || c == EOI)
                    833:                {
                    834:                        new_pos = ch_tell();
                    835:                        break;
                    836:                }
1.5       millert   837:                if (n >= size_linebuf-1)
1.1       etheisen  838:                {
1.5       millert   839:                        if (expand_linebuf())
                    840:                        {
                    841:                                /*
                    842:                                 * Overflowed the input buffer.
                    843:                                 * Pretend the line ended here.
                    844:                                 */
                    845:                                new_pos = ch_tell() - 1;
                    846:                                break;
                    847:                        }
1.1       etheisen  848:                }
1.5       millert   849:                linebuf[n++] = c;
1.1       etheisen  850:                c = ch_forw_get();
                    851:        }
1.5       millert   852:        linebuf[n] = '\0';
1.1       etheisen  853:        if (linep != NULL)
                    854:                *linep = linebuf;
                    855:        return (new_pos);
                    856: }
                    857:
                    858: /*
                    859:  * Analogous to back_line(), but deals with "raw lines".
                    860:  * {{ This is supposed to be more efficient than back_line(). }}
                    861:  */
                    862:        public POSITION
                    863: back_raw_line(curr_pos, linep)
                    864:        POSITION curr_pos;
                    865:        char **linep;
                    866: {
1.5       millert   867:        register int n;
                    868:        register int c;
1.1       etheisen  869:        POSITION new_pos;
                    870:
                    871:        if (curr_pos == NULL_POSITION || curr_pos <= ch_zero() ||
                    872:                ch_seek(curr_pos-1))
                    873:                return (NULL_POSITION);
                    874:
1.5       millert   875:        n = size_linebuf;
                    876:        linebuf[--n] = '\0';
1.1       etheisen  877:        for (;;)
                    878:        {
                    879:                c = ch_back_get();
                    880:                if (c == '\n')
                    881:                {
                    882:                        /*
                    883:                         * This is the newline ending the previous line.
                    884:                         * We have hit the beginning of the line.
                    885:                         */
                    886:                        new_pos = ch_tell() + 1;
                    887:                        break;
                    888:                }
                    889:                if (c == EOI)
                    890:                {
                    891:                        /*
                    892:                         * We have hit the beginning of the file.
                    893:                         * This must be the first line in the file.
                    894:                         * This must, of course, be the beginning of the line.
                    895:                         */
                    896:                        new_pos = ch_zero();
                    897:                        break;
                    898:                }
1.5       millert   899:                if (n <= 0)
1.1       etheisen  900:                {
1.5       millert   901:                        int old_size_linebuf = size_linebuf;
                    902:                        if (expand_linebuf())
                    903:                        {
                    904:                                /*
                    905:                                 * Overflowed the input buffer.
                    906:                                 * Pretend the line ended here.
                    907:                                 */
                    908:                                new_pos = ch_tell() + 1;
                    909:                                break;
                    910:                        }
1.1       etheisen  911:                        /*
1.5       millert   912:                         * Shift the data to the end of the new linebuf.
1.1       etheisen  913:                         */
1.5       millert   914:                        n = size_linebuf - old_size_linebuf;
1.8       millert   915:                        memmove(linebuf + n, linebuf, old_size_linebuf);
1.1       etheisen  916:                }
1.5       millert   917:                linebuf[--n] = c;
1.1       etheisen  918:        }
                    919:        if (linep != NULL)
1.5       millert   920:                *linep = &linebuf[n];
1.1       etheisen  921:        return (new_pos);
                    922: }