Annotation of mandoc/term.c, Revision 1.71
1.71 ! kristaps 1: /* $Id: terminal.c,v 1.12 2009/03/26 16:47:13 kristaps Exp $ */
1.1 kristaps 2: /*
1.61 kristaps 3: * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org>
1.1 kristaps 4: *
5: * Permission to use, copy, modify, and distribute this software for any
6: * purpose with or without fee is hereby granted, provided that the
7: * above copyright notice and this permission notice appear in all
8: * copies.
9: *
10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
11: * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
12: * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
13: * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
14: * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
15: * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
16: * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17: * PERFORMANCE OF THIS SOFTWARE.
18: */
19: #include <assert.h>
1.23 kristaps 20: #include <err.h>
1.22 kristaps 21: #include <stdio.h>
1.1 kristaps 22: #include <stdlib.h>
23: #include <string.h>
24:
1.71 ! kristaps 25: #include "term.h"
! 26: #include "man.h"
! 27: #include "mdoc.h"
1.1 kristaps 28:
1.71 ! kristaps 29: extern int man_run(struct termp *,
! 30: const struct man *);
! 31: extern int mdoc_run(struct termp *,
! 32: const struct mdoc *);
1.1 kristaps 33:
1.71 ! kristaps 34: static struct termp *term_alloc(enum termenc);
! 35: static void term_free(struct termp *);
! 36: static void term_pword(struct termp *, const char *, int);
! 37: static void term_pescape(struct termp *,
! 38: const char *, int *, int);
! 39: static void term_nescape(struct termp *,
! 40: const char *, size_t);
! 41: static void term_chara(struct termp *, char);
! 42: static void term_stringa(struct termp *,
! 43: const char *, size_t);
! 44: static int term_isopendelim(const char *, int);
! 45: static int term_isclosedelim(const char *, int);
1.1 kristaps 46:
47:
1.71 ! kristaps 48: void *
! 49: ascii_alloc(void)
1.10 kristaps 50: {
1.1 kristaps 51:
1.71 ! kristaps 52: return(term_alloc(TERMENC_ASCII));
1.1 kristaps 53: }
54:
55:
1.71 ! kristaps 56: int
! 57: terminal_run(void *arg, const struct man *man,
! 58: const struct mdoc *mdoc)
1.1 kristaps 59: {
1.71 ! kristaps 60: struct termp *p;
1.1 kristaps 61:
1.71 ! kristaps 62: p = (struct termp *)arg;
1.2 kristaps 63:
1.71 ! kristaps 64: if (NULL == p->symtab)
! 65: p->symtab = term_ascii2htab();
1.2 kristaps 66:
1.71 ! kristaps 67: if (man)
! 68: return(man_run(p, man));
! 69: if (mdoc)
! 70: return(mdoc_run(p, mdoc));
1.2 kristaps 71:
1.10 kristaps 72: return(1);
1.1 kristaps 73: }
74:
75:
1.71 ! kristaps 76: void
! 77: terminal_free(void *arg)
1.11 kristaps 78: {
79:
1.71 ! kristaps 80: term_free((struct termp *)arg);
1.11 kristaps 81: }
82:
83:
1.71 ! kristaps 84: static void
! 85: term_free(struct termp *p)
1.14 kristaps 86: {
87:
1.71 ! kristaps 88: if (p->buf)
! 89: free(p->buf);
! 90: if (TERMENC_ASCII == p->enc && p->symtab)
! 91: term_asciifree(p->symtab);
1.14 kristaps 92:
1.71 ! kristaps 93: free(p);
1.14 kristaps 94: }
95:
96:
1.71 ! kristaps 97: static struct termp *
! 98: term_alloc(enum termenc enc)
1.14 kristaps 99: {
1.71 ! kristaps 100: struct termp *p;
1.14 kristaps 101:
1.71 ! kristaps 102: if (NULL == (p = malloc(sizeof(struct termp))))
! 103: err(1, "malloc");
! 104: bzero(p, sizeof(struct termp));
! 105: p->maxrmargin = 78;
! 106: p->enc = enc;
! 107: return(p);
1.14 kristaps 108: }
109:
110:
111: static int
1.71 ! kristaps 112: term_isclosedelim(const char *p, int len)
1.14 kristaps 113: {
114:
1.71 ! kristaps 115: if (1 != len)
! 116: return(0);
1.14 kristaps 117:
1.71 ! kristaps 118: switch (*p) {
! 119: case('.'):
! 120: /* FALLTHROUGH */
! 121: case(','):
! 122: /* FALLTHROUGH */
! 123: case(';'):
! 124: /* FALLTHROUGH */
! 125: case(':'):
! 126: /* FALLTHROUGH */
! 127: case('?'):
! 128: /* FALLTHROUGH */
! 129: case('!'):
! 130: /* FALLTHROUGH */
! 131: case(')'):
! 132: /* FALLTHROUGH */
! 133: case(']'):
! 134: /* FALLTHROUGH */
! 135: case('}'):
! 136: return(1);
! 137: default:
! 138: break;
! 139: }
1.14 kristaps 140:
1.71 ! kristaps 141: return(0);
1.30 kristaps 142: }
143:
144:
1.14 kristaps 145: static int
1.71 ! kristaps 146: term_isopendelim(const char *p, int len)
1.14 kristaps 147: {
1.43 kristaps 148:
1.71 ! kristaps 149: if (1 != len)
! 150: return(0);
1.14 kristaps 151:
1.71 ! kristaps 152: switch (*p) {
! 153: case('('):
! 154: /* FALLTHROUGH */
! 155: case('['):
! 156: /* FALLTHROUGH */
! 157: case('{'):
! 158: return(1);
! 159: default:
! 160: break;
! 161: }
1.43 kristaps 162:
1.14 kristaps 163: return(0);
164: }
1.15 kristaps 165:
166:
1.71 ! kristaps 167: /*
! 168: * Flush a line of text. A "line" is loosely defined as being something
! 169: * that should be followed by a newline, regardless of whether it's
! 170: * broken apart by newlines getting there. A line can also be a
! 171: * fragment of a columnar list.
! 172: *
! 173: * Specifically, a line is whatever's in p->buf of length p->col, which
! 174: * is zeroed after this function returns.
! 175: *
! 176: * The variables TERMP_NOLPAD, TERMP_LITERAL and TERMP_NOBREAK are of
! 177: * critical importance here. Their behaviour follows:
! 178: *
! 179: * - TERMP_NOLPAD: when beginning to write the line, don't left-pad the
! 180: * offset value. This is useful when doing columnar lists where the
! 181: * prior column has right-padded.
! 182: *
! 183: * - TERMP_NOBREAK: this is the most important and is used when making
! 184: * columns. In short: don't print a newline and instead pad to the
! 185: * right margin. Used in conjunction with TERMP_NOLPAD.
! 186: *
! 187: * - TERMP_NONOBREAK: don't newline when TERMP_NOBREAK is specified.
! 188: *
! 189: * In-line line breaking:
! 190: *
! 191: * If TERMP_NOBREAK is specified and the line overruns the right
! 192: * margin, it will break and pad-right to the right margin after
! 193: * writing. If maxrmargin is violated, it will break and continue
! 194: * writing from the right-margin, which will lead to the above
! 195: * scenario upon exit.
! 196: *
! 197: * Otherwise, the line will break at the right margin. Extremely long
! 198: * lines will cause the system to emit a warning (TODO: hyphenate, if
! 199: * possible).
! 200: */
! 201: void
! 202: term_flushln(struct termp *p)
1.53 kristaps 203: {
1.71 ! kristaps 204: int i, j;
! 205: size_t vsz, vis, maxvis, mmax, bp;
1.53 kristaps 206:
1.71 ! kristaps 207: /*
! 208: * First, establish the maximum columns of "visible" content.
! 209: * This is usually the difference between the right-margin and
! 210: * an indentation, but can be, for tagged lists or columns, a
! 211: * small set of values.
! 212: */
1.53 kristaps 213:
1.71 ! kristaps 214: assert(p->offset < p->rmargin);
! 215: maxvis = p->rmargin - p->offset;
! 216: mmax = p->maxrmargin - p->offset;
! 217: bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
! 218: vis = 0;
1.53 kristaps 219:
1.71 ! kristaps 220: /*
! 221: * If in the standard case (left-justified), then begin with our
! 222: * indentation, otherwise (columns, etc.) just start spitting
! 223: * out text.
! 224: */
1.53 kristaps 225:
1.71 ! kristaps 226: if ( ! (p->flags & TERMP_NOLPAD))
! 227: /* LINTED */
! 228: for (j = 0; j < (int)p->offset; j++)
! 229: putchar(' ');
! 230:
! 231: for (i = 0; i < (int)p->col; i++) {
! 232: /*
! 233: * Count up visible word characters. Control sequences
! 234: * (starting with the CSI) aren't counted. A space
! 235: * generates a non-printing word, which is valid (the
! 236: * space is printed according to regular spacing rules).
! 237: */
! 238:
! 239: /* LINTED */
! 240: for (j = i, vsz = 0; j < (int)p->col; j++) {
! 241: if (' ' == p->buf[j])
! 242: break;
! 243: else if (8 == p->buf[j])
! 244: j += 1;
! 245: else
! 246: vsz++;
! 247: }
1.53 kristaps 248:
1.71 ! kristaps 249: /*
! 250: * Do line-breaking. If we're greater than our
! 251: * break-point and already in-line, break to the next
! 252: * line and start writing. If we're at the line start,
! 253: * then write out the word (TODO: hyphenate) and break
! 254: * in a subsequent loop invocation.
! 255: */
! 256:
! 257: if ( ! (TERMP_NOBREAK & p->flags)) {
! 258: if (vis && vis + vsz > bp) {
! 259: putchar('\n');
! 260: for (j = 0; j < (int)p->offset; j++)
! 261: putchar(' ');
! 262: vis = 0;
! 263: }
! 264: } else if (vis && vis + vsz > bp) {
! 265: putchar('\n');
! 266: for (j = 0; j < (int)p->rmargin; j++)
! 267: putchar(' ');
! 268: vis = p->rmargin - p->offset;
! 269: }
1.53 kristaps 270:
1.71 ! kristaps 271: /*
! 272: * Write out the word and a trailing space. Omit the
! 273: * space if we're the last word in the line or beyond
! 274: * our breakpoint.
! 275: */
! 276:
! 277: for ( ; i < (int)p->col; i++) {
! 278: if (' ' == p->buf[i])
! 279: break;
! 280: putchar(p->buf[i]);
! 281: }
! 282: vis += vsz;
! 283: if (i < (int)p->col && vis <= bp) {
! 284: putchar(' ');
! 285: vis++;
! 286: }
! 287: }
1.15 kristaps 288:
1.71 ! kristaps 289: /*
! 290: * If we've overstepped our maximum visible no-break space, then
! 291: * cause a newline and offset at the right margin.
! 292: */
1.15 kristaps 293:
1.71 ! kristaps 294: if ((TERMP_NOBREAK & p->flags) && vis >= maxvis) {
! 295: if ( ! (TERMP_NONOBREAK & p->flags)) {
! 296: putchar('\n');
! 297: for (i = 0; i < (int)p->rmargin; i++)
! 298: putchar(' ');
! 299: }
! 300: p->col = 0;
1.15 kristaps 301: return;
1.71 ! kristaps 302: }
1.15 kristaps 303:
1.71 ! kristaps 304: /*
! 305: * If we're not to right-marginalise it (newline), then instead
! 306: * pad to the right margin and stay off.
! 307: */
1.15 kristaps 308:
1.71 ! kristaps 309: if (p->flags & TERMP_NOBREAK) {
! 310: if ( ! (TERMP_NONOBREAK & p->flags))
! 311: for ( ; vis < maxvis; vis++)
! 312: putchar(' ');
! 313: } else
! 314: putchar('\n');
1.15 kristaps 315:
1.71 ! kristaps 316: p->col = 0;
1.15 kristaps 317: }
318:
319:
1.71 ! kristaps 320: /*
! 321: * A newline only breaks an existing line; it won't assert vertical
! 322: * space. All data in the output buffer is flushed prior to the newline
! 323: * assertion.
! 324: */
! 325: void
! 326: term_newln(struct termp *p)
1.15 kristaps 327: {
328:
1.71 ! kristaps 329: p->flags |= TERMP_NOSPACE;
! 330: if (0 == p->col) {
! 331: p->flags &= ~TERMP_NOLPAD;
1.15 kristaps 332: return;
1.16 kristaps 333: }
1.71 ! kristaps 334: term_flushln(p);
! 335: p->flags &= ~TERMP_NOLPAD;
1.16 kristaps 336: }
337:
338:
1.71 ! kristaps 339: /*
! 340: * Asserts a vertical space (a full, empty line-break between lines).
! 341: * Note that if used twice, this will cause two blank spaces and so on.
! 342: * All data in the output buffer is flushed prior to the newline
! 343: * assertion.
! 344: */
! 345: void
! 346: term_vspace(struct termp *p)
1.16 kristaps 347: {
348:
1.62 kristaps 349: term_newln(p);
1.71 ! kristaps 350: putchar('\n');
1.16 kristaps 351: }
352:
353:
1.71 ! kristaps 354: /*
! 355: * Break apart a word into "pwords" (partial-words, usually from
! 356: * breaking up a phrase into individual words) and, eventually, put them
! 357: * into the output buffer. If we're a literal word, then don't break up
! 358: * the word and put it verbatim into the output buffer.
! 359: */
! 360: void
! 361: term_word(struct termp *p, const char *word)
1.17 kristaps 362: {
1.71 ! kristaps 363: int i, j, len;
1.17 kristaps 364:
1.71 ! kristaps 365: len = (int)strlen(word);
1.17 kristaps 366:
1.71 ! kristaps 367: if (p->flags & TERMP_LITERAL) {
! 368: term_pword(p, word, len);
! 369: return;
! 370: }
1.17 kristaps 371:
1.71 ! kristaps 372: /* LINTED */
! 373: for (j = i = 0; i < len; i++) {
! 374: if (' ' != word[i]) {
! 375: j++;
! 376: continue;
! 377: }
! 378:
! 379: /* Escaped spaces don't delimit... */
! 380: if (i && ' ' == word[i] && '\\' == word[i - 1]) {
! 381: j++;
! 382: continue;
! 383: }
1.17 kristaps 384:
1.71 ! kristaps 385: if (0 == j)
! 386: continue;
! 387: assert(i >= j);
! 388: term_pword(p, &word[i - j], j);
! 389: j = 0;
! 390: }
! 391: if (j > 0) {
! 392: assert(i >= j);
! 393: term_pword(p, &word[i - j], j);
! 394: }
1.17 kristaps 395: }
396:
397:
1.71 ! kristaps 398: /*
! 399: * Determine the symbol indicated by an escape sequences, that is, one
! 400: * starting with a backslash. Once done, we pass this value into the
! 401: * output buffer by way of the symbol table.
! 402: */
! 403: static void
! 404: term_nescape(struct termp *p, const char *word, size_t len)
1.17 kristaps 405: {
1.71 ! kristaps 406: const char *rhs;
! 407: size_t sz;
1.17 kristaps 408:
1.71 ! kristaps 409: if (NULL == (rhs = term_a2ascii(p->symtab, word, len, &sz)))
! 410: return;
! 411: term_stringa(p, rhs, sz);
1.17 kristaps 412: }
413:
414:
1.71 ! kristaps 415: /*
! 416: * Handle an escape sequence: determine its length and pass it to the
! 417: * escape-symbol look table. Note that we assume mdoc(3) has validated
! 418: * the escape sequence (we assert upon badly-formed escape sequences).
! 419: */
! 420: static void
! 421: term_pescape(struct termp *p, const char *word, int *i, int len)
1.17 kristaps 422: {
1.71 ! kristaps 423: int j;
1.17 kristaps 424:
1.71 ! kristaps 425: if (++(*i) >= len)
! 426: return;
1.17 kristaps 427:
1.71 ! kristaps 428: if ('(' == word[*i]) {
! 429: (*i)++;
! 430: if (*i + 1 >= len)
! 431: return;
1.22 kristaps 432:
1.71 ! kristaps 433: term_nescape(p, &word[*i], 2);
! 434: (*i)++;
! 435: return;
1.22 kristaps 436:
1.71 ! kristaps 437: } else if ('*' == word[*i]) {
! 438: (*i)++;
! 439: if (*i >= len)
! 440: return;
1.22 kristaps 441:
1.71 ! kristaps 442: switch (word[*i]) {
! 443: case ('('):
! 444: (*i)++;
! 445: if (*i + 1 >= len)
! 446: return;
1.65 kristaps 447:
1.71 ! kristaps 448: term_nescape(p, &word[*i], 2);
! 449: (*i)++;
! 450: return;
! 451: case ('['):
! 452: break;
! 453: default:
! 454: term_nescape(p, &word[*i], 1);
! 455: return;
! 456: }
! 457:
! 458: } else if ('f' == word[*i]) {
! 459: (*i)++;
! 460: if (*i >= len)
! 461: return;
! 462: switch (word[*i]) {
! 463: case ('B'):
! 464: p->flags |= TERMP_BOLD;
! 465: break;
! 466: case ('I'):
! 467: p->flags |= TERMP_UNDER;
! 468: break;
! 469: case ('P'):
! 470: /* FALLTHROUGH */
! 471: case ('R'):
! 472: p->flags &= ~TERMP_STYLE;
! 473: break;
! 474: default:
! 475: break;
! 476: }
! 477: return;
1.22 kristaps 478:
1.71 ! kristaps 479: } else if ('[' != word[*i]) {
! 480: term_nescape(p, &word[*i], 1);
! 481: return;
! 482: }
1.28 kristaps 483:
1.71 ! kristaps 484: (*i)++;
! 485: for (j = 0; word[*i] && ']' != word[*i]; (*i)++, j++)
! 486: /* Loop... */ ;
1.28 kristaps 487:
1.71 ! kristaps 488: if (0 == word[*i])
! 489: return;
1.48 kristaps 490:
1.71 ! kristaps 491: term_nescape(p, &word[*i - j], (size_t)j);
1.48 kristaps 492: }
493:
494:
1.71 ! kristaps 495: /*
! 496: * Handle pwords, partial words, which may be either a single word or a
! 497: * phrase that cannot be broken down (such as a literal string). This
! 498: * handles word styling.
! 499: */
! 500: static void
! 501: term_pword(struct termp *p, const char *word, int len)
1.65 kristaps 502: {
1.71 ! kristaps 503: int i;
! 504:
! 505: if (term_isclosedelim(word, len))
! 506: if ( ! (TERMP_IGNDELIM & p->flags))
! 507: p->flags |= TERMP_NOSPACE;
1.65 kristaps 508:
1.71 ! kristaps 509: if ( ! (TERMP_NOSPACE & p->flags))
! 510: term_chara(p, ' ');
1.65 kristaps 511:
1.71 ! kristaps 512: if ( ! (p->flags & TERMP_NONOSPACE))
! 513: p->flags &= ~TERMP_NOSPACE;
1.65 kristaps 514:
1.71 ! kristaps 515: /*
! 516: * If ANSI (word-length styling), then apply our style now,
! 517: * before the word.
! 518: */
1.28 kristaps 519:
1.71 ! kristaps 520: for (i = 0; i < len; i++) {
! 521: if ('\\' == word[i]) {
! 522: term_pescape(p, word, &i, len);
! 523: continue;
! 524: }
1.28 kristaps 525:
1.71 ! kristaps 526: if (TERMP_STYLE & p->flags) {
! 527: if (TERMP_BOLD & p->flags) {
! 528: term_chara(p, word[i]);
! 529: term_chara(p, 8);
! 530: }
! 531: if (TERMP_UNDER & p->flags) {
! 532: term_chara(p, '_');
! 533: term_chara(p, 8);
! 534: }
! 535: }
1.28 kristaps 536:
1.71 ! kristaps 537: term_chara(p, word[i]);
! 538: }
1.65 kristaps 539:
1.71 ! kristaps 540: if (term_isopendelim(word, len))
! 541: p->flags |= TERMP_NOSPACE;
1.65 kristaps 542: }
543:
544:
1.71 ! kristaps 545: /*
! 546: * Like term_chara() but for arbitrary-length buffers. Resize the
! 547: * buffer by a factor of two (if the buffer is less than that) or the
! 548: * buffer's size.
! 549: */
1.65 kristaps 550: static void
1.71 ! kristaps 551: term_stringa(struct termp *p, const char *c, size_t sz)
1.28 kristaps 552: {
1.71 ! kristaps 553: size_t s;
1.28 kristaps 554:
1.71 ! kristaps 555: if (0 == sz)
! 556: return;
1.51 kristaps 557:
1.71 ! kristaps 558: assert(c);
! 559: if (p->col + sz >= p->maxcols) {
! 560: if (0 == p->maxcols)
! 561: p->maxcols = 256;
! 562: s = sz > p->maxcols * 2 ? sz : p->maxcols * 2;
! 563: p->buf = realloc(p->buf, s);
! 564: if (NULL == p->buf)
! 565: err(1, "realloc");
! 566: p->maxcols = s;
1.51 kristaps 567: }
568:
1.71 ! kristaps 569: (void)memcpy(&p->buf[(int)p->col], c, sz);
! 570: p->col += sz;
1.51 kristaps 571: }
572:
573:
1.71 ! kristaps 574: /*
! 575: * Insert a single character into the line-buffer. If the buffer's
! 576: * space is exceeded, then allocate more space by doubling the buffer
! 577: * size.
! 578: */
! 579: static void
! 580: term_chara(struct termp *p, char c)
1.51 kristaps 581: {
1.71 ! kristaps 582: size_t s;
1.51 kristaps 583:
1.71 ! kristaps 584: if (p->col + 1 >= p->maxcols) {
! 585: if (0 == p->maxcols)
! 586: p->maxcols = 256;
! 587: s = p->maxcols * 2;
! 588: p->buf = realloc(p->buf, s);
! 589: if (NULL == p->buf)
! 590: err(1, "realloc");
! 591: p->maxcols = s;
! 592: }
! 593: p->buf[(int)(p->col)++] = c;
1.51 kristaps 594: }
595:
CVSweb