xref: /illumos-gate/usr/src/cmd/col/col.c (revision 4c28a617e3922d92a58e813a5b955eb526b9c386)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 /*
31  *	col - filter reverse carraige motions
32  *
33  */
34 
35 
36 #include <stdio.h>
37 #include <ctype.h>
38 #include <locale.h>
39 #include <limits.h>
40 #include <stdlib.h>
41 #include <wctype.h>
42 
43 #define	PL 256
44 #define	ESC '\033'
45 #define	RLF '\013'
46 #define	SI '\017'
47 #define	SO '\016'
48 #define	GREEK 0200
49 #define	LINELN 4096
50 
51 wchar_t	*page[PL];
52 wchar_t	lbuff[LINELN], *line;
53 wchar_t	*lbuffend = lbuff + LINELN - 1;
54 wchar_t	ws_blank[2] = {' ', 0};
55 char	esc_chars, underline, temp_off, smart;
56 int	bflag, xflag, fflag, pflag;
57 int	greeked;
58 int	half;
59 int	cp, lp;
60 int	ll, llh, mustwr;
61 int	pcp = 0;
62 char	*pgmname;
63 
64 #define	USAGEMSG	"usage:\tcol [-bfxp]\n"
65 
66 static void	outc(wchar_t);
67 static void	store(int);
68 static void	fetch(int);
69 static void	emit(wchar_t *, int);
70 static void	incr(void);
71 static void	decr(void);
72 static void	wsinsert(wchar_t *, int);
73 static void	incr_line(int);
74 static int	wcscrwidth(wchar_t);
75 
76 int
77 main(int argc, char **argv)
78 {
79 	int	i, n;
80 	int	opt;
81 	int	greek;
82 	int	c;
83 	wchar_t	wc;
84 	char	byte;
85 	static char	fbuff[BUFSIZ];
86 
87 	setbuf(stdout, fbuff);
88 	(void) setlocale(LC_ALL, "");
89 #if !defined(TEXT_DOMAIN)
90 #define	TEXT_DOMAIN "SYS_TEST"
91 #endif
92 	(void) textdomain(TEXT_DOMAIN);
93 	pgmname = argv[0];
94 
95 	while ((opt = getopt(argc, argv, "bfxp")) != EOF)
96 		switch (opt) {
97 		case 'b':
98 			bflag++;
99 			break;
100 		case 'x':
101 			xflag++;
102 			break;
103 		case 'f':
104 			fflag++;
105 			break;
106 		case 'p':
107 			pflag++;
108 			break;
109 		case '?':
110 		default:
111 			(void) fprintf(stderr, gettext(USAGEMSG));
112 			exit(2);
113 		}
114 
115 	argc -= optind;
116 	if (argc >= 1) {
117 		(void) fprintf(stderr, gettext(USAGEMSG));
118 		exit(2);
119 	}
120 
121 	for (ll = 0; ll < PL; ll++)
122 		page[ll] = 0;
123 
124 	smart = temp_off = underline = esc_chars = '\0';
125 	cp = 0;
126 	ll = 0;
127 	greek = 0;
128 	mustwr = PL;
129 	line = lbuff;
130 
131 	while ((c = getwchar()) != EOF) {
132 		if (underline && temp_off && c > ' ') {
133 			outc(ESC);
134 			if (*line) {
135 				incr_line(1);
136 			}
137 			*line = 'X';
138 			incr_line(1);
139 			*line = temp_off = '\0';
140 		}
141 		if (c != '\b')
142 			if (esc_chars)
143 				esc_chars = '\0';
144 		switch (c) {
145 		case '\n':
146 			if (underline && !temp_off) {
147 				if (*line)
148 					incr_line(1);
149 				*line = ESC;
150 				incr_line(1);
151 				*line = 'Y';
152 				incr_line(1);
153 				*line = '\0';
154 				temp_off = '1';
155 			}
156 			incr();
157 			incr();
158 			cp = 0;
159 			continue;
160 
161 		case '\0':
162 			continue;
163 
164 		case ESC:
165 			c = getwchar();
166 			switch (c) {
167 			case '7':	/* reverse full line feed */
168 				decr();
169 				decr();
170 				break;
171 
172 			case '8':	/* reverse half line feed */
173 				if (fflag)
174 					decr();
175 				else {
176 					if (--half < -1) {
177 						decr();
178 						decr();
179 						half += 2;
180 					}
181 				}
182 				break;
183 
184 			case '9':	/* forward half line feed */
185 				if (fflag)
186 					incr();
187 				else {
188 					if (++half > 0) {
189 						incr();
190 						incr();
191 						half -= 2;
192 					}
193 				}
194 				break;
195 
196 			default:
197 				if (pflag)	{	/* pass through esc */
198 					outc(ESC);
199 					incr_line(1);
200 					*line = c;
201 					incr_line(1);
202 					*line = '\0';
203 					esc_chars = 1;
204 					if (c == 'X')
205 						underline = 1;
206 					if (c == 'Y' && underline)
207 						underline = temp_off = '\0';
208 					if (c == ']')
209 						smart = 1;
210 					if (c == '[')
211 						smart = '\0';
212 					}
213 				break;
214 			}
215 			continue;
216 
217 		case SO:
218 			greek = GREEK;
219 			greeked++;
220 			continue;
221 
222 		case SI:
223 			greek = 0;
224 			continue;
225 
226 		case RLF:
227 			decr();
228 			decr();
229 			continue;
230 
231 		case '\r':
232 			cp = 0;
233 			continue;
234 
235 		case '\t':
236 			cp = (cp + 8) & -8;
237 			continue;
238 
239 		case '\b':
240 			if (esc_chars) {
241 				*line = '\b';
242 				incr_line(1);
243 				*line = '\0';
244 			} else if (cp > 0)
245 				cp--;
246 			continue;
247 
248 		case ' ':
249 			cp++;
250 			continue;
251 
252 		default:
253 			if (iswprint(c)) {	/* if printable */
254 				if (!greek) {
255 					outc((wchar_t)c);
256 					cp += wcscrwidth(c);
257 				}
258 				/*
259 				 * EUC (apply SO only when there can
260 				 * be corresponding character in CS1)
261 				 */
262 				else if (iswascii(c)) {
263 					byte = (c | greek);
264 					n = mbtowc(&wc, &byte, 1);
265 					if (!iswcntrl(c) && !iswspace(c) &&
266 					    n == 1) {
267 						outc(wc);
268 						cp += wcscrwidth(wc);
269 					} else {
270 						outc((wchar_t)c);
271 						cp += wcscrwidth(c);
272 					}
273 				} else {
274 					outc((wchar_t)c);
275 					cp += wcscrwidth(c);
276 				}
277 
278 				if ((cp + 1) > LINELN) {
279 					(void) fprintf(stderr,
280 					    gettext("col: Line too long\n"));
281 					exit(2);
282 				}
283 			}
284 			continue;
285 		}
286 	}
287 
288 	for (i = 0; i < PL; i++)
289 		if (page[(mustwr+i)%PL] != 0)
290 			emit(page[(mustwr+i) % PL], mustwr+i-PL);
291 	emit(ws_blank, (llh + 1) & -2);
292 	return (0);
293 }
294 
295 static void
296 outc(wchar_t c)
297 {
298 	int	n, i;
299 	int	width, widthl, widthc;
300 	wchar_t	*p1;
301 	wchar_t c1;
302 	char esc_chars = '\0';
303 	if (lp > cp) {
304 		line = lbuff;
305 		lp = 0;
306 	}
307 
308 	while (lp < cp) {
309 		if (*line != '\b')
310 			if (esc_chars)
311 				esc_chars = '\0';
312 		switch (*line)	{
313 		case ESC:
314 			incr_line(1);
315 			esc_chars = 1;
316 			break;
317 		case '\0':
318 			*line = ' ';
319 			lp++;
320 			break;
321 		case '\b':
322 			/* if ( ! esc_chars ) */
323 			lp--;
324 			break;
325 		default:
326 			lp += wcscrwidth(*line);
327 		}
328 		incr_line(1);
329 	}
330 	while (*line == '\b') {
331 		/*
332 		 * EUC (For a multi-column character, backspace characters
333 		 * are assumed to be used like "__^H^HXX", where "XX"
334 		 * represents a two-column character, and a backspace
335 		 * always goes back by one column.)
336 		 */
337 		for (n = 0; *line == '\b'; incr_line(1)) {
338 			n++;
339 			lp--;
340 		}
341 		while (n > 0 && lp < cp) {
342 			i = *line;
343 			incr_line(1);
344 			i = wcscrwidth(i);
345 			n -= i;
346 			lp += i;
347 		}
348 	}
349 	while (*line == ESC)
350 		incr_line(6);
351 	widthc = wcscrwidth(c);
352 	widthl = wcscrwidth(*line);
353 	if (bflag || (*line == '\0') || *line == ' ') {
354 		if (*line == '\0' || widthl == widthc) {
355 			*line = c;
356 		} else if (widthl > widthc) {
357 			n = widthl - widthc;
358 			wsinsert(line, n);
359 			*line = c;
360 			incr_line(1);
361 			for (i = 0; i < n; i++) {
362 				*line = ' ';
363 				incr_line(1);
364 			}
365 			line = lbuff;
366 			lp = 0;
367 		} else {
368 			n = widthc - widthl;
369 			if (line < lbuffend) {
370 				for (p1 = line+1; n > 0 && p1 < lbuffend;
371 				    n -= wcscrwidth(i)) {
372 						i = *p1++;
373 				}
374 				*line = c;
375 				if (p1 < lbuffend) {
376 					(void) wcscpy(line+1, p1);
377 				} else {
378 					(void) fprintf(stderr,
379 					    gettext("col: Line too long.\n"));
380 					exit(1);
381 				}
382 			} else {
383 				(void) fprintf(stderr,
384 				    gettext("col: Line too long.\n"));
385 				exit(1);
386 			}
387 		}
388 	} else {
389 		if (smart && (widthl == 1) && (widthc == 1)) {
390 			wchar_t	c1, c2, c3, c4, c5, c6, c7;
391 			incr_line(1);
392 			c1 = *line;
393 			*line = ESC;
394 			incr_line(1);
395 			c2 = *line;
396 			*line = '[';
397 			incr_line(1);
398 			c3 = *line;
399 			*line = '\b';
400 			incr_line(1);
401 			c4 = *line;
402 			*line = ESC;
403 			incr_line(1);
404 			c5 = *line;
405 			*line = ']';
406 			incr_line(1);
407 			c6 = *line;
408 			*line = c;
409 			incr_line(1);
410 			while (c1) {
411 				c7 = *line;
412 				*line = c1;
413 				incr_line(1);
414 				c1 = c2;
415 				c2 = c3;
416 				c3 = c4;
417 				c4 = c5;
418 				c5 = c6;
419 				c6 = c7;
420 			}
421 		} else	{
422 			if ((widthl == 1) && (widthc == 1)) {
423 				wchar_t	c1, c2, c3;
424 				incr_line(1);
425 				c1 = *line;
426 				*line = '\b';
427 				incr_line(1);
428 				c2 = *line;
429 				*line = c;
430 				incr_line(1);
431 				while (c1) {
432 					c3 = *line;
433 					*line = c1;
434 					incr_line(1);
435 					c1 = c2;
436 					c2 = c3;
437 				}
438 			} else {
439 				width = (widthc > widthl) ? widthc : widthl;
440 				for (i = 0; i < width; i += wcscrwidth(c1)) {
441 					c1 = *line;
442 					incr_line(1);
443 				}
444 				wsinsert(line, width + (width - widthc + 1));
445 				for (i = 0; i < width; i++) {
446 					*line = '\b';
447 					incr_line(1);
448 				}
449 				*line = c;
450 				incr_line(1);
451 				for (i = widthc; i < width; i++) {
452 					*line = ' ';
453 					incr_line(1);
454 				}
455 			}
456 		}
457 		lp = 0;
458 		line = lbuff;
459 	}
460 }
461 
462 static void
463 store(int lno)
464 {
465 	lno %= PL;
466 	if (page[lno] != 0)
467 		free((char *)page[lno]);
468 	page[lno] = (wchar_t *)malloc((unsigned)(wcslen(lbuff) + 2)
469 	    * sizeof (wchar_t));
470 	if (page[lno] == 0) {
471 		/* fprintf(stderr, "%s: no storage\n", pgmname); */
472 		exit(2);
473 	}
474 	(void) wcscpy(page[lno], lbuff);
475 }
476 
477 static void
478 fetch(int lno)
479 {
480 	wchar_t	*p;
481 
482 	lno %= PL;
483 	p = lbuff;
484 	while (*p)
485 		*p++ = '\0';
486 	line = lbuff;
487 	lp = 0;
488 	if (page[lno])
489 		(void) wcscpy(line, page[lno]);
490 }
491 
492 static void
493 emit(wchar_t *s, int lineno)
494 {
495 	static int	cline = 0;
496 	int	ncp;
497 	wchar_t	*p;
498 	char	cshifted;
499 	char	chr[MB_LEN_MAX + 1];
500 
501 	int	c;
502 	static int	gflag = 0;
503 
504 	if (*s) {
505 		if (gflag) {
506 			(void) putchar(SI);
507 			gflag = 0;
508 		}
509 		while (cline < lineno - 1) {
510 			(void) putchar('\n');
511 			pcp = 0;
512 			cline += 2;
513 		}
514 		if (cline != lineno) {
515 			(void) putchar(ESC);
516 			(void) putchar('9');
517 			cline++;
518 		}
519 		if (pcp)
520 			(void) putchar('\r');
521 		pcp = 0;
522 		p = s;
523 		while (*p) {
524 			ncp = pcp;
525 			while (*p++ == ' ') {
526 				if ((++ncp & 7) == 0 && !xflag) {
527 					pcp = ncp;
528 					(void) putchar('\t');
529 				}
530 			}
531 			if (!*--p)
532 				break;
533 			while (pcp < ncp) {
534 				(void) putchar(' ');
535 				pcp++;
536 			}
537 			if (greeked) {
538 				if (wctomb(chr, *p) == 1) {
539 					if (gflag != (*chr & GREEK) &&
540 					    *p != '\b' &&
541 					    isascii(*chr ^ (gflag ^ GREEK)) &&
542 					    !iscntrl(*chr ^ (gflag ^ GREEK)) &&
543 					    !isspace(*chr ^ (gflag ^ GREEK))) {
544 						if (gflag)
545 							(void) putchar(SI);
546 						else
547 							(void) putchar(SO);
548 						gflag ^= GREEK;
549 					}
550 				}
551 			}
552 			c = *p;
553 			if (greeked) {
554 				if (wctomb(chr, (wchar_t)c) == 1) {
555 					cshifted = (*chr ^ GREEK);
556 					if (isascii(cshifted) &&
557 					    !iscntrl(cshifted) &&
558 					    !isspace(cshifted))
559 						(void) putchar(*chr & ~GREEK);
560 				} else
561 					(void) putwchar(c);
562 			} else
563 				(void) putwchar(c);
564 			if (c == '\b') {
565 				if (*(p-2) && *(p-2) == ESC) {
566 					pcp++;
567 				} else
568 					pcp--;
569 			} else {
570 				pcp += wcscrwidth(c);
571 			}
572 			p++;
573 		}
574 	}
575 }
576 
577 static void
578 incr(void)
579 {
580 	store(ll++);
581 	if (ll > llh)
582 		llh = ll;
583 	if (ll >= mustwr && page[ll%PL]) {
584 		emit(page[ll%PL], ll - PL);
585 		mustwr++;
586 		free((char *)page[ll%PL]);
587 		page[ll%PL] = 0;
588 	}
589 	fetch(ll);
590 }
591 
592 static void
593 decr(void)
594 {
595 	if (ll > mustwr - PL) {
596 		store(ll--);
597 		fetch(ll);
598 	}
599 }
600 
601 static void
602 wsinsert(wchar_t *s, int n)
603 {
604 	wchar_t	*p1, *p2;
605 
606 
607 	p1 = s + wcslen(s);
608 	p2 = p1 + n;
609 	while (p1 >= s)
610 		*p2-- = *p1--;
611 }
612 
613 /*
614  * incr_line - increments line pointer and checks for array out of bounds
615  * amt: assumed to be >= 1
616  * exit on error to avoid line pointer accessing out of the array
617  */
618 static void
619 incr_line(int amt)
620 {
621 	if (line < lbuffend - amt + 1) {
622 		line += amt;
623 	} else {
624 		(void) fprintf(stderr, gettext("col: Line too long.\n"));
625 		exit(1);
626 	}
627 }
628 
629 
630 static int
631 wcscrwidth(wchar_t wc)
632 {
633 	int	nc;
634 
635 	if (wc == 0) {
636 		/*
637 		 * if wc is a null character, needs to
638 		 * return 1 instead of 0.
639 		 */
640 		return (1);
641 	}
642 	nc = wcwidth(wc);
643 	if (nc > 0) {
644 		return (nc);
645 	} else {
646 		return (0);
647 	}
648 }
649