[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.14

1.1       etheisen    1: /*
1.12      shadchin    2:  * Copyright (C) 1984-2012  Mark Nudelman
1.14    ! nicm        3:  * Modified for use with illumos by Garrett D'Amore.
        !             4:  * Copyright 2014 Garrett D'Amore <garrett@damore.org>
1.1       etheisen    5:  *
1.5       millert     6:  * You may distribute under the terms of either the GNU General Public
                      7:  * License or the Less License, as specified in the README file.
1.1       etheisen    8:  *
1.12      shadchin    9:  * For more information, see the README file.
1.13      nicm       10:  */
1.1       etheisen   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"
1.10      shadchin   19: #include "charset.h"
1.1       etheisen   20:
1.10      shadchin   21: static char *linebuf = NULL;   /* Buffer which holds the current output line */
1.5       millert    22: static char *attr = NULL;      /* Extension of linebuf to hold attributes */
1.13      nicm       23: int size_linebuf = 0;          /* Size of line buffer (and attr buffer) */
1.5       millert    24:
1.10      shadchin   25: static int cshift;             /* Current left-shift of output line buffer */
1.13      nicm       26: int hshift;                    /* Desired left-shift of output line buffer */
                     27: int tabstops[TABSTOP_MAX] = { 0 }; /* Custom tabstops */
                     28: int ntabstops = 1;             /* Number of tabstops */
                     29: int tabdefault = 8;            /* Default repeated tabstops */
                     30: off_t highest_hilite;          /* Pos of last hilite in file found so far */
1.1       etheisen   31:
                     32: static int curr;               /* Index into linebuf */
1.13      nicm       33: static int column;     /* Printable length, accounting for backspaces, etc. */
1.1       etheisen   34: static int overstrike;         /* Next char should overstrike previous char */
1.5       millert    35: static int last_overstrike = AT_NORMAL;
1.1       etheisen   36: static int is_null_line;       /* There is no current line */
1.5       millert    37: static int lmargin;            /* Left margin */
1.1       etheisen   38: static char pendc;
1.13      nicm       39: static off_t pendpos;
1.5       millert    40: static char *end_ansi_chars;
1.10      shadchin   41: static char *mid_ansi_chars;
1.1       etheisen   42:
1.13      nicm       43: static int attr_swidth(int);
                     44: static int attr_ewidth(int);
                     45: static int do_append(LWCHAR, char *, off_t);
1.1       etheisen   46:
1.11      millert    47: extern volatile sig_atomic_t sigs;
1.1       etheisen   48: extern int bs_mode;
                     49: extern int linenums;
                     50: extern int ctldisp;
                     51: extern int twiddle;
                     52: extern int binattr;
1.5       millert    53: extern int status_col;
1.1       etheisen   54: extern int auto_wrap, ignaw;
                     55: extern int bo_s_width, bo_e_width;
                     56: extern int ul_s_width, ul_e_width;
                     57: extern int bl_s_width, bl_e_width;
                     58: extern int so_s_width, so_e_width;
                     59: extern int sc_width, sc_height;
1.5       millert    60: extern int utf_mode;
1.13      nicm       61: extern off_t start_attnpos;
                     62: extern off_t end_attnpos;
1.5       millert    63:
1.10      shadchin   64: static char mbc_buf[MAX_UTF_CHAR_LEN];
                     65: static int mbc_buf_len = 0;
                     66: static int mbc_buf_index = 0;
1.13      nicm       67: static off_t mbc_pos;
1.10      shadchin   68:
1.5       millert    69: /*
                     70:  * Initialize from environment variables.
                     71:  */
1.13      nicm       72: void
                     73: init_line(void)
1.5       millert    74: {
                     75:        end_ansi_chars = lgetenv("LESSANSIENDCHARS");
                     76:        if (end_ansi_chars == NULL || *end_ansi_chars == '\0')
                     77:                end_ansi_chars = "m";
1.10      shadchin   78:
                     79:        mid_ansi_chars = lgetenv("LESSANSIMIDCHARS");
                     80:        if (mid_ansi_chars == NULL || *mid_ansi_chars == '\0')
                     81:                mid_ansi_chars = "0123456789;[?!\"'#%()*+ ";
                     82:
1.13      nicm       83:        linebuf = ecalloc(LINEBUF_SIZE, sizeof (char));
                     84:        attr = ecalloc(LINEBUF_SIZE, sizeof (char));
1.5       millert    85:        size_linebuf = LINEBUF_SIZE;
                     86: }
                     87:
                     88: /*
                     89:  * Expand the line buffer.
                     90:  */
1.13      nicm       91: static int
                     92: expand_linebuf(void)
1.5       millert    93: {
1.10      shadchin   94:        /* Double the size of the line buffer. */
1.7       millert    95:        int new_size = size_linebuf * 2;
1.10      shadchin   96:
                     97:        /* Just realloc to expand the buffer, if we can. */
1.13      nicm       98:        char *new_buf = realloc(linebuf, new_size);
                     99:        char *new_attr = realloc(attr, new_size);
                    100:        if (new_buf == NULL || new_attr == NULL) {
                    101:                if (new_attr != NULL)
                    102:                        free(new_attr);
                    103:                if (new_buf != NULL)
                    104:                        free(new_buf);
                    105:                return (1);
1.5       millert   106:        }
                    107:        linebuf = new_buf;
                    108:        attr = new_attr;
                    109:        size_linebuf = new_size;
1.13      nicm      110:        return (0);
1.5       millert   111: }
1.1       etheisen  112:
                    113: /*
1.10      shadchin  114:  * Is a character ASCII?
                    115:  */
1.13      nicm      116: int
                    117: is_ascii_char(LWCHAR ch)
1.10      shadchin  118: {
                    119:        return (ch <= 0x7F);
                    120: }
                    121:
                    122: /*
1.1       etheisen  123:  * Rewind the line buffer.
                    124:  */
1.13      nicm      125: void
                    126: prewind(void)
1.1       etheisen  127: {
                    128:        curr = 0;
                    129:        column = 0;
1.10      shadchin  130:        cshift = 0;
1.1       etheisen  131:        overstrike = 0;
1.10      shadchin  132:        last_overstrike = AT_NORMAL;
                    133:        mbc_buf_len = 0;
1.1       etheisen  134:        is_null_line = 0;
                    135:        pendc = '\0';
1.5       millert   136:        lmargin = 0;
                    137:        if (status_col)
                    138:                lmargin += 1;
1.1       etheisen  139: }
                    140:
                    141: /*
                    142:  * Insert the line number (of the given position) into the line buffer.
                    143:  */
1.13      nicm      144: void
                    145: plinenum(off_t pos)
1.1       etheisen  146: {
1.13      nicm      147:        LINENUM linenum = 0;
                    148:        int i;
1.5       millert   149:
1.13      nicm      150:        if (linenums == OPT_ONPLUS) {
1.5       millert   151:                /*
                    152:                 * Get the line number and put it in the current line.
                    153:                 * {{ Note: since find_linenum calls forw_raw_line,
1.13      nicm      154:                 *    it may seek in the input file, requiring the caller
1.5       millert   155:                 *    of plinenum to re-seek if necessary. }}
                    156:                 * {{ Since forw_raw_line modifies linebuf, we must
                    157:                 *    do this first, before storing anything in linebuf. }}
                    158:                 */
                    159:                linenum = find_linenum(pos);
                    160:        }
1.1       etheisen  161:
                    162:        /*
1.5       millert   163:         * Display a status column if the -J option is set.
1.1       etheisen  164:         */
1.13      nicm      165:        if (status_col) {
1.5       millert   166:                linebuf[curr] = ' ';
1.13      nicm      167:                if (start_attnpos != -1 &&
1.5       millert   168:                    pos >= start_attnpos && pos < end_attnpos)
1.10      shadchin  169:                        attr[curr] = AT_NORMAL|AT_HILITE;
1.5       millert   170:                else
1.10      shadchin  171:                        attr[curr] = AT_NORMAL;
1.5       millert   172:                curr++;
                    173:                column++;
                    174:        }
1.1       etheisen  175:        /*
1.5       millert   176:         * Display the line number at the start of each line
                    177:         * if the -N option is set.
1.1       etheisen  178:         */
1.13      nicm      179:        if (linenums == OPT_ONPLUS) {
1.5       millert   180:                char buf[INT_STRLEN_BOUND(pos) + 2];
                    181:                int n;
1.1       etheisen  182:
1.13      nicm      183:                linenumtoa(linenum, buf, sizeof (buf));
1.5       millert   184:                n = strlen(buf);
                    185:                if (n < MIN_LINENUM_WIDTH)
                    186:                        n = MIN_LINENUM_WIDTH;
1.6       millert   187:                snprintf(linebuf+curr, size_linebuf-curr, "%*s ", n, buf);
1.5       millert   188:                n++;  /* One space after the line number. */
                    189:                for (i = 0; i < n; i++)
                    190:                        attr[curr+i] = AT_NORMAL;
                    191:                curr += n;
                    192:                column += n;
                    193:                lmargin += n;
                    194:        }
1.1       etheisen  195:
                    196:        /*
1.5       millert   197:         * Append enough spaces to bring us to the lmargin.
1.1       etheisen  198:         */
1.13      nicm      199:        while (column < lmargin) {
1.1       etheisen  200:                linebuf[curr] = ' ';
                    201:                attr[curr++] = AT_NORMAL;
                    202:                column++;
1.5       millert   203:        }
                    204: }
                    205:
                    206: /*
1.10      shadchin  207:  * Shift the input line left.
                    208:  * This means discarding N printable chars at the start of the buffer.
1.5       millert   209:  */
1.13      nicm      210: static void
                    211: pshift(int shift)
1.10      shadchin  212: {
                    213:        LWCHAR prev_ch = 0;
                    214:        unsigned char c;
                    215:        int shifted = 0;
                    216:        int to;
                    217:        int from;
1.5       millert   218:        int len;
1.10      shadchin  219:        int width;
                    220:        int prev_attr;
                    221:        int next_attr;
                    222:
                    223:        if (shift > column - lmargin)
                    224:                shift = column - lmargin;
                    225:        if (shift > curr - lmargin)
                    226:                shift = curr - lmargin;
1.5       millert   227:
1.10      shadchin  228:        to = from = lmargin;
1.5       millert   229:        /*
1.10      shadchin  230:         * We keep on going when shifted == shift
                    231:         * to get all combining chars.
1.5       millert   232:         */
1.13      nicm      233:        while (shifted <= shift && from < curr) {
1.10      shadchin  234:                c = linebuf[from];
1.13      nicm      235:                if (ctldisp == OPT_ONPLUS && IS_CSI_START(c)) {
1.10      shadchin  236:                        /* Keep cumulative effect.  */
                    237:                        linebuf[to] = c;
                    238:                        attr[to++] = attr[from++];
1.13      nicm      239:                        while (from < curr && linebuf[from]) {
1.10      shadchin  240:                                linebuf[to] = linebuf[from];
                    241:                                attr[to++] = attr[from];
                    242:                                if (!is_ansi_middle(linebuf[from++]))
                    243:                                        break;
1.13      nicm      244:                        }
1.10      shadchin  245:                        continue;
                    246:                }
                    247:
                    248:                width = 0;
                    249:
1.13      nicm      250:                if (!IS_ASCII_OCTET(c) && utf_mode) {
1.10      shadchin  251:                        /* Assumes well-formedness validation already done.  */
                    252:                        LWCHAR ch;
                    253:
                    254:                        len = utf_len(c);
                    255:                        if (from + len > curr)
                    256:                                break;
                    257:                        ch = get_wchar(linebuf + from);
1.13      nicm      258:                        if (!is_composing_char(ch) &&
                    259:                            !is_combining_char(prev_ch, ch))
1.10      shadchin  260:                                width = is_wide_char(ch) ? 2 : 1;
                    261:                        prev_ch = ch;
1.13      nicm      262:                } else {
1.10      shadchin  263:                        len = 1;
                    264:                        if (c == '\b')
                    265:                                /* XXX - Incorrect if several '\b' in a row.  */
1.13      nicm      266:                                width = (utf_mode && is_wide_char(prev_ch)) ?
                    267:                                    -2 : -1;
1.10      shadchin  268:                        else if (!control_char(c))
                    269:                                width = 1;
                    270:                        prev_ch = 0;
                    271:                }
                    272:
                    273:                if (width == 2 && shift - shifted == 1) {
                    274:                        /* Should never happen when called by pshift_all().  */
                    275:                        attr[to] = attr[from];
                    276:                        /*
                    277:                         * Assume a wide_char will never be the first half of a
                    278:                         * combining_char pair, so reset prev_ch in case we're
                    279:                         * followed by a '\b'.
                    280:                         */
                    281:                        prev_ch = linebuf[to++] = ' ';
                    282:                        from += len;
                    283:                        shifted++;
                    284:                        continue;
                    285:                }
                    286:
                    287:                /* Adjust width for magic cookies. */
                    288:                prev_attr = (to > 0) ? attr[to-1] : AT_NORMAL;
                    289:                next_attr = (from + len < curr) ? attr[from + len] : prev_attr;
1.13      nicm      290:                if (!is_at_equiv(attr[from], prev_attr) &&
                    291:                    !is_at_equiv(attr[from], next_attr)) {
1.10      shadchin  292:                        width += attr_swidth(attr[from]);
                    293:                        if (from + len < curr)
                    294:                                width += attr_ewidth(attr[from]);
1.13      nicm      295:                        if (is_at_equiv(prev_attr, next_attr)) {
1.10      shadchin  296:                                width += attr_ewidth(prev_attr);
                    297:                                if (from + len < curr)
                    298:                                        width += attr_swidth(next_attr);
1.5       millert   299:                        }
                    300:                }
                    301:
1.10      shadchin  302:                if (shift - shifted < width)
                    303:                        break;
                    304:                from += len;
                    305:                shifted += width;
                    306:                if (shifted < 0)
                    307:                        shifted = 0;
                    308:        }
1.13      nicm      309:        while (from < curr) {
1.10      shadchin  310:                linebuf[to] = linebuf[from];
                    311:                attr[to++] = attr[from++];
                    312:        }
                    313:        curr = to;
                    314:        column -= shifted;
                    315:        cshift += shifted;
1.5       millert   316: }
                    317:
                    318: /*
1.10      shadchin  319:  *
1.5       millert   320:  */
1.13      nicm      321: void
                    322: pshift_all(void)
1.5       millert   323: {
1.10      shadchin  324:        pshift(column);
1.1       etheisen  325: }
                    326:
                    327: /*
                    328:  * Return the printing width of the start (enter) sequence
                    329:  * for a given character attribute.
                    330:  */
1.13      nicm      331: static int
                    332: attr_swidth(int a)
1.1       etheisen  333: {
1.10      shadchin  334:        int w = 0;
                    335:
                    336:        a = apply_at_specials(a);
                    337:
                    338:        if (a & AT_UNDERLINE)
                    339:                w += ul_s_width;
                    340:        if (a & AT_BOLD)
                    341:                w += bo_s_width;
                    342:        if (a & AT_BLINK)
                    343:                w += bl_s_width;
                    344:        if (a & AT_STANDOUT)
                    345:                w += so_s_width;
                    346:
1.13      nicm      347:        return (w);
1.1       etheisen  348: }
                    349:
                    350: /*
                    351:  * Return the printing width of the end (exit) sequence
                    352:  * for a given character attribute.
                    353:  */
1.13      nicm      354: static int
                    355: attr_ewidth(int a)
1.1       etheisen  356: {
1.10      shadchin  357:        int w = 0;
                    358:
                    359:        a = apply_at_specials(a);
                    360:
                    361:        if (a & AT_UNDERLINE)
                    362:                w += ul_e_width;
                    363:        if (a & AT_BOLD)
                    364:                w += bo_e_width;
                    365:        if (a & AT_BLINK)
                    366:                w += bl_e_width;
                    367:        if (a & AT_STANDOUT)
                    368:                w += so_e_width;
                    369:
1.13      nicm      370:        return (w);
1.1       etheisen  371: }
                    372:
                    373: /*
                    374:  * Return the printing width of a given character and attribute,
                    375:  * if the character were added to the current position in the line buffer.
                    376:  * Adding a character with a given attribute may cause an enter or exit
                    377:  * attribute sequence to be inserted, so this must be taken into account.
                    378:  */
1.13      nicm      379: static int
                    380: pwidth(LWCHAR ch, int a, LWCHAR prev_ch)
1.1       etheisen  381: {
1.10      shadchin  382:        int w;
1.1       etheisen  383:
1.10      shadchin  384:        if (ch == '\b')
1.1       etheisen  385:                /*
1.10      shadchin  386:                 * Backspace moves backwards one or two positions.
                    387:                 * XXX - Incorrect if several '\b' in a row.
1.1       etheisen  388:                 */
1.13      nicm      389:                return ((utf_mode && is_wide_char(prev_ch)) ? -2 : -1);
                    390:
                    391:        if (!utf_mode || is_ascii_char(ch)) {
                    392:                if (control_char((char)ch)) {
1.10      shadchin  393:                        /*
                    394:                         * Control characters do unpredictable things,
                    395:                         * so we don't even try to guess; say it doesn't move.
                    396:                         * This can only happen if the -r flag is in effect.
                    397:                         */
                    398:                        return (0);
                    399:                }
1.13      nicm      400:        } else {
                    401:                if (is_composing_char(ch) || is_combining_char(prev_ch, ch)) {
1.10      shadchin  402:                        /*
                    403:                         * Composing and combining chars take up no space.
                    404:                         *
                    405:                         * Some terminals, upon failure to compose a
                    406:                         * composing character with the character(s) that
                    407:                         * precede(s) it will actually take up one column
                    408:                         * for the composing character; there isn't much
                    409:                         * we could do short of testing the (complex)
                    410:                         * composition process ourselves and printing
                    411:                         * a binary representation when it fails.
                    412:                         */
                    413:                        return (0);
                    414:                }
                    415:        }
1.1       etheisen  416:
                    417:        /*
1.10      shadchin  418:         * Other characters take one or two columns,
1.1       etheisen  419:         * plus the width of any attribute enter/exit sequence.
                    420:         */
                    421:        w = 1;
1.10      shadchin  422:        if (is_wide_char(ch))
                    423:                w++;
                    424:        if (curr > 0 && !is_at_equiv(attr[curr-1], a))
1.1       etheisen  425:                w += attr_ewidth(attr[curr-1]);
1.10      shadchin  426:        if ((apply_at_specials(a) != AT_NORMAL) &&
                    427:            (curr == 0 || !is_at_equiv(attr[curr-1], a)))
1.1       etheisen  428:                w += attr_swidth(a);
                    429:        return (w);
                    430: }
                    431:
                    432: /*
1.10      shadchin  433:  * Delete to the previous base character in the line buffer.
                    434:  * Return 1 if one is found.
1.1       etheisen  435:  */
1.13      nicm      436: static int
                    437: backc(void)
1.1       etheisen  438: {
1.10      shadchin  439:        LWCHAR prev_ch;
                    440:        char *p = linebuf + curr;
                    441:        LWCHAR ch = step_char(&p, -1, linebuf + lmargin);
                    442:        int width;
                    443:
                    444:        /* This assumes that there is no '\b' in linebuf.  */
1.13      nicm      445:        while (curr > lmargin && column > lmargin &&
                    446:            (!(attr[curr - 1] & (AT_ANSI|AT_BINARY)))) {
1.10      shadchin  447:                curr = p - linebuf;
                    448:                prev_ch = step_char(&p, -1, linebuf + lmargin);
                    449:                width = pwidth(ch, attr[curr], prev_ch);
                    450:                column -= width;
                    451:                if (width > 0)
1.13      nicm      452:                        return (1);
1.10      shadchin  453:                ch = prev_ch;
                    454:        }
                    455:
1.13      nicm      456:        return (0);
1.1       etheisen  457: }
                    458:
                    459: /*
1.5       millert   460:  * Are we currently within a recognized ANSI escape sequence?
                    461:  */
1.13      nicm      462: static int
                    463: in_ansi_esc_seq(void)
1.5       millert   464: {
1.10      shadchin  465:        char *p;
1.5       millert   466:
                    467:        /*
                    468:         * Search backwards for either an ESC (which means we ARE in a seq);
                    469:         * or an end char (which means we're NOT in a seq).
                    470:         */
1.13      nicm      471:        for (p = &linebuf[curr];  p > linebuf; ) {
1.10      shadchin  472:                LWCHAR ch = step_char(&p, -1, linebuf);
                    473:                if (IS_CSI_START(ch))
1.5       millert   474:                        return (1);
1.10      shadchin  475:                if (!is_ansi_middle(ch))
1.5       millert   476:                        return (0);
                    477:        }
                    478:        return (0);
                    479: }
                    480:
                    481: /*
                    482:  * Is a character the end of an ANSI escape sequence?
                    483:  */
1.13      nicm      484: int
                    485: is_ansi_end(LWCHAR ch)
1.10      shadchin  486: {
                    487:        if (!is_ascii_char(ch))
                    488:                return (0);
1.13      nicm      489:        return (strchr(end_ansi_chars, (char)ch) != NULL);
1.10      shadchin  490: }
                    491:
                    492: /*
                    493:  *
                    494:  */
1.13      nicm      495: int
                    496: is_ansi_middle(LWCHAR ch)
1.5       millert   497: {
1.10      shadchin  498:        if (!is_ascii_char(ch))
                    499:                return (0);
                    500:        if (is_ansi_end(ch))
                    501:                return (0);
1.13      nicm      502:        return (strchr(mid_ansi_chars, (char)ch) != NULL);
1.5       millert   503: }
                    504:
                    505: /*
1.1       etheisen  506:  * Append a character and attribute to the line buffer.
                    507:  */
1.13      nicm      508: #define        STORE_CHAR(ch, a, rep, pos)                             \
                    509:                if (store_char((ch), (a), (rep), (pos)))        \
                    510:                        return (1)
                    511:
                    512: static int
                    513: store_char(LWCHAR ch, char a, char *rep, off_t pos)
1.1       etheisen  514: {
1.10      shadchin  515:        int w;
                    516:        int replen;
                    517:        char cs;
1.13      nicm      518:        int matches;
1.10      shadchin  519:
                    520:        w = (a & (AT_UNDERLINE|AT_BOLD));       /* Pre-use w.  */
                    521:        if (w != AT_NORMAL)
                    522:                last_overstrike = w;
1.1       etheisen  523:
1.13      nicm      524:        if (is_hilited(pos, pos+1, 0, &matches)) {
                    525:                /*
                    526:                 * This character should be highlighted.
                    527:                 * Override the attribute passed in.
                    528:                 */
                    529:                if (a != AT_ANSI) {
                    530:                        if (highest_hilite != -1 && pos > highest_hilite)
                    531:                                highest_hilite = pos;
                    532:                        a |= AT_HILITE;
1.10      shadchin  533:                }
1.5       millert   534:        }
1.10      shadchin  535:
1.13      nicm      536:        if (ctldisp == OPT_ONPLUS && in_ansi_esc_seq()) {
1.10      shadchin  537:                if (!is_ansi_end(ch) && !is_ansi_middle(ch)) {
                    538:                        /* Remove whole unrecognized sequence.  */
                    539:                        char *p = &linebuf[curr];
                    540:                        LWCHAR bch;
                    541:                        do {
                    542:                                bch = step_char(&p, -1, linebuf);
                    543:                        } while (p > linebuf && !IS_CSI_START(bch));
                    544:                        curr = p - linebuf;
1.13      nicm      545:                        return (0);
1.10      shadchin  546:                }
                    547:                a = AT_ANSI;    /* Will force re-AT_'ing around it.  */
1.5       millert   548:                w = 0;
1.13      nicm      549:        } else if (ctldisp == OPT_ONPLUS && IS_CSI_START(ch)) {
1.10      shadchin  550:                a = AT_ANSI;    /* Will force re-AT_'ing around it.  */
                    551:                w = 0;
1.13      nicm      552:        } else {
1.10      shadchin  553:                char *p = &linebuf[curr];
                    554:                LWCHAR prev_ch = step_char(&p, -1, linebuf);
                    555:                w = pwidth(ch, a, prev_ch);
                    556:        }
                    557:
1.5       millert   558:        if (ctldisp != OPT_ON && column + w + attr_ewidth(a) > sc_width)
1.1       etheisen  559:                /*
                    560:                 * Won't fit on screen.
                    561:                 */
                    562:                return (1);
                    563:
1.13      nicm      564:        if (rep == NULL) {
                    565:                cs = (char)ch;
1.10      shadchin  566:                rep = &cs;
                    567:                replen = 1;
1.13      nicm      568:        } else {
1.10      shadchin  569:                replen = utf_len(rep[0]);
                    570:        }
1.13      nicm      571:        if (curr + replen >= size_linebuf-6) {
1.1       etheisen  572:                /*
                    573:                 * Won't fit in line buffer.
1.5       millert   574:                 * Try to expand it.
1.1       etheisen  575:                 */
1.5       millert   576:                if (expand_linebuf())
                    577:                        return (1);
                    578:        }
1.1       etheisen  579:
1.13      nicm      580:        while (replen-- > 0) {
1.10      shadchin  581:                linebuf[curr] = *rep++;
                    582:                attr[curr] = a;
                    583:                curr++;
1.1       etheisen  584:        }
                    585:        column += w;
                    586:        return (0);
                    587: }
                    588:
                    589: /*
1.5       millert   590:  * Append a tab to the line buffer.
                    591:  * Store spaces to represent the tab.
                    592:  */
1.13      nicm      593: #define        STORE_TAB(a, pos)               \
                    594:        if (store_tab((a), (pos)))      \
                    595:                return (1)
1.5       millert   596:
1.13      nicm      597: static int
                    598: store_tab(int attr, off_t pos)
1.5       millert   599: {
                    600:        int to_tab = column + cshift - lmargin;
                    601:        int i;
                    602:
                    603:        if (ntabstops < 2 || to_tab >= tabstops[ntabstops-1])
                    604:                to_tab = tabdefault -
1.13      nicm      605:                    ((to_tab - tabstops[ntabstops-1]) % tabdefault);
                    606:        else {
1.5       millert   607:                for (i = ntabstops - 2;  i >= 0;  i--)
                    608:                        if (to_tab >= tabstops[i])
                    609:                                break;
                    610:                to_tab = tabstops[i+1] - to_tab;
                    611:        }
                    612:
1.13      nicm      613:        if (column + to_tab - 1 + pwidth(' ', attr, 0) +
                    614:            attr_ewidth(attr) > sc_width)
                    615:                return (1);
1.10      shadchin  616:
1.5       millert   617:        do {
1.10      shadchin  618:                STORE_CHAR(' ', attr, " ", pos);
1.5       millert   619:        } while (--to_tab > 0);
1.13      nicm      620:        return (0);
1.5       millert   621: }
                    622:
1.13      nicm      623: #define        STORE_PRCHAR(c, pos)            \
                    624:        if (store_prchar((c), (pos)))   \
                    625:                return (1)
1.10      shadchin  626:
1.13      nicm      627: static int
                    628: store_prchar(char c, off_t pos)
1.10      shadchin  629: {
                    630:        char *s;
                    631:
                    632:        /*
                    633:         * Convert to printable representation.
                    634:         */
                    635:        s = prchar(c);
                    636:
                    637:        /*
                    638:         * Make sure we can get the entire representation
                    639:         * of the character on this line.
                    640:         */
1.13      nicm      641:        if (column + (int)strlen(s) - 1 +
                    642:            pwidth(' ', binattr, 0) + attr_ewidth(binattr) > sc_width)
                    643:                return (1);
1.10      shadchin  644:
1.13      nicm      645:        for (; *s != 0; s++) {
1.10      shadchin  646:                STORE_CHAR(*s, AT_BINARY, NULL, pos);
1.13      nicm      647:        }
                    648:        return (0);
1.10      shadchin  649: }
                    650:
1.13      nicm      651: static int
                    652: flush_mbc_buf(off_t pos)
1.10      shadchin  653: {
                    654:        int i;
                    655:
                    656:        for (i = 0; i < mbc_buf_index; i++)
                    657:                if (store_prchar(mbc_buf[i], pos))
1.13      nicm      658:                        return (mbc_buf_index - i);
1.10      shadchin  659:
1.13      nicm      660:        return (0);
1.10      shadchin  661: }
                    662:
1.5       millert   663: /*
1.1       etheisen  664:  * Append a character to the line buffer.
                    665:  * Expand tabs into spaces, handle underlining, boldfacing, etc.
                    666:  * Returns 0 if ok, 1 if couldn't fit in buffer.
                    667:  */
1.13      nicm      668: int
                    669: pappend(char c, off_t pos)
1.1       etheisen  670: {
1.5       millert   671:        int r;
                    672:
1.13      nicm      673:        if (pendc) {
1.10      shadchin  674:                if (do_append(pendc, NULL, pendpos))
1.1       etheisen  675:                        /*
                    676:                         * Oops.  We've probably lost the char which
                    677:                         * was in pendc, since caller won't back up.
                    678:                         */
                    679:                        return (1);
                    680:                pendc = '\0';
                    681:        }
                    682:
1.13      nicm      683:        if (c == '\r' && bs_mode == BS_SPECIAL) {
                    684:                if (mbc_buf_len > 0)  /* utf_mode must be on. */ {
1.10      shadchin  685:                        /* Flush incomplete (truncated) sequence. */
                    686:                        r = flush_mbc_buf(mbc_pos);
                    687:                        mbc_buf_index = r + 1;
                    688:                        mbc_buf_len = 0;
                    689:                        if (r)
                    690:                                return (mbc_buf_index);
                    691:                }
                    692:
1.1       etheisen  693:                /*
1.13      nicm      694:                 * Don't put the CR into the buffer until we see
1.1       etheisen  695:                 * the next char.  If the next char is a newline,
                    696:                 * discard the CR.
                    697:                 */
                    698:                pendc = c;
                    699:                pendpos = pos;
                    700:                return (0);
                    701:        }
                    702:
1.13      nicm      703:        if (!utf_mode) {
1.10      shadchin  704:                r = do_append((LWCHAR) c, NULL, pos);
1.13      nicm      705:        } else {
1.10      shadchin  706:                /* Perform strict validation in all possible cases. */
1.13      nicm      707:                if (mbc_buf_len == 0) {
                    708: retry:
1.10      shadchin  709:                        mbc_buf_index = 1;
                    710:                        *mbc_buf = c;
1.13      nicm      711:                        if (IS_ASCII_OCTET(c)) {
1.10      shadchin  712:                                r = do_append((LWCHAR) c, NULL, pos);
1.13      nicm      713:                        } else if (IS_UTF8_LEAD(c)) {
1.10      shadchin  714:                                mbc_buf_len = utf_len(c);
                    715:                                mbc_pos = pos;
                    716:                                return (0);
1.13      nicm      717:                        } else {
1.10      shadchin  718:                                /* UTF8_INVALID or stray UTF8_TRAIL */
                    719:                                r = flush_mbc_buf(pos);
1.13      nicm      720:                        }
                    721:                } else if (IS_UTF8_TRAIL(c)) {
1.10      shadchin  722:                        mbc_buf[mbc_buf_index++] = c;
                    723:                        if (mbc_buf_index < mbc_buf_len)
                    724:                                return (0);
                    725:                        if (is_utf8_well_formed(mbc_buf))
1.13      nicm      726:                                r = do_append(get_wchar(mbc_buf), mbc_buf,
                    727:                                    mbc_pos);
1.10      shadchin  728:                        else
                    729:                                /* Complete, but not shortest form, sequence. */
                    730:                                mbc_buf_index = r = flush_mbc_buf(mbc_pos);
                    731:                        mbc_buf_len = 0;
1.13      nicm      732:                } else {
1.10      shadchin  733:                        /* Flush incomplete (truncated) sequence.  */
                    734:                        r = flush_mbc_buf(mbc_pos);
                    735:                        mbc_buf_index = r + 1;
                    736:                        mbc_buf_len = 0;
                    737:                        /* Handle new char.  */
                    738:                        if (!r)
                    739:                                goto retry;
1.13      nicm      740:                }
1.10      shadchin  741:        }
                    742:
1.5       millert   743:        /*
                    744:         * If we need to shift the line, do it.
                    745:         * But wait until we get to at least the middle of the screen,
                    746:         * so shifting it doesn't affect the chars we're currently
                    747:         * pappending.  (Bold & underline can get messed up otherwise.)
                    748:         */
1.13      nicm      749:        if (cshift < hshift && column > sc_width / 2) {
1.5       millert   750:                linebuf[curr] = '\0';
                    751:                pshift(hshift - cshift);
                    752:        }
1.13      nicm      753:        if (r) {
1.10      shadchin  754:                /* How many chars should caller back up? */
                    755:                r = (!utf_mode) ? 1 : mbc_buf_index;
                    756:        }
1.5       millert   757:        return (r);
1.1       etheisen  758: }
                    759:
1.13      nicm      760: static int
                    761: do_append(LWCHAR ch, char *rep, off_t pos)
1.1       etheisen  762: {
1.13      nicm      763:        int a;
1.10      shadchin  764:        LWCHAR prev_ch;
1.1       etheisen  765:
1.10      shadchin  766:        a = AT_NORMAL;
1.1       etheisen  767:
1.13      nicm      768:        if (ch == '\b') {
1.10      shadchin  769:                if (bs_mode == BS_CONTROL)
1.5       millert   770:                        goto do_control_char;
1.10      shadchin  771:
                    772:                /*
                    773:                 * A better test is needed here so we don't
                    774:                 * backspace over part of the printed
                    775:                 * representation of a binary character.
                    776:                 */
1.13      nicm      777:                if (curr <= lmargin ||
                    778:                    column <= lmargin ||
                    779:                    (attr[curr - 1] & (AT_ANSI|AT_BINARY))) {
1.10      shadchin  780:                        STORE_PRCHAR('\b', pos);
1.13      nicm      781:                } else if (bs_mode == BS_NORMAL) {
1.10      shadchin  782:                        STORE_CHAR(ch, AT_NORMAL, NULL, pos);
1.13      nicm      783:                } else if (bs_mode == BS_SPECIAL) {
1.10      shadchin  784:                        overstrike = backc();
1.13      nicm      785:                }
1.10      shadchin  786:
1.13      nicm      787:                return (0);
1.10      shadchin  788:        }
                    789:
1.13      nicm      790:        if (overstrike > 0) {
1.1       etheisen  791:                /*
                    792:                 * Overstrike the character at the current position
1.13      nicm      793:                 * in the line buffer.  This will cause either
                    794:                 * underline (if a "_" is overstruck),
1.1       etheisen  795:                 * bold (if an identical character is overstruck),
                    796:                 * or just deletion of the character in the buffer.
                    797:                 */
1.10      shadchin  798:                overstrike = utf_mode ? -1 : 0;
                    799:                /* To be correct, this must be a base character.  */
                    800:                prev_ch = get_wchar(linebuf + curr);
                    801:                a = attr[curr];
1.13      nicm      802:                if (ch == prev_ch) {
1.5       millert   803:                        /*
                    804:                         * Overstriking a char with itself means make it bold.
                    805:                         * But overstriking an underscore with itself is
                    806:                         * ambiguous.  It could mean make it bold, or
                    807:                         * it could mean make it underlined.
                    808:                         * Use the previous overstrike to resolve it.
                    809:                         */
1.13      nicm      810:                        if (ch == '_') {
1.10      shadchin  811:                                if ((a & (AT_BOLD|AT_UNDERLINE)) != AT_NORMAL)
                    812:                                        a |= (AT_BOLD|AT_UNDERLINE);
                    813:                                else if (last_overstrike != AT_NORMAL)
                    814:                                        a |= last_overstrike;
                    815:                                else
                    816:                                        a |= AT_BOLD;
1.13      nicm      817:                        } else {
1.10      shadchin  818:                                a |= AT_BOLD;
1.13      nicm      819:                        }
                    820:                } else if (ch == '_') {
1.10      shadchin  821:                        a |= AT_UNDERLINE;
                    822:                        ch = prev_ch;
                    823:                        rep = linebuf + curr;
1.13      nicm      824:                } else if (prev_ch == '_') {
1.10      shadchin  825:                        a |= AT_UNDERLINE;
                    826:                }
                    827:                /* Else we replace prev_ch, but we keep its attributes.  */
1.13      nicm      828:        } else if (overstrike < 0) {
                    829:                if (is_composing_char(ch) ||
                    830:                    is_combining_char(get_wchar(linebuf + curr), ch))
1.10      shadchin  831:                        /* Continuation of the same overstrike.  */
                    832:                        a = last_overstrike;
1.1       etheisen  833:                else
1.10      shadchin  834:                        overstrike = 0;
                    835:        }
                    836:
1.13      nicm      837:        if (ch == '\t') {
1.5       millert   838:                /*
                    839:                 * Expand a tab into spaces.
                    840:                 */
1.13      nicm      841:                switch (bs_mode) {
1.1       etheisen  842:                case BS_CONTROL:
                    843:                        goto do_control_char;
1.5       millert   844:                case BS_NORMAL:
1.1       etheisen  845:                case BS_SPECIAL:
1.10      shadchin  846:                        STORE_TAB(a, pos);
1.1       etheisen  847:                        break;
                    848:                }
1.13      nicm      849:        } else if ((!utf_mode || is_ascii_char(ch)) && control_char((char)ch)) {
                    850: do_control_char:
                    851:                if (ctldisp == OPT_ON ||
                    852:                    (ctldisp == OPT_ONPLUS && IS_CSI_START(ch))) {
1.1       etheisen  853:                        /*
                    854:                         * Output as a normal character.
                    855:                         */
1.10      shadchin  856:                        STORE_CHAR(ch, AT_NORMAL, rep, pos);
1.13      nicm      857:                } else {
                    858:                        STORE_PRCHAR((char)ch, pos);
1.10      shadchin  859:                }
1.13      nicm      860:        } else if (utf_mode && ctldisp != OPT_ON && is_ubin_char(ch)) {
1.10      shadchin  861:                char *s;
                    862:
                    863:                s = prutfchar(ch);
1.1       etheisen  864:
1.13      nicm      865:                if (column + (int)strlen(s) - 1 +
1.10      shadchin  866:                    pwidth(' ', binattr, 0) + attr_ewidth(binattr) > sc_width)
                    867:                        return (1);
1.1       etheisen  868:
1.13      nicm      869:                for (; *s != 0; s++)
1.10      shadchin  870:                        STORE_CHAR(*s, AT_BINARY, NULL, pos);
1.13      nicm      871:        } else {
1.10      shadchin  872:                STORE_CHAR(ch, a, rep, pos);
1.1       etheisen  873:        }
1.13      nicm      874:        return (0);
1.10      shadchin  875: }
                    876:
                    877: /*
                    878:  *
                    879:  */
1.13      nicm      880: int
                    881: pflushmbc(void)
1.10      shadchin  882: {
                    883:        int r = 0;
1.1       etheisen  884:
1.13      nicm      885:        if (mbc_buf_len > 0) {
1.10      shadchin  886:                /* Flush incomplete (truncated) sequence.  */
                    887:                r = flush_mbc_buf(mbc_pos);
                    888:                mbc_buf_len = 0;
                    889:        }
1.13      nicm      890:        return (r);
1.1       etheisen  891: }
                    892:
                    893: /*
                    894:  * Terminate the line in the line buffer.
                    895:  */
1.13      nicm      896: void
                    897: pdone(int endline, int forw)
1.1       etheisen  898: {
1.10      shadchin  899:        (void) pflushmbc();
                    900:
1.1       etheisen  901:        if (pendc && (pendc != '\r' || !endline))
                    902:                /*
                    903:                 * If we had a pending character, put it in the buffer.
                    904:                 * But discard a pending CR if we are at end of line
                    905:                 * (that is, discard the CR in a CR/LF sequence).
                    906:                 */
1.10      shadchin  907:                (void) do_append(pendc, NULL, pendpos);
1.1       etheisen  908:
                    909:        /*
1.5       millert   910:         * Make sure we've shifted the line, if we need to.
                    911:         */
                    912:        if (cshift < hshift)
                    913:                pshift(hshift - cshift);
                    914:
1.13      nicm      915:        if (ctldisp == OPT_ONPLUS && is_ansi_end('m')) {
1.10      shadchin  916:                /* Switch to normal attribute at end of line. */
                    917:                char *p = "\033[m";
1.13      nicm      918:                for (; *p != '\0'; p++) {
1.10      shadchin  919:                        linebuf[curr] = *p;
                    920:                        attr[curr++] = AT_ANSI;
                    921:                }
                    922:        }
                    923:
1.5       millert   924:        /*
1.1       etheisen  925:         * Add a newline if necessary,
                    926:         * and append a '\0' to the end of the line.
1.10      shadchin  927:         * We output a newline if we're not at the right edge of the screen,
                    928:         * or if the terminal doesn't auto wrap,
                    929:         * or if this is really the end of the line AND the terminal ignores
                    930:         * a newline at the right edge.
1.13      nicm      931:         * (In the last case we don't want to output a newline if the terminal
1.10      shadchin  932:         * doesn't ignore it since that would produce an extra blank line.
                    933:         * But we do want to output a newline if the terminal ignores it in case
                    934:         * the next line is blank.  In that case the single newline output for
                    935:         * that blank line would be ignored!)
1.1       etheisen  936:         */
1.13      nicm      937:        if (column < sc_width || !auto_wrap || (endline && ignaw) ||
                    938:            ctldisp == OPT_ON) {
1.1       etheisen  939:                linebuf[curr] = '\n';
                    940:                attr[curr] = AT_NORMAL;
                    941:                curr++;
1.13      nicm      942:        } else if (ignaw && column >= sc_width && forw) {
1.10      shadchin  943:                /*
                    944:                 * Terminals with "ignaw" don't wrap until they *really* need
                    945:                 * to, i.e. when the character *after* the last one to fit on a
                    946:                 * line is output. But they are too hard to deal with when they
                    947:                 * get in the state where a full screen width of characters
                    948:                 * have been output but the cursor is sitting on the right edge
                    949:                 * instead of at the start of the next line.
1.13      nicm      950:                 * So we nudge them into wrapping by outputting a space
                    951:                 * character plus a backspace.  But do this only if moving
1.10      shadchin  952:                 * forward; if we're moving backward and drawing this line at
                    953:                 * the top of the screen, the space would overwrite the first
1.13      nicm      954:                 * char on the next line.  We don't need to do this "nudge"
1.10      shadchin  955:                 * at the top of the screen anyway.
                    956:                 */
                    957:                linebuf[curr] = ' ';
                    958:                attr[curr++] = AT_NORMAL;
1.13      nicm      959:                linebuf[curr] = '\b';
1.10      shadchin  960:                attr[curr++] = AT_NORMAL;
1.1       etheisen  961:        }
                    962:        linebuf[curr] = '\0';
                    963:        attr[curr] = AT_NORMAL;
1.10      shadchin  964: }
1.5       millert   965:
1.10      shadchin  966: /*
                    967:  *
                    968:  */
1.13      nicm      969: void
                    970: set_status_col(char c)
1.10      shadchin  971: {
                    972:        linebuf[0] = c;
                    973:        attr[0] = AT_NORMAL|AT_HILITE;
1.1       etheisen  974: }
                    975:
                    976: /*
                    977:  * Get a character from the current line.
                    978:  * Return the character as the function return value,
                    979:  * and the character attribute in *ap.
                    980:  */
1.13      nicm      981: int
                    982: gline(int i, int *ap)
1.1       etheisen  983: {
1.13      nicm      984:        if (is_null_line) {
1.1       etheisen  985:                /*
                    986:                 * If there is no current line, we pretend the line is
                    987:                 * either "~" or "", depending on the "twiddle" flag.
                    988:                 */
1.13      nicm      989:                if (twiddle) {
                    990:                        if (i == 0) {
1.10      shadchin  991:                                *ap = AT_BOLD;
1.13      nicm      992:                                return ('~');
1.10      shadchin  993:                        }
                    994:                        --i;
                    995:                }
                    996:                /* Make sure we're back to AT_NORMAL before the '\n'.  */
                    997:                *ap = AT_NORMAL;
1.13      nicm      998:                return (i ? '\0' : '\n');
1.1       etheisen  999:        }
                   1000:
                   1001:        *ap = attr[i];
1.10      shadchin 1002:        return (linebuf[i] & 0xFF);
1.1       etheisen 1003: }
                   1004:
                   1005: /*
                   1006:  * Indicate that there is no current line.
                   1007:  */
1.13      nicm     1008: void
                   1009: null_line(void)
1.1       etheisen 1010: {
                   1011:        is_null_line = 1;
1.5       millert  1012:        cshift = 0;
1.1       etheisen 1013: }
                   1014:
                   1015: /*
                   1016:  * Analogous to forw_line(), but deals with "raw lines":
                   1017:  * lines which are not split for screen width.
                   1018:  * {{ This is supposed to be more efficient than forw_line(). }}
                   1019:  */
1.13      nicm     1020: off_t
                   1021: forw_raw_line(off_t curr_pos, char **linep, int *line_lenp)
                   1022: {
                   1023:        int n;
                   1024:        int c;
                   1025:        off_t new_pos;
                   1026:
                   1027:        if (curr_pos == -1 || ch_seek(curr_pos) ||
                   1028:            (c = ch_forw_get()) == EOI)
                   1029:                return (-1);
1.1       etheisen 1030:
1.5       millert  1031:        n = 0;
1.13      nicm     1032:        for (;;) {
                   1033:                if (c == '\n' || c == EOI || ABORT_SIGS()) {
1.1       etheisen 1034:                        new_pos = ch_tell();
                   1035:                        break;
                   1036:                }
1.13      nicm     1037:                if (n >= size_linebuf-1) {
                   1038:                        if (expand_linebuf()) {
1.5       millert  1039:                                /*
                   1040:                                 * Overflowed the input buffer.
                   1041:                                 * Pretend the line ended here.
                   1042:                                 */
                   1043:                                new_pos = ch_tell() - 1;
                   1044:                                break;
                   1045:                        }
1.1       etheisen 1046:                }
1.13      nicm     1047:                linebuf[n++] = (char)c;
1.1       etheisen 1048:                c = ch_forw_get();
                   1049:        }
1.5       millert  1050:        linebuf[n] = '\0';
1.1       etheisen 1051:        if (linep != NULL)
                   1052:                *linep = linebuf;
1.10      shadchin 1053:        if (line_lenp != NULL)
                   1054:                *line_lenp = n;
1.1       etheisen 1055:        return (new_pos);
                   1056: }
                   1057:
                   1058: /*
                   1059:  * Analogous to back_line(), but deals with "raw lines".
                   1060:  * {{ This is supposed to be more efficient than back_line(). }}
                   1061:  */
1.13      nicm     1062: off_t
                   1063: back_raw_line(off_t curr_pos, char **linep, int *line_lenp)
                   1064: {
                   1065:        int n;
                   1066:        int c;
                   1067:        off_t new_pos;
                   1068:
                   1069:        if (curr_pos == -1 || curr_pos <= ch_zero() || ch_seek(curr_pos - 1))
                   1070:                return (-1);
1.1       etheisen 1071:
1.5       millert  1072:        n = size_linebuf;
                   1073:        linebuf[--n] = '\0';
1.13      nicm     1074:        for (;;) {
1.1       etheisen 1075:                c = ch_back_get();
1.13      nicm     1076:                if (c == '\n' || ABORT_SIGS()) {
1.1       etheisen 1077:                        /*
                   1078:                         * This is the newline ending the previous line.
                   1079:                         * We have hit the beginning of the line.
                   1080:                         */
                   1081:                        new_pos = ch_tell() + 1;
                   1082:                        break;
                   1083:                }
1.13      nicm     1084:                if (c == EOI) {
1.1       etheisen 1085:                        /*
                   1086:                         * We have hit the beginning of the file.
                   1087:                         * This must be the first line in the file.
                   1088:                         * This must, of course, be the beginning of the line.
                   1089:                         */
                   1090:                        new_pos = ch_zero();
                   1091:                        break;
                   1092:                }
1.13      nicm     1093:                if (n <= 0) {
1.5       millert  1094:                        int old_size_linebuf = size_linebuf;
1.13      nicm     1095:                        if (expand_linebuf()) {
1.5       millert  1096:                                /*
                   1097:                                 * Overflowed the input buffer.
                   1098:                                 * Pretend the line ended here.
                   1099:                                 */
                   1100:                                new_pos = ch_tell() + 1;
                   1101:                                break;
                   1102:                        }
1.1       etheisen 1103:                        /*
1.5       millert  1104:                         * Shift the data to the end of the new linebuf.
1.1       etheisen 1105:                         */
1.5       millert  1106:                        n = size_linebuf - old_size_linebuf;
1.8       millert  1107:                        memmove(linebuf + n, linebuf, old_size_linebuf);
1.1       etheisen 1108:                }
1.5       millert  1109:                linebuf[--n] = c;
1.1       etheisen 1110:        }
                   1111:        if (linep != NULL)
1.5       millert  1112:                *linep = &linebuf[n];
1.10      shadchin 1113:        if (line_lenp != NULL)
                   1114:                *line_lenp = size_linebuf - 1 - n;
1.1       etheisen 1115:        return (new_pos);
                   1116: }