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