]> git.cameronkatri.com Git - mandoc.git/blob - mdocterm.c
26304acaff11bbb5e983a4c74eb7f80fc57928e6
[mandoc.git] / mdocterm.c
1 /* $Id: mdocterm.c,v 1.30 2009/03/04 14:04:02 kristaps Exp $ */
2 /*
3 * Copyright (c) 2008 Kristaps Dzonsons <kristaps@kth.se>
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 <sys/utsname.h>
20
21 #include <assert.h>
22 #include <ctype.h>
23 #include <err.h>
24 #include <getopt.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #ifndef __OpenBSD__
29 #include <time.h>
30 #endif
31
32 #include "mmain.h"
33 #include "term.h"
34
35 struct termenc {
36 const char *enc;
37 int sym;
38 };
39
40 static void body(struct termp *,
41 struct termpair *,
42 const struct mdoc_meta *,
43 const struct mdoc_node *);
44 static void header(struct termp *,
45 const struct mdoc_meta *);
46 static void footer(struct termp *,
47 const struct mdoc_meta *);
48
49 static void pword(struct termp *, const char *, size_t);
50 static void pescape(struct termp *, const char *,
51 size_t *, size_t);
52 static void nescape(struct termp *,
53 const char *, size_t);
54 static void chara(struct termp *, char);
55 static void stringa(struct termp *,
56 const char *, size_t);
57 static void symbola(struct termp *, enum tsym);
58 static void stylea(struct termp *, enum tstyle);
59
60 #ifdef __linux__
61 extern size_t strlcat(char *, const char *, size_t);
62 extern size_t strlcpy(char *, const char *, size_t);
63 #endif
64
65 static struct termenc termenc1[] = {
66 { "\\", TERMSYM_SLASH },
67 { "\'", TERMSYM_RSQUOTE },
68 { "`", TERMSYM_LSQUOTE },
69 { "-", TERMSYM_HYPHEN },
70 { " ", TERMSYM_SPACE },
71 { ".", TERMSYM_PERIOD },
72 { "&", TERMSYM_BREAK },
73 { "e", TERMSYM_SLASH },
74 { "q", TERMSYM_DQUOTE },
75 { NULL, 0 }
76 };
77
78 static struct termenc termenc2[] = {
79 { "rB", TERMSYM_RBRACK },
80 { "lB", TERMSYM_LBRACK },
81 { "Lq", TERMSYM_LDQUOTE },
82 { "lq", TERMSYM_LDQUOTE },
83 { "Rq", TERMSYM_RDQUOTE },
84 { "rq", TERMSYM_RDQUOTE },
85 { "oq", TERMSYM_LSQUOTE },
86 { "aq", TERMSYM_RSQUOTE },
87
88 { "<-", TERMSYM_LARROW },
89 { "->", TERMSYM_RARROW },
90 { "ua", TERMSYM_UARROW },
91 { "da", TERMSYM_DARROW },
92
93 { "bu", TERMSYM_BULLET },
94 { "Ba", TERMSYM_BAR },
95 { "ba", TERMSYM_BAR },
96 { "co", TERMSYM_COPY },
97 { "Am", TERMSYM_AMP },
98
99 { "Le", TERMSYM_LE },
100 { "<=", TERMSYM_LE },
101 { "Ge", TERMSYM_GE },
102 { "=>", TERMSYM_GE },
103 { "==", TERMSYM_EQ },
104 { "Ne", TERMSYM_NEQ },
105 { "!=", TERMSYM_NEQ },
106 { "Pm", TERMSYM_PLUSMINUS },
107 { "+-", TERMSYM_PLUSMINUS },
108 { "If", TERMSYM_INF2 },
109 { "if", TERMSYM_INF },
110 { "Na", TERMSYM_NAN },
111 { "na", TERMSYM_NAN },
112 { "**", TERMSYM_ASTERISK },
113 { "Gt", TERMSYM_GT },
114 { "Lt", TERMSYM_LT },
115
116 { "aa", TERMSYM_ACUTE },
117 { "ga", TERMSYM_GRAVE },
118
119 { "en", TERMSYM_EN },
120 { "em", TERMSYM_EM },
121
122 { "Pi", TERMSYM_PI },
123 { NULL, 0 }
124 };
125
126 static struct termsym termsym_ansi[] = {
127 { "]", 1 }, /* TERMSYM_RBRACK */
128 { "[", 1 }, /* TERMSYM_LBRACK */
129 { "<-", 2 }, /* TERMSYM_LARROW */
130 { "->", 2 }, /* TERMSYM_RARROW */
131 { "^", 1 }, /* TERMSYM_UARROW */
132 { "v", 1 }, /* TERMSYM_DARROW */
133 { "`", 1 }, /* TERMSYM_LSQUOTE */
134 { "\'", 1 }, /* TERMSYM_RSQUOTE */
135 { "\'", 1 }, /* TERMSYM_SQUOTE */
136 { "``", 2 }, /* TERMSYM_LDQUOTE */
137 { "\'\'", 2 }, /* TERMSYM_RDQUOTE */
138 { "\"", 1 }, /* TERMSYM_DQUOTE */
139 { "<", 1 }, /* TERMSYM_LT */
140 { ">", 1 }, /* TERMSYM_GT */
141 { "<=", 2 }, /* TERMSYM_LE */
142 { ">=", 2 }, /* TERMSYM_GE */
143 { "==", 2 }, /* TERMSYM_EQ */
144 { "!=", 2 }, /* TERMSYM_NEQ */
145 { "\'", 1 }, /* TERMSYM_ACUTE */
146 { "`", 1 }, /* TERMSYM_GRAVE */
147 { "pi", 2 }, /* TERMSYM_PI */
148 { "+=", 2 }, /* TERMSYM_PLUSMINUS */
149 { "oo", 2 }, /* TERMSYM_INF */
150 { "infinity", 8 }, /* TERMSYM_INF2 */
151 { "NaN", 3 }, /* TERMSYM_NAN */
152 { "|", 1 }, /* TERMSYM_BAR */
153 { "o", 1 }, /* TERMSYM_BULLET */
154 { "&", 1 }, /* TERMSYM_AMP */
155 { "--", 2 }, /* TERMSYM_EM */
156 { "-", 1 }, /* TERMSYM_EN */
157 { "(C)", 3 }, /* TERMSYM_COPY */
158 { "*", 1 }, /* TERMSYM_ASTERISK */
159 { "\\", 1 }, /* TERMSYM_SLASH */
160 { "-", 1 }, /* TERMSYM_HYPHEN */
161 { " ", 1 }, /* TERMSYM_SPACE */
162 { ".", 1 }, /* TERMSYM_PERIOD */
163 { "", 0 }, /* TERMSYM_BREAK */
164 };
165
166 static const char ansi_clear[] = { 27, '[', '0', 'm' };
167 static const char ansi_bold[] = { 27, '[', '1', 'm' };
168 static const char ansi_under[] = { 27, '[', '4', 'm' };
169
170 static struct termsym termstyle_ansi[] = {
171 { ansi_clear, 4 },
172 { ansi_bold, 4 },
173 { ansi_under, 4 }
174 };
175
176
177 int
178 main(int argc, char *argv[])
179 {
180 struct mmain *p;
181 const struct mdoc *mdoc;
182 struct termp termp;
183
184 p = mmain_alloc();
185
186 if ( ! mmain_getopt(p, argc, argv, NULL, NULL, NULL, NULL))
187 mmain_exit(p, 1);
188
189 if (NULL == (mdoc = mmain_mdoc(p)))
190 mmain_exit(p, 1);
191
192 termp.maxrmargin = termp.rmargin = 78; /* XXX */
193 termp.maxcols = 1024;
194 termp.offset = termp.col = 0;
195 termp.flags = TERMP_NOSPACE;
196 termp.symtab = termsym_ansi;
197 termp.styletab = termstyle_ansi;
198
199 if (NULL == (termp.buf = malloc(termp.maxcols)))
200 err(1, "malloc");
201
202 header(&termp, mdoc_meta(mdoc));
203 body(&termp, NULL, mdoc_meta(mdoc), mdoc_node(mdoc));
204 footer(&termp, mdoc_meta(mdoc));
205
206 free(termp.buf);
207
208 mmain_exit(p, 0);
209 /* NOTREACHED */
210 }
211
212
213 /*
214 * Flush a line of text. A "line" is loosely defined as being something
215 * that should be followed by a newline, regardless of whether it's
216 * broken apart by newlines getting there. A line can also be a
217 * fragment of a columnar list.
218 *
219 * Specifically, a line is whatever's in p->buf of length p->col, which
220 * is zeroed after this function returns.
221 *
222 * The variables TERMP_NOLPAD, TERMP_LITERAL and TERMP_NOBREAK are of
223 * critical importance here. Their behaviour follows:
224 *
225 * - TERMP_NOLPAD: when beginning to write the line, don't left-pad the
226 * offset value. This is useful when doing columnar lists where the
227 * prior column has right-padded.
228 *
229 * - TERMP_NOBREAK: this is the most important and is used when making
230 * columns. In short: don't print a newline and instead pad to the
231 * right margin. Used in conjunction with TERMP_NOLPAD.
232 *
233 * In-line line breaking:
234 *
235 * If TERMP_NOBREAK is specified and the line overruns the right
236 * margin, it will break and pad-right to the right margin after
237 * writing. If maxrmargin is violated, it will break and continue
238 * writing from the right-margin, which will lead to the above
239 * scenario upon exit.
240 *
241 * Otherwise, the line will break at the right margin. Extremely long
242 * lines will cause the system to emit a warning (TODO: hyphenate, if
243 * possible).
244 */
245 void
246 flushln(struct termp *p)
247 {
248 size_t i, j, vsz, vis, maxvis, mmax, bp;
249
250 /*
251 * First, establish the maximum columns of "visible" content.
252 * This is usually the difference between the right-margin and
253 * an indentation, but can be, for tagged lists or columns, a
254 * small set of values.
255 */
256
257 assert(p->offset < p->rmargin);
258 maxvis = p->rmargin - p->offset;
259 mmax = p->maxrmargin - p->offset;
260 bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
261 vis = 0;
262
263 /*
264 * If in the standard case (left-justified), then begin with our
265 * indentation, otherwise (columns, etc.) just start spitting
266 * out text.
267 */
268
269 if ( ! (p->flags & TERMP_NOLPAD))
270 /* LINTED */
271 for (j = 0; j < p->offset; j++)
272 putchar(' ');
273
274 for (i = 0; i < p->col; i++) {
275 /*
276 * Count up visible word characters. Control sequences
277 * (starting with the CSI) aren't counted. A space
278 * generates a non-printing word, which is valid (the
279 * space is printed according to regular spacing rules).
280 */
281
282 /* FIXME: make non-ANSI friendly. */
283
284 /* LINTED */
285 for (j = i, vsz = 0; j < p->col; j++) {
286 if (isspace((int)p->buf[j]))
287 break;
288 else if (27 == p->buf[j]) {
289 assert(j + 4 <= p->col);
290 j += 3;
291 } else
292 vsz++;
293 }
294
295 /*
296 * Do line-breaking. If we're greater than our
297 * break-point and already in-line, break to the next
298 * line and start writing. If we're at the line start,
299 * then write out the word (TODO: hyphenate) and break
300 * in a subsequent loop invocation.
301 */
302
303 if ( ! (TERMP_NOBREAK & p->flags)) {
304 if (vis && vis + vsz > bp) {
305 putchar('\n');
306 for (j = 0; j < p->offset; j++)
307 putchar(' ');
308 vis = 0;
309 } else if (vis + vsz > bp)
310 warnx("word breaks right margin");
311
312 /* TODO: hyphenate. */
313
314 } else {
315 if (vis && vis + vsz > bp) {
316 putchar('\n');
317 for (j = 0; j < p->rmargin; j++)
318 putchar(' ');
319 vis = p->rmargin - p->offset;
320 } else if (vis + vsz > bp)
321 warnx("word breaks right margin");
322
323 /* TODO: hyphenate. */
324 }
325
326 /*
327 * Write out the word and a trailing space. Omit the
328 * space if we're the last word in the line or beyond
329 * our breakpoint.
330 */
331
332 for ( ; i < p->col; i++) {
333 if (isspace((int)p->buf[i]))
334 break;
335 putchar(p->buf[i]);
336 }
337 vis += vsz;
338 if (i < p->col && vis <= bp) {
339 putchar(' ');
340 vis++;
341 }
342 }
343
344 /*
345 * If we've overstepped our maximum visible no-break space, then
346 * cause a newline and offset at the right margin.
347 */
348
349 if ((TERMP_NOBREAK & p->flags) && vis >= maxvis) {
350 putchar('\n');
351 for (i = 0; i < p->rmargin; i++)
352 putchar(' ');
353 p->col = 0;
354 return;
355 }
356
357 /*
358 * If we're not to right-marginalise it (newline), then instead
359 * pad to the right margin and stay off.
360 */
361
362 if (p->flags & TERMP_NOBREAK)
363 for ( ; vis < maxvis; vis++)
364 putchar(' ');
365 else
366 putchar('\n');
367
368 p->col = 0;
369 }
370
371
372 /*
373 * A newline only breaks an existing line; it won't assert vertical
374 * space. All data in the output buffer is flushed prior to the newline
375 * assertion.
376 */
377 void
378 newln(struct termp *p)
379 {
380
381 p->flags |= TERMP_NOSPACE;
382 if (0 == p->col) {
383 p->flags &= ~TERMP_NOLPAD;
384 return;
385 }
386 flushln(p);
387 p->flags &= ~TERMP_NOLPAD;
388 }
389
390
391 /*
392 * Asserts a vertical space (a full, empty line-break between lines).
393 * Note that if used twice, this will cause two blank spaces and so on.
394 * All data in the output buffer is flushed prior to the newline
395 * assertion.
396 */
397 void
398 vspace(struct termp *p)
399 {
400
401 newln(p);
402 putchar('\n');
403 }
404
405
406 /*
407 * Break apart a word into "pwords" (partial-words, usually from
408 * breaking up a phrase into individual words) and, eventually, put them
409 * into the output buffer. If we're a literal word, then don't break up
410 * the word and put it verbatim into the output buffer.
411 */
412 void
413 word(struct termp *p, const char *word)
414 {
415 size_t i, j, len;
416
417 if (p->flags & TERMP_LITERAL) {
418 pword(p, word, strlen(word));
419 return;
420 }
421
422 len = strlen(word);
423 assert(len > 0);
424
425 if (mdoc_isdelim(word)) {
426 if ( ! (p->flags & TERMP_IGNDELIM))
427 p->flags |= TERMP_NOSPACE;
428 p->flags &= ~TERMP_IGNDELIM;
429 }
430
431 /* LINTED */
432 for (j = i = 0; i < len; i++) {
433 if ( ! isspace((int)word[i])) {
434 j++;
435 continue;
436 }
437
438 /* Escaped spaces don't delimit... */
439 if (i > 0 && isspace((int)word[i]) &&
440 '\\' == word[i - 1]) {
441 j++;
442 continue;
443 }
444
445 if (0 == j)
446 continue;
447 assert(i >= j);
448 pword(p, &word[i - j], j);
449 j = 0;
450 }
451 if (j > 0) {
452 assert(i >= j);
453 pword(p, &word[i - j], j);
454 }
455 }
456
457
458 /*
459 * This is the main function for printing out nodes. It's constituted
460 * of PRE and POST functions, which correspond to prefix and infix
461 * processing. The termpair structure allows data to persist between
462 * prefix and postfix invocations.
463 */
464 static void
465 body(struct termp *p, struct termpair *ppair,
466 const struct mdoc_meta *meta,
467 const struct mdoc_node *node)
468 {
469 int dochild;
470 struct termpair pair;
471
472 /* Pre-processing. */
473
474 dochild = 1;
475 pair.ppair = ppair;
476 pair.type = 0;
477 pair.offset = pair.rmargin = 0;
478 pair.flag = 0;
479 pair.count = 0;
480
481 if (MDOC_TEXT != node->type) {
482 if (termacts[node->tok].pre)
483 if ( ! (*termacts[node->tok].pre)(p, &pair, meta, node))
484 dochild = 0;
485 } else /* MDOC_TEXT == node->type */
486 word(p, node->data.text.string);
487
488 /* Children. */
489
490 if (TERMPAIR_FLAG & pair.type)
491 p->flags |= pair.flag;
492
493 if (dochild && node->child)
494 body(p, &pair, meta, node->child);
495
496 if (TERMPAIR_FLAG & pair.type)
497 p->flags &= ~pair.flag;
498
499 /* Post-processing. */
500
501 if (MDOC_TEXT != node->type)
502 if (termacts[node->tok].post)
503 (*termacts[node->tok].post)(p, &pair, meta, node);
504
505 /* Siblings. */
506
507 if (node->next)
508 body(p, ppair, meta, node->next);
509 }
510
511
512 static void
513 footer(struct termp *p, const struct mdoc_meta *meta)
514 {
515 struct tm *tm;
516 char *buf, *os;
517
518 if (NULL == (buf = malloc(p->rmargin)))
519 err(1, "malloc");
520 if (NULL == (os = malloc(p->rmargin)))
521 err(1, "malloc");
522
523 tm = localtime(&meta->date);
524
525 #ifdef __OpenBSD__
526 if (NULL == strftime(buf, p->rmargin, "%B %d, %Y", tm))
527 #else
528 if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm))
529 #endif
530 err(1, "strftime");
531
532 (void)strlcpy(os, meta->os, p->rmargin);
533
534 /*
535 * This is /slightly/ different from regular groff output
536 * because we don't have page numbers. Print the following:
537 *
538 * OS MDOCDATE
539 */
540
541 vspace(p);
542
543 p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
544 p->rmargin = p->maxrmargin - strlen(buf);
545 p->offset = 0;
546
547 word(p, os);
548 flushln(p);
549
550 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
551 p->offset = p->rmargin;
552 p->rmargin = p->maxrmargin;
553 p->flags &= ~TERMP_NOBREAK;
554
555 word(p, buf);
556 flushln(p);
557
558 free(buf);
559 free(os);
560 }
561
562
563 static void
564 header(struct termp *p, const struct mdoc_meta *meta)
565 {
566 char *buf, *title, *bufp, *vbuf;
567 const char *pp;
568 struct utsname uts;
569
570 p->rmargin = p->maxrmargin;
571 p->offset = 0;
572
573 if (NULL == (buf = malloc(p->rmargin)))
574 err(1, "malloc");
575 if (NULL == (title = malloc(p->rmargin)))
576 err(1, "malloc");
577 if (NULL == (vbuf = malloc(p->rmargin)))
578 err(1, "malloc");
579
580 if (NULL == (pp = mdoc_vol2a(meta->vol))) {
581 switch (meta->msec) {
582 case (MSEC_1):
583 /* FALLTHROUGH */
584 case (MSEC_6):
585 /* FALLTHROUGH */
586 case (MSEC_7):
587 pp = mdoc_vol2a(VOL_URM);
588 break;
589 case (MSEC_8):
590 pp = mdoc_vol2a(VOL_SMM);
591 break;
592 case (MSEC_2):
593 /* FALLTHROUGH */
594 case (MSEC_3):
595 /* FALLTHROUGH */
596 case (MSEC_4):
597 /* FALLTHROUGH */
598 case (MSEC_5):
599 pp = mdoc_vol2a(VOL_PRM);
600 break;
601 case (MSEC_9):
602 pp = mdoc_vol2a(VOL_KM);
603 break;
604 default:
605 break;
606 }
607 }
608 vbuf[0] = 0;
609
610 if (pp) {
611 if (-1 == uname(&uts))
612 err(1, "uname");
613 (void)strlcat(vbuf, uts.sysname, p->rmargin);
614 (void)strlcat(vbuf, " ", p->rmargin);
615 } else if (NULL == (pp = mdoc_msec2a(meta->msec)))
616 pp = mdoc_msec2a(MSEC_local);
617
618 (void)strlcat(vbuf, pp, p->rmargin);
619
620 /*
621 * The header is strange. It has three components, which are
622 * really two with the first duplicated. It goes like this:
623 *
624 * IDENTIFIER TITLE IDENTIFIER
625 *
626 * The IDENTIFIER is NAME(SECTION), which is the command-name
627 * (if given, or "unknown" if not) followed by the manual page
628 * section. These are given in `Dt'. The TITLE is a free-form
629 * string depending on the manual volume. If not specified, it
630 * switches on the manual section.
631 */
632
633 if (mdoc_arch2a(meta->arch))
634 (void)snprintf(buf, p->rmargin, "%s (%s)",
635 vbuf, mdoc_arch2a(meta->arch));
636 else
637 (void)strlcpy(buf, vbuf, p->rmargin);
638
639 pp = mdoc_msec2a(meta->msec);
640
641 (void)snprintf(title, p->rmargin, "%s(%s)",
642 meta->title, pp ? pp : "");
643
644 for (bufp = title; *bufp; bufp++)
645 *bufp = toupper(*bufp);
646
647 p->offset = 0;
648 p->rmargin = (p->maxrmargin - strlen(buf)) / 2;
649 p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
650
651 word(p, title);
652 flushln(p);
653
654 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
655 p->offset = p->rmargin;
656 p->rmargin = p->maxrmargin - strlen(title);
657
658 word(p, buf);
659 flushln(p);
660
661 p->offset = p->rmargin;
662 p->rmargin = p->maxrmargin;
663 p->flags &= ~TERMP_NOBREAK;
664 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
665
666 word(p, title);
667 flushln(p);
668
669 p->rmargin = p->maxrmargin;
670 p->offset = 0;
671 p->flags &= ~TERMP_NOSPACE;
672
673 free(title);
674 free(vbuf);
675 free(buf);
676 }
677
678
679 /*
680 * Determine the symbol indicated by an escape sequences, that is, one
681 * starting with a backslash. Once done, we pass this value into the
682 * output buffer by way of the symbol table.
683 */
684 static void
685 nescape(struct termp *p, const char *word, size_t len)
686 {
687 struct termenc *enc;
688
689 switch (len) {
690 case (1):
691 enc = termenc1;
692 break;
693 case (2):
694 enc = termenc2;
695 break;
696 default:
697 warnx("unsupported %zu-byte escape sequence", len);
698 return;
699 }
700
701 for ( ; enc->enc; enc++)
702 if (0 == memcmp(enc->enc, word, len)) {
703 symbola(p, enc->sym);
704 return;
705 }
706
707 warnx("unsupported %zu-byte escape sequence", len);
708 }
709
710
711 /*
712 * Handle an escape sequence: determine its length and pass it to the
713 * escape-symbol look table. Note that we assume mdoc(3) has validated
714 * the escape sequence (we assert upon badly-formed escape sequences).
715 */
716 static void
717 pescape(struct termp *p, const char *word, size_t *i, size_t len)
718 {
719 size_t j;
720
721 (*i)++;
722 assert(*i < len);
723
724 if ('(' == word[*i]) {
725 (*i)++;
726 assert(*i + 1 < len);
727 nescape(p, &word[*i], 2);
728 (*i)++;
729 return;
730
731 } else if ('*' == word[*i]) {
732 /* XXX - deprecated! */
733 (*i)++;
734 assert(*i < len);
735 switch (word[*i]) {
736 case ('('):
737 (*i)++;
738 assert(*i + 1 < len);
739 nescape(p, &word[*i], 2);
740 (*i)++;
741 return;
742 case ('['):
743 break;
744 default:
745 nescape(p, &word[*i], 1);
746 return;
747 }
748
749 } else if ('[' != word[*i]) {
750 nescape(p, &word[*i], 1);
751 return;
752 }
753
754 (*i)++;
755 for (j = 0; word[*i] && ']' != word[*i]; (*i)++, j++)
756 /* Loop... */ ;
757
758 assert(word[*i]);
759 nescape(p, &word[*i - j], j);
760 }
761
762
763 /*
764 * Handle pwords, partial words, which may be either a single word or a
765 * phrase that cannot be broken down (such as a literal string). This
766 * handles word styling.
767 */
768 static void
769 pword(struct termp *p, const char *word, size_t len)
770 {
771 size_t i;
772
773 if ( ! (TERMP_NOSPACE & p->flags) &&
774 ! (TERMP_LITERAL & p->flags))
775 chara(p, ' ');
776
777 if ( ! (p->flags & TERMP_NONOSPACE))
778 p->flags &= ~TERMP_NOSPACE;
779
780 /*
781 * XXX - if literal and underlining, this will underline the
782 * spaces between literal words.
783 */
784
785 if (p->flags & TERMP_BOLD)
786 stylea(p, TERMSTYLE_BOLD);
787 if (p->flags & TERMP_UNDERLINE)
788 stylea(p, TERMSTYLE_UNDER);
789
790 for (i = 0; i < len; i++) {
791 if ('\\' == word[i]) {
792 pescape(p, word, &i, len);
793 continue;
794 }
795 chara(p, word[i]);
796 }
797
798 if (p->flags & TERMP_BOLD ||
799 p->flags & TERMP_UNDERLINE)
800 stylea(p, TERMSTYLE_CLEAR);
801 }
802
803
804 /*
805 * Add a symbol to the output line buffer.
806 */
807 static void
808 symbola(struct termp *p, enum tsym sym)
809 {
810
811 assert(p->symtab[sym].sym);
812 stringa(p, p->symtab[sym].sym, p->symtab[sym].sz);
813 }
814
815
816 /*
817 * Add a style to the output line buffer.
818 */
819 static void
820 stylea(struct termp *p, enum tstyle style)
821 {
822
823 assert(p->styletab[style].sym);
824 stringa(p, p->styletab[style].sym, p->styletab[style].sz);
825 }
826
827
828 /*
829 * Like chara() but for arbitrary-length buffers. Resize the buffer by
830 * a factor of two (if the buffer is less than that) or the buffer's
831 * size.
832 */
833 static void
834 stringa(struct termp *p, const char *c, size_t sz)
835 {
836 size_t s;
837
838 if (0 == sz)
839 return;
840
841 s = sz > p->maxcols * 2 ? sz : p->maxcols * 2;
842
843 assert(c);
844 if (p->col + sz >= p->maxcols) {
845 p->buf = realloc(p->buf, s);
846 if (NULL == p->buf)
847 err(1, "realloc");
848 p->maxcols = s;
849 }
850
851 (void)memcpy(&p->buf[p->col], c, sz);
852 p->col += sz;
853 }
854
855
856 /*
857 * Insert a single character into the line-buffer. If the buffer's
858 * space is exceeded, then allocate more space by doubling the buffer
859 * size.
860 */
861 static void
862 chara(struct termp *p, char c)
863 {
864
865 if (p->col + 1 >= p->maxcols) {
866 p->buf = realloc(p->buf, p->maxcols * 2);
867 if (NULL == p->buf)
868 err(1, "malloc");
869 p->maxcols *= 2;
870 }
871 p->buf[(p->col)++] = c;
872 }