Annotation of mandoc/terminal.c, Revision 1.12
1.12 ! kristaps 1: /* $Id: terminal.c,v 1.11 2009/03/26 16:44:22 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.9 kristaps 29: extern int man_run(struct termp *,
30: const struct man *);
31: extern int mdoc_run(struct termp *,
32: const struct mdoc *);
33:
1.5 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 *,
1.1 kristaps 38: const char *, int *, int);
1.5 kristaps 39: static void term_nescape(struct termp *,
1.1 kristaps 40: const char *, size_t);
1.5 kristaps 41: static void term_chara(struct termp *, char);
42: static void term_stringa(struct termp *,
1.1 kristaps 43: const char *, size_t);
1.6 kristaps 44: static int term_isopendelim(const char *, int);
45: static int term_isclosedelim(const char *, int);
1.1 kristaps 46:
47:
48: void *
49: ascii_alloc(void)
50: {
51:
1.5 kristaps 52: return(term_alloc(TERMENC_ASCII));
1.1 kristaps 53: }
54:
55:
56: int
1.7 kristaps 57: terminal_run(void *arg, const struct man *man,
58: const struct mdoc *mdoc)
1.1 kristaps 59: {
60: struct termp *p;
61:
62: p = (struct termp *)arg;
63:
64: if (NULL == p->symtab)
1.3 kristaps 65: p->symtab = term_ascii2htab();
1.1 kristaps 66:
1.9 kristaps 67: if (man)
68: return(man_run(p, man));
69: if (mdoc)
70: return(mdoc_run(p, mdoc));
1.1 kristaps 71:
72: return(1);
73: }
74:
75:
76: void
77: terminal_free(void *arg)
78: {
79:
1.5 kristaps 80: term_free((struct termp *)arg);
1.1 kristaps 81: }
82:
83:
84: static void
1.5 kristaps 85: term_free(struct termp *p)
1.1 kristaps 86: {
87:
88: if (p->buf)
89: free(p->buf);
90: if (TERMENC_ASCII == p->enc && p->symtab)
1.3 kristaps 91: term_asciifree(p->symtab);
1.1 kristaps 92:
93: free(p);
94: }
95:
96:
97: static struct termp *
1.5 kristaps 98: term_alloc(enum termenc enc)
1.1 kristaps 99: {
100: struct termp *p;
101:
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);
108: }
109:
110:
1.5 kristaps 111: static int
1.6 kristaps 112: term_isclosedelim(const char *p, int len)
1.5 kristaps 113: {
114:
115: if (1 != len)
116: return(0);
117:
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: }
140:
141: return(0);
142: }
143:
144:
145: static int
1.6 kristaps 146: term_isopendelim(const char *p, int len)
1.5 kristaps 147: {
148:
149: if (1 != len)
150: return(0);
151:
152: switch (*p) {
153: case('('):
154: /* FALLTHROUGH */
155: case('['):
156: /* FALLTHROUGH */
157: case('{'):
158: return(1);
159: default:
160: break;
161: }
162:
163: return(0);
164: }
165:
166:
1.1 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
1.3 kristaps 202: term_flushln(struct termp *p)
1.1 kristaps 203: {
204: int i, j;
205: size_t vsz, vis, maxvis, mmax, bp;
206:
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: */
213:
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;
219:
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: */
225:
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: }
248:
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;
1.4 kristaps 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;
1.1 kristaps 269: }
270:
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: }
288:
289: /*
290: * If we've overstepped our maximum visible no-break space, then
291: * cause a newline and offset at the right margin.
292: */
293:
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;
301: return;
302: }
303:
304: /*
305: * If we're not to right-marginalise it (newline), then instead
306: * pad to the right margin and stay off.
307: */
308:
309: if (p->flags & TERMP_NOBREAK) {
310: if ( ! (TERMP_NONOBREAK & p->flags))
311: for ( ; vis < maxvis; vis++)
312: putchar(' ');
313: } else
314: putchar('\n');
315:
316: p->col = 0;
317: }
318:
319:
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
1.3 kristaps 326: term_newln(struct termp *p)
1.1 kristaps 327: {
328:
329: p->flags |= TERMP_NOSPACE;
330: if (0 == p->col) {
331: p->flags &= ~TERMP_NOLPAD;
332: return;
333: }
1.3 kristaps 334: term_flushln(p);
1.1 kristaps 335: p->flags &= ~TERMP_NOLPAD;
336: }
337:
338:
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
1.3 kristaps 346: term_vspace(struct termp *p)
1.1 kristaps 347: {
348:
1.3 kristaps 349: term_newln(p);
1.1 kristaps 350: putchar('\n');
351: }
352:
353:
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
1.3 kristaps 361: term_word(struct termp *p, const char *word)
1.1 kristaps 362: {
363: int i, j, len;
364:
1.3 kristaps 365: len = (int)strlen(word);
366:
1.1 kristaps 367: if (p->flags & TERMP_LITERAL) {
1.5 kristaps 368: term_pword(p, word, len);
1.1 kristaps 369: return;
370: }
371:
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: }
384:
385: if (0 == j)
386: continue;
387: assert(i >= j);
1.5 kristaps 388: term_pword(p, &word[i - j], j);
1.1 kristaps 389: j = 0;
390: }
391: if (j > 0) {
392: assert(i >= j);
1.5 kristaps 393: term_pword(p, &word[i - j], j);
1.1 kristaps 394: }
395: }
396:
397:
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
1.5 kristaps 404: term_nescape(struct termp *p, const char *word, size_t len)
1.1 kristaps 405: {
406: const char *rhs;
407: size_t sz;
408:
1.3 kristaps 409: if (NULL == (rhs = term_a2ascii(p->symtab, word, len, &sz)))
1.1 kristaps 410: return;
1.5 kristaps 411: term_stringa(p, rhs, sz);
1.1 kristaps 412: }
413:
414:
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
1.5 kristaps 421: term_pescape(struct termp *p, const char *word, int *i, int len)
1.1 kristaps 422: {
423: int j;
424:
425: if (++(*i) >= len)
426: return;
427:
428: if ('(' == word[*i]) {
429: (*i)++;
430: if (*i + 1 >= len)
431: return;
432:
1.5 kristaps 433: term_nescape(p, &word[*i], 2);
1.1 kristaps 434: (*i)++;
435: return;
436:
437: } else if ('*' == word[*i]) {
438: (*i)++;
439: if (*i >= len)
440: return;
441:
442: switch (word[*i]) {
443: case ('('):
444: (*i)++;
445: if (*i + 1 >= len)
446: return;
447:
1.5 kristaps 448: term_nescape(p, &word[*i], 2);
1.1 kristaps 449: (*i)++;
450: return;
451: case ('['):
452: break;
453: default:
1.5 kristaps 454: term_nescape(p, &word[*i], 1);
1.1 kristaps 455: return;
456: }
1.11 kristaps 457:
458: } else if ('f' == word[*i]) {
1.12 ! kristaps 459: (*i)++;
! 460: if (*i >= len)
1.11 kristaps 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.1 kristaps 478:
479: } else if ('[' != word[*i]) {
1.5 kristaps 480: term_nescape(p, &word[*i], 1);
1.1 kristaps 481: return;
482: }
483:
484: (*i)++;
485: for (j = 0; word[*i] && ']' != word[*i]; (*i)++, j++)
486: /* Loop... */ ;
487:
488: if (0 == word[*i])
489: return;
490:
1.5 kristaps 491: term_nescape(p, &word[*i - j], (size_t)j);
1.1 kristaps 492: }
493:
494:
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
1.5 kristaps 501: term_pword(struct termp *p, const char *word, int len)
1.1 kristaps 502: {
503: int i;
504:
1.5 kristaps 505: if (term_isclosedelim(word, len))
506: if ( ! (TERMP_IGNDELIM & p->flags))
507: p->flags |= TERMP_NOSPACE;
508:
1.3 kristaps 509: if ( ! (TERMP_NOSPACE & p->flags))
1.5 kristaps 510: term_chara(p, ' ');
1.1 kristaps 511:
512: if ( ! (p->flags & TERMP_NONOSPACE))
513: p->flags &= ~TERMP_NOSPACE;
514:
515: /*
516: * If ANSI (word-length styling), then apply our style now,
517: * before the word.
518: */
519:
520: for (i = 0; i < len; i++) {
521: if ('\\' == word[i]) {
1.5 kristaps 522: term_pescape(p, word, &i, len);
1.1 kristaps 523: continue;
524: }
525:
526: if (TERMP_STYLE & p->flags) {
527: if (TERMP_BOLD & p->flags) {
1.5 kristaps 528: term_chara(p, word[i]);
529: term_chara(p, 8);
1.1 kristaps 530: }
531: if (TERMP_UNDER & p->flags) {
1.5 kristaps 532: term_chara(p, '_');
533: term_chara(p, 8);
1.1 kristaps 534: }
535: }
536:
1.5 kristaps 537: term_chara(p, word[i]);
1.1 kristaps 538: }
1.5 kristaps 539:
540: if (term_isopendelim(word, len))
541: p->flags |= TERMP_NOSPACE;
1.1 kristaps 542: }
543:
544:
545: /*
1.5 kristaps 546: * Like term_chara() but for arbitrary-length buffers. Resize the
1.1 kristaps 547: * buffer by a factor of two (if the buffer is less than that) or the
548: * buffer's size.
549: */
550: static void
1.5 kristaps 551: term_stringa(struct termp *p, const char *c, size_t sz)
1.1 kristaps 552: {
553: size_t s;
554:
555: if (0 == sz)
556: return;
557:
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;
567: }
568:
569: (void)memcpy(&p->buf[(int)p->col], c, sz);
570: p->col += sz;
571: }
572:
573:
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
1.5 kristaps 580: term_chara(struct termp *p, char c)
1.1 kristaps 581: {
582: size_t s;
583:
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;
594: }
595:
CVSweb