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