-/* $Id: term.c,v 1.82 2009/06/22 12:38:07 kristaps Exp $ */
+/* $Id: term.c,v 1.96 2009/07/27 13:10:08 kristaps Exp $ */
/*
* Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se>
*
static struct termp *term_alloc(enum termenc);
static void term_free(struct termp *);
-static void term_pword(struct termp *, const char *, int);
-static void term_pescape(struct termp *,
- const char *, int *, int);
-static void term_nescape(struct termp *,
+
+static void do_escaped(struct termp *, const char **);
+static void do_special(struct termp *,
+ const char *, size_t);
+static void do_reserved(struct termp *,
const char *, size_t);
-static void term_chara(struct termp *, char);
-static void term_encodea(struct termp *, char);
-static int term_isopendelim(const char *, int);
-static int term_isclosedelim(const char *, int);
+static void buffer(struct termp *, char);
+static void encode(struct termp *, char);
+static int isopendelim(const char *);
+static int isclosedelim(const char *);
void *
static int
-term_isclosedelim(const char *p, int len)
+isclosedelim(const char *p)
{
- if (1 != len)
+ if ( ! (*p && 0 == *(p + 1)))
return(0);
switch (*p) {
static int
-term_isopendelim(const char *p, int len)
+isopendelim(const char *p)
{
- if (1 != len)
+ if ( ! (*p && 0 == *(p + 1)))
return(0);
switch (*p) {
* Specifically, a line is whatever's in p->buf of length p->col, which
* is zeroed after this function returns.
*
- * The variables TERMP_NOLPAD, TERMP_LITERAL and TERMP_NOBREAK are of
- * critical importance here. Their behaviour follows:
+ * The usage of termp:flags is as follows:
*
* - TERMP_NOLPAD: when beginning to write the line, don't left-pad the
* offset value. This is useful when doing columnar lists where the
* columns. In short: don't print a newline and instead pad to the
* right margin. Used in conjunction with TERMP_NOLPAD.
*
- * - TERMP_NONOBREAK: don't newline when TERMP_NOBREAK is specified.
+ * - TERMP_TWOSPACE: when padding, make sure there are at least two
+ * space characters of padding. Otherwise, rather break the line.
+ *
+ * - TERMP_DANGLE: don't newline when TERMP_NOBREAK is specified and
+ * the line is overrun, and don't pad-right if it's underrun.
+ *
+ * - TERMP_HANG: like TERMP_DANGLE, but doesn't newline when
+ * overruning, instead save the position and continue at that point
+ * when the next invocation.
*
* In-line line breaking:
*
* Otherwise, the line will break at the right margin. Extremely long
* lines will cause the system to emit a warning (TODO: hyphenate, if
* possible).
- *
- * FIXME: newline breaks occur (in groff) also occur when a single
- * space follows a NOBREAK!
*/
void
term_flushln(struct termp *p)
{
int i, j;
size_t vbl, vsz, vis, maxvis, mmax, bp;
+ static int overstep = 0;
/*
* First, establish the maximum columns of "visible" content.
*/
assert(p->offset < p->rmargin);
- maxvis = p->rmargin - p->offset;
- mmax = p->maxrmargin - p->offset;
+ assert((int)(p->rmargin - p->offset) - overstep > 0);
+
+ maxvis = /* LINTED */
+ p->rmargin - p->offset - overstep;
+ mmax = /* LINTED */
+ p->maxrmargin - p->offset - overstep;
+
bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
vis = 0;
+ overstep = 0;
/*
* If in the standard case (left-justified), then begin with our
/* LINTED */
for (j = i, vsz = 0; j < (int)p->col; j++) {
- if (' ' == p->buf[j])
+ if (j && ' ' == p->buf[j])
break;
else if (8 == p->buf[j])
- j += 1;
+ vsz--;
else
vsz++;
}
}
vis += vsz;
}
+ p->col = 0;
- /*
- * If we've overstepped our maximum visible no-break space, then
- * cause a newline and offset at the right margin.
- */
-
- if ((TERMP_NOBREAK & p->flags) && vis >= maxvis) {
- if ( ! (TERMP_NONOBREAK & p->flags)) {
- putchar('\n');
- for (i = 0; i < (int)p->rmargin; i++)
- putchar(' ');
- }
- p->col = 0;
+ if ( ! (TERMP_NOBREAK & p->flags)) {
+ putchar('\n');
return;
}
- /*
- * If we're not to right-marginalise it (newline), then instead
- * pad to the right margin and stay off.
- */
+ if (TERMP_HANG & p->flags) {
+ /* We need one blank after the tag. */
+ overstep = /* LINTED */
+ vis - maxvis + 1;
- if (p->flags & TERMP_NOBREAK) {
- if ( ! (TERMP_NONOBREAK & p->flags))
- for ( ; vis < maxvis; vis++)
- putchar(' ');
- } else
- putchar('\n');
+ /*
+ * Behave exactly the same way as groff:
+ * If we have overstepped the margin, temporarily move
+ * it to the right and flag the rest of the line to be
+ * shorter.
+ * If we landed right at the margin, be happy.
+ * If we are one step before the margin, temporarily
+ * move it one step LEFT and flag the rest of the line
+ * to be longer.
+ */
+ if (overstep >= -1) {
+ assert((int)maxvis + overstep >= 0);
+ /* LINTED */
+ maxvis += overstep;
+ } else
+ overstep = 0;
+
+ } else if (TERMP_DANGLE & p->flags)
+ return;
- p->col = 0;
+ /* Right-pad. */
+ if (maxvis > vis + /* LINTED */
+ ((TERMP_TWOSPACE & p->flags) ? 1 : 0))
+ for ( ; vis < maxvis; vis++)
+ putchar(' ');
+ else { /* ...or newline break. */
+ putchar('\n');
+ for (i = 0; i < (int)p->rmargin; i++)
+ putchar(' ');
+ }
}
}
-/*
- * Break apart a word into "pwords" (partial-words, usually from
- * breaking up a phrase into individual words) and, eventually, put them
- * into the output buffer. If we're a literal word, then don't break up
- * the word and put it verbatim into the output buffer.
- */
-void
-term_word(struct termp *p, const char *word)
+static void
+do_special(struct termp *p, const char *word, size_t len)
{
- int i, j, len;
+ const char *rhs;
+ size_t sz;
+ int i;
- len = (int)strlen(word);
+ rhs = term_a2ascii(p->symtab, word, len, &sz);
- if (p->flags & TERMP_LITERAL) {
- term_pword(p, word, len);
+ if (NULL == rhs) {
+#if 1
+ fputs("Unknown special character: ", stderr);
+ for (i = 0; i < (int)len; i++)
+ fputc(word[i], stderr);
+ fputc('\n', stderr);
+#endif
return;
}
-
- /* LINTED */
- for (j = i = 0; i < len; i++) {
- if (' ' != word[i]) {
- j++;
- continue;
- }
-
- /* Escaped spaces don't delimit... */
- if (i && ' ' == word[i] && '\\' == word[i - 1]) {
- j++;
- continue;
- }
-
- if (0 == j)
- continue;
- assert(i >= j);
- term_pword(p, &word[i - j], j);
- j = 0;
- }
- if (j > 0) {
- assert(i >= j);
- term_pword(p, &word[i - j], j);
- }
+ for (i = 0; i < (int)sz; i++)
+ encode(p, rhs[i]);
}
-/*
- * Determine the symbol indicated by an escape sequences, that is, one
- * starting with a backslash. Once done, we pass this value into the
- * output buffer by way of the symbol table.
- */
static void
-term_nescape(struct termp *p, const char *word, size_t len)
+do_reserved(struct termp *p, const char *word, size_t len)
{
const char *rhs;
size_t sz;
int i;
- if ((rhs = term_a2ascii(p->symtab, word, len, &sz)))
- for (i = 0; i < (int)sz; i++)
- term_encodea(p, rhs[i]);
+ rhs = term_a2res(p->symtab, word, len, &sz);
+
+ if (NULL == rhs) {
+#if 0
+ fputs("Unknown reserved word: ", stderr);
+ for (i = 0; i < (int)len; i++)
+ fputc(word[i], stderr);
+ fputc('\n', stderr);
+#endif
+ return;
+ }
+ for (i = 0; i < (int)sz; i++)
+ encode(p, rhs[i]);
}
* the escape sequence (we assert upon badly-formed escape sequences).
*/
static void
-term_pescape(struct termp *p, const char *word, int *i, int len)
+do_escaped(struct termp *p, const char **word)
{
int j;
+ const char *wp;
- if (++(*i) >= len)
+ wp = *word;
+
+ if (0 == *(++wp)) {
+ *word = wp;
return;
+ }
- if ('(' == word[*i]) {
- (*i)++;
- if (*i + 1 >= len)
+ if ('(' == *wp) {
+ wp++;
+ if (0 == *wp || 0 == *(wp + 1)) {
+ *word = 0 == *wp ? wp : wp + 1;
return;
+ }
- term_nescape(p, &word[*i], 2);
- (*i)++;
+ do_special(p, wp, 2);
+ *word = ++wp;
return;
- } else if ('*' == word[*i]) {
- (*i)++;
- if (*i >= len)
+ } else if ('*' == *wp) {
+ if (0 == *(++wp)) {
+ *word = wp;
return;
+ }
- switch (word[*i]) {
+ switch (*wp) {
case ('('):
- (*i)++;
- if (*i + 1 >= len)
+ wp++;
+ if (0 == *wp || 0 == *(wp + 1)) {
+ *word = 0 == *wp ? wp : wp + 1;
return;
+ }
- term_nescape(p, &word[*i], 2);
- (*i)++;
+ do_reserved(p, wp, 2);
+ *word = ++wp;
return;
case ('['):
break;
default:
- term_nescape(p, &word[*i], 1);
+ do_reserved(p, wp, 1);
+ *word = wp;
return;
}
- } else if ('f' == word[*i]) {
- (*i)++;
- if (*i >= len)
+ } else if ('f' == *wp) {
+ if (0 == *(++wp)) {
+ *word = wp;
return;
- switch (word[*i]) {
+ }
+
+ switch (*wp) {
case ('B'):
p->flags |= TERMP_BOLD;
break;
default:
break;
}
+
+ *word = wp;
return;
- } else if ('[' != word[*i]) {
- term_nescape(p, &word[*i], 1);
+ } else if ('[' != *wp) {
+ do_special(p, wp, 1);
+ *word = wp;
return;
}
- (*i)++;
- for (j = 0; word[*i] && ']' != word[*i]; (*i)++, j++)
+ wp++;
+ for (j = 0; *wp && ']' != *wp; wp++, j++)
/* Loop... */ ;
- if (0 == word[*i])
+ if (0 == *wp) {
+ *word = wp;
return;
+ }
- term_nescape(p, &word[*i - j], (size_t)j);
+ do_special(p, wp - j, (size_t)j);
+ *word = wp;
}
* phrase that cannot be broken down (such as a literal string). This
* handles word styling.
*/
-static void
-term_pword(struct termp *p, const char *word, int len)
+void
+term_word(struct termp *p, const char *word)
{
- int i;
+ const char *sv;
- if (term_isclosedelim(word, len))
+ if (isclosedelim(word))
if ( ! (TERMP_IGNDELIM & p->flags))
p->flags |= TERMP_NOSPACE;
if ( ! (TERMP_NOSPACE & p->flags))
- term_chara(p, ' ');
+ buffer(p, ' ');
if ( ! (p->flags & TERMP_NONOSPACE))
p->flags &= ~TERMP_NOSPACE;
- /*
- * If ANSI (word-length styling), then apply our style now,
- * before the word.
- */
-
- for (i = 0; i < len; i++)
- if ('\\' == word[i])
- term_pescape(p, word, &i, len);
+ for (sv = word; *word; word++)
+ if ('\\' != *word)
+ encode(p, *word);
else
- term_encodea(p, word[i]);
+ do_escaped(p, &word);
- if (term_isopendelim(word, len))
+ if (isopendelim(sv))
p->flags |= TERMP_NOSPACE;
}
* size.
*/
static void
-term_chara(struct termp *p, char c)
+buffer(struct termp *p, char c)
{
size_t s;
static void
-term_encodea(struct termp *p, char c)
+encode(struct termp *p, char c)
{
-
- if (TERMP_STYLE & p->flags) {
+
+ if (' ' != c && TERMP_STYLE & p->flags) {
if (TERMP_BOLD & p->flags) {
- term_chara(p, c);
- term_chara(p, 8);
+ buffer(p, c);
+ buffer(p, 8);
}
if (TERMP_UNDER & p->flags) {
- term_chara(p, '_');
- term_chara(p, 8);
+ buffer(p, '_');
+ buffer(p, 8);
}
}
- term_chara(p, c);
+ buffer(p, c);
}