1*d40754b0Schristos /* $NetBSD: fmt.c,v 1.33 2017/10/13 00:11:56 christos Exp $ */
27642736cSjtc
361f28255Scgd /*
47642736cSjtc * Copyright (c) 1980, 1993
57642736cSjtc * The Regents of the University of California. All rights reserved.
661f28255Scgd *
761f28255Scgd * Redistribution and use in source and binary forms, with or without
861f28255Scgd * modification, are permitted provided that the following conditions
961f28255Scgd * are met:
1061f28255Scgd * 1. Redistributions of source code must retain the above copyright
1161f28255Scgd * notice, this list of conditions and the following disclaimer.
1261f28255Scgd * 2. Redistributions in binary form must reproduce the above copyright
1361f28255Scgd * notice, this list of conditions and the following disclaimer in the
1461f28255Scgd * documentation and/or other materials provided with the distribution.
1589aaa1bbSagc * 3. Neither the name of the University nor the names of its contributors
1661f28255Scgd * may be used to endorse or promote products derived from this software
1761f28255Scgd * without specific prior written permission.
1861f28255Scgd *
1961f28255Scgd * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2061f28255Scgd * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2161f28255Scgd * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2261f28255Scgd * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2361f28255Scgd * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2461f28255Scgd * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2561f28255Scgd * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2661f28255Scgd * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2761f28255Scgd * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2861f28255Scgd * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2961f28255Scgd * SUCH DAMAGE.
3061f28255Scgd */
3161f28255Scgd
320c2c736dSlukem #include <sys/cdefs.h>
3361f28255Scgd #ifndef lint
3498e5374cSlukem __COPYRIGHT("@(#) Copyright (c) 1980, 1993\
3598e5374cSlukem The Regents of the University of California. All rights reserved.");
3661f28255Scgd #endif /* not lint */
3761f28255Scgd
3861f28255Scgd #ifndef lint
397642736cSjtc #if 0
407642736cSjtc static char sccsid[] = "@(#)fmt.c 8.1 (Berkeley) 7/20/93";
417642736cSjtc #endif
42*d40754b0Schristos __RCSID("$NetBSD: fmt.c,v 1.33 2017/10/13 00:11:56 christos Exp $");
4361f28255Scgd #endif /* not lint */
4461f28255Scgd
45*d40754b0Schristos #include <wctype.h>
46482ca466Swiz #include <locale.h>
4761f28255Scgd #include <stdio.h>
4855d8a77fScgd #include <stdlib.h>
493d9ab41dSchristos #include <unistd.h>
503d9ab41dSchristos #include <errno.h>
513d9ab41dSchristos #include <err.h>
523d9ab41dSchristos #include <limits.h>
5355d8a77fScgd #include <string.h>
54*d40754b0Schristos #include <locale.h>
559ac95763Schristos #include "buffer.h"
5661f28255Scgd
5761f28255Scgd /*
5861f28255Scgd * fmt -- format the concatenation of input files or standard input
5961f28255Scgd * onto standard output. Designed for use with Mail ~|
6061f28255Scgd *
6161f28255Scgd * Syntax : fmt [ goal [ max ] ] [ name ... ]
6261f28255Scgd * Authors: Kurt Shoens (UCB) 12/7/78;
6361f28255Scgd * Liz Allen (UMCP) 2/24/83 [Addition of goal length concept].
6461f28255Scgd */
6561f28255Scgd
6661f28255Scgd /* LIZ@UOM 6/18/85 --New variables goal_length and max_length */
6761f28255Scgd #define GOAL_LENGTH 65
6861f28255Scgd #define MAX_LENGTH 75
699ac95763Schristos static size_t goal_length; /* Target or goal line length in output */
709ac95763Schristos static size_t max_length; /* Max line length in output */
719ac95763Schristos static size_t pfx; /* Current leading blank count */
723d9ab41dSchristos static int raw; /* Don't treat mail specially */
73bd359116Schristos static int lineno; /* Current input line */
74bd359116Schristos static int mark; /* Last place we saw a head line */
759ac95763Schristos static int center;
769ac95763Schristos static struct buffer outbuf;
7761f28255Scgd
78*d40754b0Schristos static const wchar_t *headnames[] = { L"To", L"Subject", L"Cc", NULL };
7961f28255Scgd
808b0f9554Sperry static void usage(void) __dead;
813d9ab41dSchristos static int getnum(const char *, const char *, size_t *, int);
82482ca466Swiz static void fmt(FILE *);
83*d40754b0Schristos static int ispref(const wchar_t *, const wchar_t *);
84482ca466Swiz static void leadin(void);
85482ca466Swiz static void oflush(void);
86*d40754b0Schristos static void pack(const wchar_t *, size_t);
879ac95763Schristos static void prefix(const struct buffer *, int);
88*d40754b0Schristos static void split(const wchar_t *, int);
899ac95763Schristos static void tabulate(struct buffer *);
909ac95763Schristos
915f8283eaSjdolecek
92*d40754b0Schristos int ishead(const wchar_t *);
930c2c736dSlukem
9461f28255Scgd /*
9561f28255Scgd * Drive the whole formatter by managing input files. Also,
9661f28255Scgd * cause initialization of the output stuff and flush it out
9761f28255Scgd * at the end.
9861f28255Scgd */
9961f28255Scgd
1000c2c736dSlukem int
main(int argc,char ** argv)101482ca466Swiz main(int argc, char **argv)
10261f28255Scgd {
1030c2c736dSlukem FILE *fi;
1040c2c736dSlukem int errs = 0;
1053d9ab41dSchristos int compat = 1;
1063d9ab41dSchristos int c;
10761f28255Scgd
10861f28255Scgd goal_length = GOAL_LENGTH;
10961f28255Scgd max_length = MAX_LENGTH;
1109ac95763Schristos buf_init(&outbuf);
11161f28255Scgd lineno = 1;
112bd359116Schristos mark = -10;
11386d80fb6Skleink
1149ac95763Schristos setprogname(*argv);
11568e618ceSchristos (void)setlocale(LC_ALL, "");
11686d80fb6Skleink
117cc700845Schristos while ((c = getopt(argc, argv, "Cg:m:rw:")) != -1)
1183d9ab41dSchristos switch (c) {
1193d9ab41dSchristos case 'C':
120ef12b8c8Sabs center++;
1213d9ab41dSchristos break;
1223d9ab41dSchristos case 'g':
1233d9ab41dSchristos (void)getnum(optarg, "goal", &goal_length, 1);
1243d9ab41dSchristos compat = 0;
1253d9ab41dSchristos break;
1263d9ab41dSchristos case 'm':
127cc700845Schristos case 'w':
1283d9ab41dSchristos (void)getnum(optarg, "max", &max_length, 1);
1293d9ab41dSchristos compat = 0;
1303d9ab41dSchristos break;
1313d9ab41dSchristos case 'r':
1323d9ab41dSchristos raw++;
1333d9ab41dSchristos break;
1343d9ab41dSchristos default:
1353d9ab41dSchristos usage();
136ef12b8c8Sabs }
1373d9ab41dSchristos
1383d9ab41dSchristos argc -= optind;
1393d9ab41dSchristos argv += optind;
1403d9ab41dSchristos
1413d9ab41dSchristos /*
1423d9ab41dSchristos * compatibility with old usage.
1433d9ab41dSchristos */
144c3f4be5bSchristos if (compat && argc > 0 && getnum(*argv, "goal", &goal_length, 0)) {
14561f28255Scgd argv++;
14661f28255Scgd argc--;
147c3f4be5bSchristos if (argc > 0 && getnum(*argv, "max", &max_length, 0)) {
14861f28255Scgd argv++;
14961f28255Scgd argc--;
15061f28255Scgd }
15161f28255Scgd }
1523d9ab41dSchristos
15361f28255Scgd if (max_length <= goal_length) {
1549ac95763Schristos errx(1, "Max length (%zu) must be greater than goal "
1559ac95763Schristos "length (%zu)", max_length, goal_length);
15661f28255Scgd }
1573d9ab41dSchristos if (argc == 0) {
15861f28255Scgd fmt(stdin);
15961f28255Scgd oflush();
1609ac95763Schristos return 0;
16161f28255Scgd }
162bb0b4504Schristos for (;argc; argc--, argv++) {
163bb0b4504Schristos if ((fi = fopen(*argv, "r")) == NULL) {
1649ac95763Schristos warn("Cannot open `%s'", *argv);
16561f28255Scgd errs++;
16661f28255Scgd continue;
16761f28255Scgd }
16861f28255Scgd fmt(fi);
16968e618ceSchristos (void)fclose(fi);
17061f28255Scgd }
17161f28255Scgd oflush();
1729ac95763Schristos buf_end(&outbuf);
1739ac95763Schristos return errs;
17461f28255Scgd }
17561f28255Scgd
1763d9ab41dSchristos static void
usage(void)1773d9ab41dSchristos usage(void)
1783d9ab41dSchristos {
1793d9ab41dSchristos (void)fprintf(stderr,
180cc700845Schristos "Usage: %s [-Cr] [-g <goal>] [-m|w <max>] [<files>..]\n"
1813d9ab41dSchristos "\t %s [-Cr] [<goal>] [<max>] [<files>]\n",
1823d9ab41dSchristos getprogname(), getprogname());
1833d9ab41dSchristos exit(1);
1843d9ab41dSchristos }
1853d9ab41dSchristos
1863d9ab41dSchristos static int
getnum(const char * str,const char * what,size_t * res,int badnum)1873d9ab41dSchristos getnum(const char *str, const char *what, size_t *res, int badnum)
1883d9ab41dSchristos {
1893d9ab41dSchristos unsigned long ul;
1903d9ab41dSchristos char *ep;
1913d9ab41dSchristos
1923d9ab41dSchristos errno = 0;
1933d9ab41dSchristos ul = strtoul(str, &ep, 0);
1943d9ab41dSchristos if (*str != '\0' && *ep == '\0') {
1953d9ab41dSchristos if ((errno == ERANGE && ul == ULONG_MAX) || ul > SIZE_T_MAX)
1963d9ab41dSchristos errx(1, "%s number `%s' too big", what, str);
1973d9ab41dSchristos *res = (size_t)ul;
1983d9ab41dSchristos return 1;
1993d9ab41dSchristos } else if (badnum)
2003d9ab41dSchristos errx(1, "Bad %s number `%s'", what, str);
2013d9ab41dSchristos
2023d9ab41dSchristos return 0;
2033d9ab41dSchristos }
2043d9ab41dSchristos
20561f28255Scgd /*
20661f28255Scgd * Read up characters from the passed input file, forming lines,
20761f28255Scgd * doing ^H processing, expanding tabs, stripping trailing blanks,
20861f28255Scgd * and sending each line down for analysis.
20961f28255Scgd */
2105f8283eaSjdolecek static void
fmt(FILE * fi)211482ca466Swiz fmt(FILE *fi)
21261f28255Scgd {
2139ac95763Schristos struct buffer lbuf, cbuf;
214*d40754b0Schristos wchar_t *cp, *cp2;
215*d40754b0Schristos wint_t c;
216*d40754b0Schristos int add_space;
217420b514aSdholland size_t len, col, i;
21861f28255Scgd
219ef12b8c8Sabs if (center) {
2209ac95763Schristos for (;;) {
221*d40754b0Schristos cp = fgetwln(fi, &len);
222ef12b8c8Sabs if (!cp)
223ef12b8c8Sabs return;
224420b514aSdholland
225420b514aSdholland /* skip over leading space */
226420b514aSdholland while (len > 0) {
227*d40754b0Schristos if (!iswspace(*cp))
228420b514aSdholland break;
229ef12b8c8Sabs cp++;
230420b514aSdholland len--;
231420b514aSdholland }
232420b514aSdholland
233420b514aSdholland /* clear trailing space */
234420b514aSdholland while (len > 0) {
235*d40754b0Schristos if (!iswspace((unsigned char)cp[len-1]))
236420b514aSdholland break;
237420b514aSdholland len--;
238420b514aSdholland }
239420b514aSdholland
240420b514aSdholland if (len == 0) {
241420b514aSdholland /* blank line */
242*d40754b0Schristos (void)putwchar(L'\n');
243420b514aSdholland continue;
244420b514aSdholland }
245420b514aSdholland
246420b514aSdholland if (goal_length > len) {
247420b514aSdholland for (i = 0; i < (goal_length - len) / 2; i++) {
248*d40754b0Schristos (void)putwchar(L' ');
249420b514aSdholland }
250420b514aSdholland }
251420b514aSdholland for (i = 0; i < len; i++) {
252*d40754b0Schristos (void)putwchar(cp[i]);
253420b514aSdholland }
254*d40754b0Schristos (void)putwchar(L'\n');
255ef12b8c8Sabs }
256ef12b8c8Sabs }
2579ac95763Schristos
2589ac95763Schristos buf_init(&lbuf);
2599ac95763Schristos buf_init(&cbuf);
260*d40754b0Schristos c = getwc(fi);
2619ac95763Schristos
262*d40754b0Schristos while (c != WEOF) {
26361f28255Scgd /*
26461f28255Scgd * Collect a line, doing ^H processing.
26561f28255Scgd * Leave tabs for now.
26661f28255Scgd */
2679ac95763Schristos buf_reset(&lbuf);
268*d40754b0Schristos while (c != '\n' && c != WEOF) {
26961f28255Scgd if (c == '\b') {
27068e618ceSchristos (void)buf_unputc(&lbuf);
271*d40754b0Schristos c = getwc(fi);
27261f28255Scgd continue;
27361f28255Scgd }
274*d40754b0Schristos if(!(iswprint(c) || c == '\t' || c >= 160)) {
275*d40754b0Schristos c = getwc(fi);
27661f28255Scgd continue;
27761f28255Scgd }
2789ac95763Schristos buf_putc(&lbuf, c);
279*d40754b0Schristos c = getwc(fi);
28061f28255Scgd }
2819ac95763Schristos buf_putc(&lbuf, '\0');
28268e618ceSchristos (void)buf_unputc(&lbuf);
283*d40754b0Schristos add_space = c != WEOF;
28461f28255Scgd
28561f28255Scgd /*
2869ac95763Schristos * Expand tabs on the way.
28761f28255Scgd */
28861f28255Scgd col = 0;
2899ac95763Schristos cp = lbuf.bptr;
2909ac95763Schristos buf_reset(&cbuf);
2919ac95763Schristos while ((c = *cp++) != '\0') {
29261f28255Scgd if (c != '\t') {
29361f28255Scgd col++;
2949ac95763Schristos buf_putc(&cbuf, c);
29561f28255Scgd continue;
29661f28255Scgd }
29761f28255Scgd do {
2989ac95763Schristos buf_putc(&cbuf, ' ');
29961f28255Scgd col++;
30061f28255Scgd } while ((col & 07) != 0);
30161f28255Scgd }
30261f28255Scgd
30361f28255Scgd /*
30461f28255Scgd * Swipe trailing blanks from the line.
30561f28255Scgd */
3069ac95763Schristos for (cp2 = cbuf.ptr - 1; cp2 >= cbuf.bptr && *cp2 == ' '; cp2--)
3079ac95763Schristos continue;
3089ac95763Schristos cbuf.ptr = cp2 + 1;
3099ac95763Schristos buf_putc(&cbuf, '\0');
31068e618ceSchristos (void)buf_unputc(&cbuf);
3119ac95763Schristos prefix(&cbuf, add_space);
312*d40754b0Schristos if (c != WEOF)
313*d40754b0Schristos c = getwc(fi);
31461f28255Scgd }
3159ac95763Schristos buf_end(&cbuf);
3169ac95763Schristos buf_end(&lbuf);
31761f28255Scgd }
31861f28255Scgd
31961f28255Scgd /*
32061f28255Scgd * Take a line devoid of tabs and other garbage and determine its
32161f28255Scgd * blank prefix. If the indent changes, call for a linebreak.
32261f28255Scgd * If the input line is blank, echo the blank line on the output.
32361f28255Scgd * Finally, if the line minus the prefix is a mail header, try to keep
32461f28255Scgd * it on a line by itself.
32561f28255Scgd */
3265f8283eaSjdolecek static void
prefix(const struct buffer * buf,int add_space)3279ac95763Schristos prefix(const struct buffer *buf, int add_space)
32861f28255Scgd {
329*d40754b0Schristos const wchar_t *cp;
330*d40754b0Schristos const wchar_t **hp;
3319ac95763Schristos size_t np;
3329ac95763Schristos int h;
33361f28255Scgd
3349ac95763Schristos if (buf->ptr == buf->bptr) {
33561f28255Scgd oflush();
336*d40754b0Schristos (void)putwchar(L'\n');
33761f28255Scgd return;
33861f28255Scgd }
3399ac95763Schristos for (cp = buf->bptr; *cp == ' '; cp++)
3409ac95763Schristos continue;
3419ac95763Schristos np = cp - buf->bptr;
34261f28255Scgd
34361f28255Scgd /*
34461f28255Scgd * The following horrible expression attempts to avoid linebreaks
34561f28255Scgd * when the indent changes due to a paragraph.
34661f28255Scgd */
3479ac95763Schristos if (np != pfx && (np > pfx || abs((int)(pfx - np)) > 8))
34861f28255Scgd oflush();
3493d9ab41dSchristos if (!raw) {
3509ac95763Schristos if ((h = ishead(cp)) != 0) {
3519ac95763Schristos oflush();
3529ac95763Schristos mark = lineno;
3539ac95763Schristos }
354bd359116Schristos if (lineno - mark < 3 && lineno - mark > 0)
3559ac95763Schristos for (hp = &headnames[0]; *hp != NULL; hp++)
35661f28255Scgd if (ispref(*hp, cp)) {
35761f28255Scgd h = 1;
35861f28255Scgd oflush();
35961f28255Scgd break;
36061f28255Scgd }
36161f28255Scgd if (!h && (h = (*cp == '.')))
36261f28255Scgd oflush();
3633d9ab41dSchristos } else
3643d9ab41dSchristos h = 0;
36561f28255Scgd pfx = np;
3665f8283eaSjdolecek if (h) {
3679ac95763Schristos pack(cp, (size_t)(buf->ptr - cp));
36861f28255Scgd oflush();
3695f8283eaSjdolecek } else
3705f8283eaSjdolecek split(cp, add_space);
37161f28255Scgd lineno++;
37261f28255Scgd }
37361f28255Scgd
37461f28255Scgd /*
37561f28255Scgd * Split up the passed line into output "words" which are
37661f28255Scgd * maximal strings of non-blanks with the blank separation
37761f28255Scgd * attached at the end. Pass these words along to the output
37861f28255Scgd * line packer.
37961f28255Scgd */
3805f8283eaSjdolecek static void
split(const wchar_t line[],int add_space)381*d40754b0Schristos split(const wchar_t line[], int add_space)
38261f28255Scgd {
383*d40754b0Schristos const wchar_t *cp;
3849ac95763Schristos struct buffer word;
3859ac95763Schristos size_t wlen;
38661f28255Scgd
3879ac95763Schristos buf_init(&word);
38861f28255Scgd cp = line;
38961f28255Scgd while (*cp) {
3909ac95763Schristos buf_reset(&word);
3919ac95763Schristos wlen = 0;
39261f28255Scgd
39361f28255Scgd /*
39461f28255Scgd * Collect a 'word,' allowing it to contain escaped white
39561f28255Scgd * space.
39661f28255Scgd */
39761f28255Scgd while (*cp && *cp != ' ') {
398*d40754b0Schristos if (*cp == '\\' && iswspace(cp[1]))
3999ac95763Schristos buf_putc(&word, *cp++);
4009ac95763Schristos buf_putc(&word, *cp++);
4019ac95763Schristos wlen++;
40261f28255Scgd }
40361f28255Scgd
40461f28255Scgd /*
40561f28255Scgd * Guarantee a space at end of line. Two spaces after end of
40661f28255Scgd * sentence punctuation.
40761f28255Scgd */
4085f8283eaSjdolecek if (*cp == '\0' && add_space) {
4099ac95763Schristos buf_putc(&word, ' ');
4106533f941Slukem if (strchr(".:!", cp[-1]))
4119ac95763Schristos buf_putc(&word, ' ');
41261f28255Scgd }
41361f28255Scgd while (*cp == ' ')
4149ac95763Schristos buf_putc(&word, *cp++);
4159ac95763Schristos
4169ac95763Schristos buf_putc(&word, '\0');
41768e618ceSchristos (void)buf_unputc(&word);
4189ac95763Schristos
4199ac95763Schristos pack(word.bptr, wlen);
42061f28255Scgd }
4219ac95763Schristos buf_end(&word);
42261f28255Scgd }
42361f28255Scgd
42461f28255Scgd /*
42561f28255Scgd * Output section.
42661f28255Scgd * Build up line images from the words passed in. Prefix
42775bdcdeaSchristos * each line with correct number of blanks.
42875bdcdeaSchristos *
42975bdcdeaSchristos * At the bottom of this whole mess, leading tabs are reinserted.
43061f28255Scgd */
43161f28255Scgd
43261f28255Scgd /*
43361f28255Scgd * Pack a word onto the output line. If this is the beginning of
43461f28255Scgd * the line, push on the appropriately-sized string of blanks first.
43561f28255Scgd * If the word won't fit on the current line, flush and begin a new
43661f28255Scgd * line. If the word is too long to fit all by itself on a line,
43761f28255Scgd * just give it its own and hope for the best.
43861f28255Scgd *
43961f28255Scgd * LIZ@UOM 6/18/85 -- If the new word will fit in at less than the
44061f28255Scgd * goal length, take it. If not, then check to see if the line
44161f28255Scgd * will be over the max length; if so put the word on the next
44261f28255Scgd * line. If not, check to see if the line will be closer to the
44361f28255Scgd * goal length with or without the word and take it or put it on
44461f28255Scgd * the next line accordingly.
44561f28255Scgd */
44661f28255Scgd
4475f8283eaSjdolecek static void
pack(const wchar_t * word,size_t wlen)448*d40754b0Schristos pack(const wchar_t *word, size_t wlen)
44961f28255Scgd {
450*d40754b0Schristos const wchar_t *cp;
4519ac95763Schristos size_t s, t;
45261f28255Scgd
4539ac95763Schristos if (outbuf.bptr == outbuf.ptr)
45461f28255Scgd leadin();
45561f28255Scgd /*
45661f28255Scgd * LIZ@UOM 6/18/85 -- change condition to check goal_length; s is the
45761f28255Scgd * length of the line before the word is added; t is now the length
45861f28255Scgd * of the line after the word is added
45961f28255Scgd */
4609ac95763Schristos s = outbuf.ptr - outbuf.bptr;
4619ac95763Schristos t = wlen + s;
462e4a41ed5Schristos if ((t <= goal_length) || ((t <= max_length) &&
463e4a41ed5Schristos (s <= goal_length) && (t - goal_length <= goal_length - s))) {
46461f28255Scgd /*
46561f28255Scgd * In like flint!
46661f28255Scgd */
4679ac95763Schristos for (cp = word; *cp;)
4689ac95763Schristos buf_putc(&outbuf, *cp++);
46961f28255Scgd return;
47061f28255Scgd }
47161f28255Scgd if (s > pfx) {
47261f28255Scgd oflush();
47361f28255Scgd leadin();
47461f28255Scgd }
4759ac95763Schristos for (cp = word; *cp;)
4769ac95763Schristos buf_putc(&outbuf, *cp++);
47761f28255Scgd }
47861f28255Scgd
47961f28255Scgd /*
48061f28255Scgd * If there is anything on the current output line, send it on
48175bdcdeaSchristos * its way. Reset outbuf.
48261f28255Scgd */
4835f8283eaSjdolecek static void
oflush(void)484482ca466Swiz oflush(void)
48561f28255Scgd {
4869ac95763Schristos if (outbuf.bptr == outbuf.ptr)
48761f28255Scgd return;
4889ac95763Schristos buf_putc(&outbuf, '\0');
48968e618ceSchristos (void)buf_unputc(&outbuf);
4909ac95763Schristos tabulate(&outbuf);
4919ac95763Schristos buf_reset(&outbuf);
49261f28255Scgd }
49361f28255Scgd
49461f28255Scgd /*
49561f28255Scgd * Take the passed line buffer, insert leading tabs where possible, and
49661f28255Scgd * output on standard output (finally).
49761f28255Scgd */
4985f8283eaSjdolecek static void
tabulate(struct buffer * buf)4999ac95763Schristos tabulate(struct buffer *buf)
50061f28255Scgd {
501*d40754b0Schristos wchar_t *cp;
5029ac95763Schristos size_t b, t;
50361f28255Scgd
50461f28255Scgd /*
50561f28255Scgd * Toss trailing blanks in the output line.
50661f28255Scgd */
5079ebf6192Schristos for (cp = buf->ptr - 1; cp >= buf->bptr && *cp == ' '; cp--)
5089ac95763Schristos continue;
5099ebf6192Schristos *++cp = '\0';
51061f28255Scgd
51161f28255Scgd /*
51261f28255Scgd * Count the leading blank space and tabulate.
51361f28255Scgd */
5149ac95763Schristos for (cp = buf->bptr; *cp == ' '; cp++)
5159ac95763Schristos continue;
5169ac95763Schristos b = cp - buf->bptr;
51775bdcdeaSchristos t = b / 8;
51875bdcdeaSchristos b = b % 8;
51961f28255Scgd if (t > 0)
52061f28255Scgd do
521*d40754b0Schristos (void)putwchar(L'\t');
52261f28255Scgd while (--t);
52361f28255Scgd if (b > 0)
52461f28255Scgd do
525*d40754b0Schristos (void)putwchar(L' ');
52661f28255Scgd while (--b);
52761f28255Scgd while (*cp)
528*d40754b0Schristos (void)putwchar(*cp++);
529*d40754b0Schristos (void)putwchar(L'\n');
53061f28255Scgd }
53161f28255Scgd
53261f28255Scgd /*
53361f28255Scgd * Initialize the output line with the appropriate number of
53461f28255Scgd * leading blanks.
53561f28255Scgd */
5365f8283eaSjdolecek static void
leadin(void)537482ca466Swiz leadin(void)
53861f28255Scgd {
5399ac95763Schristos size_t b;
54061f28255Scgd
5419ac95763Schristos buf_reset(&outbuf);
5429ac95763Schristos
5439ac95763Schristos for (b = 0; b < pfx; b++)
5449ac95763Schristos buf_putc(&outbuf, ' ');
54561f28255Scgd }
54661f28255Scgd
54761f28255Scgd /*
54861f28255Scgd * Is s1 a prefix of s2??
54961f28255Scgd */
5505f8283eaSjdolecek static int
ispref(const wchar_t * s1,const wchar_t * s2)551*d40754b0Schristos ispref(const wchar_t *s1, const wchar_t *s2)
55261f28255Scgd {
55361f28255Scgd
55461f28255Scgd while (*s1++ == *s2)
5559ac95763Schristos continue;
5569ac95763Schristos return *s1 == '\0';
55761f28255Scgd }
558