=================================================================== RCS file: /cvs/mandoc/term.c,v retrieving revision 1.1 retrieving revision 1.8 diff -u -p -r1.1 -r1.8 --- mandoc/term.c 2009/02/20 11:04:23 1.1 +++ mandoc/term.c 2009/02/22 15:50:45 1.8 @@ -1,6 +1,6 @@ -/* $Id: term.c,v 1.1 2009/02/20 11:04:23 kristaps Exp $ */ +/* $Id: term.c,v 1.8 2009/02/22 15:50:45 kristaps Exp $ */ /* - * Copyright (c) 2008 Kristaps Dzonsons + * Copyright (c) 2008, 2009 Kristaps Dzonsons * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the @@ -17,273 +17,406 @@ * PERFORMANCE OF THIS SOFTWARE. */ #include -#include +#include #include #include #include #include -#include #include -#include "mdoc.h" +#ifdef __linux__ +#include +#endif +#include "term.h" -static int termprint_r(size_t, size_t, +enum termstyle { + STYLE_CLEAR, + STYLE_BOLD, + STYLE_UNDERLINE +}; + +static void termprint_r(struct termp *, + const struct mdoc_meta *, const struct mdoc_node *); -static void termprint_head(size_t, +static void termprint_header(struct termp *, const struct mdoc_meta *); -static void termprint_tail(size_t, +static void termprint_footer(struct termp *, const struct mdoc_meta *); -static char *arch2a(enum mdoc_arch); -static char *vol2a(enum mdoc_vol); -static char *msec2a(enum mdoc_msec); +static void pword(struct termp *, const char *, size_t); +static void pescape(struct termp *, + const char *, size_t *, size_t); +static void chara(struct termp *, char); +static void style(struct termp *, enum termstyle); -static size_t ttitle2a(char *, enum mdoc_vol, enum mdoc_msec, - enum mdoc_arch, size_t); +#ifdef __linux__ +extern size_t strlcat(char *, const char *, size_t); +extern size_t strlcpy(char *, const char *, size_t); +#endif - -static char * -arch2a(enum mdoc_arch arch) +void +flushln(struct termp *p) { + size_t i, j, vsz, vis, maxvis; - switch (arch) { - case (ARCH_alpha): - return("Alpha"); - case (ARCH_amd64): - return("AMD64"); - case (ARCH_amiga): - return("Amiga"); - case (ARCH_arc): - return("ARC"); - case (ARCH_arm): - return("ARM"); - case (ARCH_armish): - return("ARMISH"); - case (ARCH_aviion): - return("AViion"); - case (ARCH_hp300): - return("HP300"); - case (ARCH_hppa): - return("HPPA"); - case (ARCH_hppa64): - return("HPPA64"); - case (ARCH_i386): - return("i386"); - case (ARCH_landisk): - return("LANDISK"); - case (ARCH_luna88k): - return("Luna88k"); - case (ARCH_mac68k): - return("Mac68k"); - case (ARCH_macppc): - return("MacPPC"); - case (ARCH_mvme68k): - return("MVME68k"); - case (ARCH_mvme88k): - return("MVME88k"); - case (ARCH_mvmeppc): - return("MVMEPPC"); - case (ARCH_pmax): - return("PMAX"); - case (ARCH_sgi): - return("SGI"); - case (ARCH_socppc): - return("SOCPPC"); - case (ARCH_sparc): - return("SPARC"); - case (ARCH_sparc64): - return("SPARC64"); - case (ARCH_sun3): - return("Sun3"); - case (ARCH_vax): - return("VAX"); - case (ARCH_zaurus): - return("Zaurus"); - default: - break; + /* + * First, establish the maximum columns of "visible" content. + * This is usually the difference between the right-margin and + * an indentation, but can be, for tagged lists or columns, a + * small set of values. + */ + + assert(p->offset < p->rmargin); + maxvis = p->rmargin - p->offset; + vis = 0; + + /* + * If in the standard case (left-justified), then begin with our + * indentation, otherwise (columns, etc.) just start spitting + * out text. + */ + + if ( ! (p->flags & TERMP_NOLPAD)) + /* LINTED */ + for (j = 0; j < p->offset; j++) + putchar(' '); + + /* + * If we're literal, print out verbatim. + */ + if (p->flags & TERMP_LITERAL) { + /* FIXME: count non-printing chars. */ + for (i = 0; i < p->col; i++) + putchar(p->buf[i]); + putchar('\n'); + p->col = 0; + return; } - return(NULL); + for (i = 0; i < p->col; i++) { + /* + * Count up visible word characters. Control sequences + * (starting with the CSI) aren't counted. + */ + assert( ! isspace(p->buf[i])); + + /* LINTED */ + for (j = i, vsz = 0; j < p->col; j++) { + if (isspace(p->buf[j])) + break; + else if (27 == p->buf[j]) { + assert(j + 4 <= p->col); + j += 3; + } else + vsz++; + } + assert(vsz > 0); + + /* + * If a word is too long and we're within a line, put it + * on the next line. Puke if we're being asked to write + * something that will exceed the right margin (i.e., + * from a fresh line or when we're not allowed to break + * the line with TERMP_NOBREAK). + */ + + if (vis && vis + vsz >= maxvis) { + /* FIXME */ + if (p->flags & TERMP_NOBREAK) + errx(1, "word breaks right margin"); + putchar('\n'); + for (j = 0; j < p->offset; j++) + putchar(' '); + vis = 0; + } else if (vis + vsz >= maxvis) { + /* FIXME */ + errx(1, "word breaks right margin"); + } + + /* + * Write out the word and a trailing space. Omit the + * space if we're the last word in the line. + */ + + for ( ; i < p->col; i++) { + if (isspace(p->buf[i])) + break; + putchar(p->buf[i]); + } + vis += vsz; + if (i < p->col) { + putchar(' '); + vis++; + } + } + + /* + * If we're not to right-marginalise it (newline), then instead + * pad to the right margin and stay off. + */ + + if (p->flags & TERMP_NOBREAK) { + for ( ; vis <= maxvis; vis++) + putchar(' '); + } else + putchar('\n'); + + p->col = 0; } -static char * -vol2a(enum mdoc_vol vol) +void +newln(struct termp *p) { - switch (vol) { - case (VOL_AMD): - return("OpenBSD Ancestral Manual Documents"); - case (VOL_IND): - return("OpenBSD Manual Master Index"); - case (VOL_KM): - return("OpenBSD Kernel Manual"); - case (VOL_LOCAL): - return("OpenBSD Local Manual"); - case (VOL_PRM): - return("OpenBSD Programmer's Manual"); - case (VOL_PS1): - return("OpenBSD Programmer's Supplementary Documents"); - case (VOL_SMM): - return("OpenBSD System Manager's Manual"); - case (VOL_URM): - return("OpenBSD Reference Manual"); - case (VOL_USD): - return("OpenBSD User's Supplementary Documents"); - default: - break; - } + /* + * A newline only breaks an existing line; it won't assert + * vertical space. + */ + p->flags |= TERMP_NOSPACE; + if (0 == p->col) + return; + flushln(p); +} - return(NULL); + +void +vspace(struct termp *p) +{ + + /* + * Asserts a vertical space (a full, empty line-break between + * lines). + */ + newln(p); + putchar('\n'); } -static char * -msec2a(enum mdoc_msec msec) +static void +chara(struct termp *p, char c) { - switch (msec) { - case(MSEC_1): - return("1"); - case(MSEC_2): - return("2"); - case(MSEC_3): - return("3"); - case(MSEC_3f): - return("3f"); - case(MSEC_3p): - return("3p"); - case(MSEC_4): - return("4"); - case(MSEC_5): - return("5"); - case(MSEC_6): - return("6"); - case(MSEC_7): - return("7"); - case(MSEC_8): - return("8"); - case(MSEC_9): - return("9"); - case(MSEC_X11): - return("X11"); - case(MSEC_X11R6): - return("X11R6"); - case(MSEC_local): - return("local"); - case(MSEC_n): - return("n"); - case(MSEC_unass): - /* FALLTHROUGH */ - case(MSEC_draft): - return("draft"); - case(MSEC_paper): - return("paper"); - default: + /* TODO: dynamically expand the buffer. */ + if (p->col + 1 >= p->maxcols) + errx(1, "line overrun"); + p->buf[(p->col)++] = c; +} + + +static void +style(struct termp *p, enum termstyle esc) +{ + + if (p->col + 4 >= p->maxcols) + errx(1, "line overrun"); + + p->buf[(p->col)++] = 27; + p->buf[(p->col)++] = '['; + switch (esc) { + case (STYLE_CLEAR): + p->buf[(p->col)++] = '0'; break; + case (STYLE_BOLD): + p->buf[(p->col)++] = '1'; + break; + case (STYLE_UNDERLINE): + p->buf[(p->col)++] = '4'; + break; + default: + abort(); + /* NOTREACHED */ } - return(NULL); + p->buf[(p->col)++] = 'm'; } -static size_t -ttitle2a(char *dst, enum mdoc_vol vol, enum mdoc_msec msec, - enum mdoc_arch arch, size_t sz) +static void +pescape(struct termp *p, const char *word, size_t *i, size_t len) { - char *p; - size_t ssz; - if (NULL == (p = vol2a(vol))) - switch (msec) { - case (MSEC_1): + (*i)++; + assert(*i < len); + + if ('(' == word[*i]) { + /* Two-character escapes. */ + (*i)++; + assert(*i + 1 < len); + + if ('r' == word[*i] && 'B' == word[*i + 1]) + chara(p, ']'); + else if ('l' == word[*i] && 'B' == word[*i + 1]) + chara(p, '['); + + (*i)++; + return; + + } else if ('[' != word[*i]) { + /* One-character escapes. */ + switch (word[*i]) { + case ('\\'): /* FALLTHROUGH */ - case (MSEC_6): + case ('\''): /* FALLTHROUGH */ - case (MSEC_7): - p = vol2a(VOL_URM); - break; - case (MSEC_8): - p = vol2a(VOL_SMM); - break; - case (MSEC_2): + case ('`'): /* FALLTHROUGH */ - case (MSEC_3): + case ('-'): /* FALLTHROUGH */ - case (MSEC_4): - /* FALLTHROUGH */ - case (MSEC_5): - p = vol2a(VOL_PRM); - break; - case (MSEC_9): - p = vol2a(VOL_KM); - break; + case ('.'): + chara(p, word[*i]); default: - /* FIXME: capitalise. */ - if (NULL == (p = msec2a(msec))) - p = msec2a(MSEC_local); break; } - assert(p); + return; + } + /* n-character escapes. */ +} - if ((ssz = strlcpy(dst, p, sz)) >= sz) - return(ssz); - if ((p = arch2a(arch))) { - if ((ssz = strlcat(dst, " (", sz)) >= sz) - return(ssz); - if ((ssz = strlcat(dst, p, sz)) >= sz) - return(ssz); - if ((ssz = strlcat(dst, ")", sz)) >= sz) - return(ssz); +static void +pword(struct termp *p, const char *word, size_t len) +{ + size_t i; + + /*assert(len > 0);*/ /* Can be, if literal. */ + + if ( ! (p->flags & TERMP_NOSPACE) && + ! (p->flags & TERMP_LITERAL)) + chara(p, ' '); + + p->flags &= ~TERMP_NOSPACE; + + if (p->flags & TERMP_BOLD) + style(p, STYLE_BOLD); + if (p->flags & TERMP_UNDERLINE) + style(p, STYLE_UNDERLINE); + + for (i = 0; i < len; i++) { + if ('\\' == word[i]) { + pescape(p, word, &i, len); + continue; + } + chara(p, word[i]); } - return(ssz); + if (p->flags & TERMP_BOLD || + p->flags & TERMP_UNDERLINE) + style(p, STYLE_CLEAR); } -static int -termprint_r(size_t cols, size_t indent, const struct mdoc_node *node) +void +word(struct termp *p, const char *word) { + size_t i, j, len; - return(1); + if (p->flags & TERMP_LITERAL) { + pword(p, word, strlen(word)); + return; + } + + len = strlen(word); + assert(len > 0); + + if (mdoc_isdelim(word)) + p->flags |= TERMP_NOSPACE; + + /* LINTED */ + for (j = i = 0; i < len; i++) { + if ( ! isspace(word[i])) { + j++; + continue; + } + if (0 == j) + continue; + assert(i >= j); + pword(p, &word[i - j], j); + j = 0; + } + if (j > 0) { + assert(i >= j); + pword(p, &word[i - j], j); + } } static void -termprint_tail(size_t cols, const struct mdoc_meta *meta) +termprint_r(struct termp *p, const struct mdoc_meta *meta, + const struct mdoc_node *node) { + int dochild; + + /* Pre-processing. */ + + dochild = 1; + + if (MDOC_TEXT != node->type) { + if (termacts[node->tok].pre) + if ( ! (*termacts[node->tok].pre)(p, meta, node)) + dochild = 0; + } else /* MDOC_TEXT == node->type */ + word(p, node->data.text.string); + + /* Children. */ + + if (dochild && node->child) + termprint_r(p, meta, node->child); + + /* Post-processing. */ + + if (MDOC_TEXT != node->type) + if (termacts[node->tok].post) + (*termacts[node->tok].post)(p, meta, node); + + /* Siblings. */ + + if (node->next) + termprint_r(p, meta, node->next); +} + + +static void +termprint_footer(struct termp *p, const struct mdoc_meta *meta) +{ struct tm *tm; char *buf, *os; size_t sz, osz, ssz, i; - if (NULL == (buf = malloc(cols))) + if (NULL == (buf = malloc(p->rmargin))) err(1, "malloc"); - if (NULL == (os = malloc(cols))) + if (NULL == (os = malloc(p->rmargin))) err(1, "malloc"); tm = localtime(&meta->date); - if (NULL == strftime(buf, cols, "%B %d, %Y", tm)) + +#ifdef __linux__ + if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm)) +#else + if (NULL == strftime(buf, p->rmargin, "%B %d, %Y", tm)) +#endif err(1, "strftime"); - osz = strlcpy(os, meta->os, cols); + osz = strlcpy(os, meta->os, p->rmargin); sz = strlen(buf); ssz = sz + osz + 1; - if (ssz > cols) { - ssz -= cols; + if (ssz > p->rmargin) { + ssz -= p->rmargin; assert(ssz <= osz); os[osz - ssz] = 0; ssz = 1; } else - ssz = cols - ssz + 1; + ssz = p->rmargin - ssz + 1; + printf("\n"); printf("%s", os); for (i = 0; i < ssz; i++) printf(" "); printf("%s\n", buf); + fflush(stdout); free(buf); free(os); @@ -291,28 +424,70 @@ termprint_tail(size_t cols, const struct mdoc_meta *me static void -termprint_head(size_t cols, const struct mdoc_meta *meta) +termprint_header(struct termp *p, const struct mdoc_meta *meta) { - char *msec, *buf, *title; - size_t ssz, tsz, ttsz, i; + char *buf, *title; + const char *pp, *msec; + size_t ssz, tsz, ttsz, i;; - if (NULL == (buf = malloc(cols))) + if (NULL == (buf = malloc(p->rmargin))) err(1, "malloc"); - if (NULL == (title = malloc(cols))) + if (NULL == (title = malloc(p->rmargin))) err(1, "malloc"); - /* Format the manual page header. */ + if (NULL == (pp = mdoc_vol2a(meta->vol))) + switch (meta->msec) { + case (MSEC_1): + /* FALLTHROUGH */ + case (MSEC_6): + /* FALLTHROUGH */ + case (MSEC_7): + pp = mdoc_vol2a(VOL_URM); + break; + case (MSEC_8): + pp = mdoc_vol2a(VOL_SMM); + break; + case (MSEC_2): + /* FALLTHROUGH */ + case (MSEC_3): + /* FALLTHROUGH */ + case (MSEC_4): + /* FALLTHROUGH */ + case (MSEC_5): + pp = mdoc_vol2a(VOL_PRM); + break; + case (MSEC_9): + pp = mdoc_vol2a(VOL_KM); + break; + default: + /* FIXME: capitalise. */ + if (NULL == (pp = mdoc_msec2a(meta->msec))) + pp = mdoc_msec2a(MSEC_local); + break; + } + assert(pp); - tsz = ttitle2a(buf, meta->vol, meta->msec, meta->arch, cols); - ttsz = strlcpy(title, meta->title, cols); + tsz = strlcpy(buf, pp, p->rmargin); + assert(tsz < p->rmargin); - if (NULL == (msec = msec2a(meta->msec))) + if ((pp = mdoc_arch2a(meta->arch))) { + tsz = strlcat(buf, " (", p->rmargin); + assert(tsz < p->rmargin); + tsz = strlcat(buf, pp, p->rmargin); + assert(tsz < p->rmargin); + tsz = strlcat(buf, ")", p->rmargin); + assert(tsz < p->rmargin); + } + + ttsz = strlcpy(title, meta->title, p->rmargin); + + if (NULL == (msec = mdoc_msec2a(meta->msec))) msec = ""; ssz = (2 * (ttsz + 2 + strlen(msec))) + tsz + 2; - if (ssz > cols) { - if ((ssz -= cols) % 2) + if (ssz > p->rmargin) { + if ((ssz -= p->rmargin) % 2) ssz++; ssz /= 2; @@ -320,7 +495,7 @@ termprint_head(size_t cols, const struct mdoc_meta *me title[ttsz - ssz] = 0; ssz = 1; } else - ssz = ((cols - ssz) / 2) + 1; + ssz = ((p->rmargin - ssz) / 2) + 1; printf("%s(%s)", title, msec); @@ -332,29 +507,34 @@ termprint_head(size_t cols, const struct mdoc_meta *me for (i = 0; i < ssz; i++) printf(" "); - printf("%s(%s)\n\n", title, msec); + printf("%s(%s)\n", title, msec); + fflush(stdout); free(title); free(buf); } -int +void termprint(const struct mdoc_node *node, const struct mdoc_meta *meta) { - size_t cols; + struct termp p; - if (ERR == setupterm(NULL, STDOUT_FILENO, NULL)) - return(0); + p.maxrmargin = 80; /* XXX */ + p.rmargin = p.maxrmargin; + p.maxcols = 1024; + p.offset = p.col = 0; + p.flags = TERMP_NOSPACE; - cols = columns < 60 ? 60 : (size_t)columns; + if (NULL == (p.buf = malloc(p.maxcols))) + err(1, "malloc"); - termprint_head(cols, meta); - if ( ! termprint_r(cols, 0, node)) - return(0); - termprint_tail(cols, meta); - return(1); + termprint_header(&p, meta); + termprint_r(&p, meta, node); + termprint_footer(&p, meta); + + free(p.buf); }