/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"@(#)tabs.c	1.14	96/08/21 SMI"	/* SVr4.0 1.10	*/

/*
 *	tabs [tabspec] [+mn] [-Ttype]
 *	set tabs (and margin, if +mn), for terminal type
 */


#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <curses.h>
#include <term.h>
#include <locale.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>

#define	EQ(a, b)	(strcmp(a, b) == 0)
/*	max # columns used (needed for GSI) */
#define	NCOLS	256
#define	NTABS	65	/* max # tabs +1 (to be set) */
#define	NTABSCL	21	/* max # tabs + 1 that will be cleared */
#define	ESC	033
#define	CLEAR	'2'
#define	SET	'1'
#define	TAB	'\t'
#define	CR	'\r'
#define	NMG	0	/* no margin setting */
#define	GMG	1	/* DTC300s margin */
#define	TMG	2	/* TERMINET margin */
#define	DMG	3	/* DASI450 margin */
#define	FMG	4	/* TTY 43 margin */
#define	TRMG	5	/* Trendata 4000a */

#define	TCLRLN	0	/* long, repetitive, general tab clear */

static char tsethp[] = {ESC,  '1', 0};		/* (default) */
static char tsetibm[] = {ESC, '0', 0};		/* ibm */
static char tclrhp[] = {ESC, '3', CR, 0};	/* hp terminals */
/* short sequence for many terminals */
static char tclrsh[] = {ESC, CLEAR, CR, 0};
static char tclrgs[] = {ESC, TAB, CR, 0};	/* short, for 300s */
static char tclr40[] = {ESC, 'R', CR, 0};	/* TTY 40/2, 4424 */
static char tclribm[] = {ESC, '1', CR, 0};	/* ibm */

static struct ttab {
	char *ttype;	/* -Tttype */
	char *tclr;	/* char sequence to clear tabs and return carriage */
	int tmaxtab;	/* maximum allowed position */
} *tt;

static struct ttab termtab[] = {
	"",		tclrsh,	132,
	"1620-12",	tclrsh,	158,
	"1620-12-8",	tclrsh,	158,
	"1700-12",	tclrsh,	132,
	"1700-12-8",	tclrsh,	158,
	"300-12",	TCLRLN,	158,
	"300s-12",	tclrgs,	158,
	"4424",		tclr40,	 80,
	"4000a",	tclrsh,	132,
	"4000a-12",	tclrsh,	158,
	"450-12",	tclrsh,	158,
	"450-12-8",	tclrsh,	158,
	"2631",		tclrhp, 240,
	"2631-c",	tclrhp, 240,
	"ibm",		tclribm, 80,
	0
};

static int	err;
static int 	tmarg;
static char	settab[32], clear_tabs[32];

static int	maxtab;		/* max tab for repetitive spec */
static int	margin;
static int	margflg;	/* >0 ==> +m option used, 0 ==> not */
static char	*terminal = "";
static char	*tabspec = "-8";	/* default tab specification */

static struct termio ttyold;	/* tty table */
static int	ttyisave;	/* save for input modes */
static int	ttyosave;	/* save for output modes */
static int	istty;		/* 1 ==> is actual tty */

static struct	stat	statbuf;
static char	*devtty;

static void scantab(char *scan, int tabvect[NTABS], int level);
static void repetab(char *scan, int tabvect[NTABS]);
static void arbitab(char *scan, int tabvect[NTABS]);
static void filetab(char *scan, int tabvect[NTABS], int level);
static int getmarg(char *term);
static struct ttab *termadj();
static void settabs(int tabvect[NTABS]);
static char * cleartabs(register char *p, char *qq);
static int getnum(char **scan1);
static void endup();
static int stdtab(char option[], int tabvect[]);
static void usage();
static int chk_codes(char *codes);

void
main(int argc, char **argv)
{
	int tabvect[NTABS];	/* build tab list here */
	char *scan;	/* scan pointer to next char */
	char operand[LINE_MAX];
	int option_end = 0;

	(void) setlocale(LC_ALL, "");

#if !defined(TEXT_DOMAIN)
#define	TEXT_DOMAIN "SYS_TEST"
#endif
	(void) textdomain(TEXT_DOMAIN);

	signal(SIGINT, endup);
	if (ioctl(1, TCGETA, &ttyold) == 0) {
		ttyisave = ttyold.c_iflag;
		ttyosave = ttyold.c_oflag;
		(void) fstat(1, &statbuf);
		devtty = ttyname(1);
		(void) chmod(devtty, 0000);	/* nobody, not even us */
		istty++;
	}
	tabvect[0] = 0;	/* mark as not yet filled in */
	while (--argc > 0) {
		scan = *++argv;
		if (*scan == '+') {
			if (!option_end) {
				if (*++scan == 'm') {
					margflg++;
					if (*++scan)
						margin = getnum(&scan);
					else
						margin = 10;
				} else {
					(void) fprintf(stderr, gettext(
				"tabs: %s: invalid tab spec\n"), scan-1);
					usage();
				}
			} else {
				/*
				 * only n1[,n2,...] operand can follow
				 * end of options delimiter "--"
				 */
				(void) fprintf(stderr, gettext(
				"tabs: %s: invalid tab stop operand\n"), scan);
				usage();
			}
		} else if (*scan == '-') {
			if (!option_end) {
				if (*(scan+1) == 'T') {
					/* allow space or no space after -T */
					if (*(scan+2) == '\0') {
						if (--argc > 0)
							terminal = *++argv;
						else
							usage();
					} else
						terminal = scan+2;
				} else if (*(scan+1) == '-')
					if (*(scan+2) == '\0')
						option_end = 1;
					else
						tabspec = scan; /* --file */
				else if (strcmp(scan+1, "code") == 0);
					/* skip to next argument */
				else if (chk_codes(scan+1) ||
				(isdigit(*(scan+1)) && *(scan+2) == '\0')) {
					/*
					 * valid code or single digit decimal
					 * number
					 */
					tabspec = scan;
				} else {
					(void) fprintf(stderr, gettext(
					"tabs: %s: invalid tab spec\n"), scan);
					usage();
				}
			} else {
				/*
				 * only n1[,n2,...] operand can follow
				 * end of options delimiter "--"
				 */
				(void) fprintf(stderr, gettext(
				"tabs: %s: invalid tab stop operand\n"), scan);
				usage();
			}
		} else {
			/*
			 * Tab-stop values separated using either commas
			 * or blanks.  If any number (except the first one)
			 * is preceded by a plus sign, it is taken as an
			 * increment to be added to the previous value.
			 */
			operand[0] = '\0';
			while (argc > 0) {
				if (strrchr(*argv, '-') == (char *)NULL) {
					(void) strcat(operand, *argv);
					if (argc > 1)
						(void) strcat(operand, ",");
					--argc;
					++argv;
				} else {
					(void) fprintf(stderr, gettext(
		"tabs: %s: tab stop values must be positive integers\n"),
					*argv);
					usage();
				}
			}
			tabspec = operand;	/* save tab specification */
		}
	}
	if (*terminal == '\0') {
		if ((terminal = getenv("TERM")) == (char *) NULL ||
						*terminal == '\0') {
			/*
			 * Use tab setting and clearing sequences specified
			 * by the ANSI standard.
			 */
			terminal = "ansi+tabs";
		}
	}
	if (setupterm(terminal, 1, &err) == ERR) {
		(void) fprintf(stderr, gettext(
		"tabs: %s: terminfo file not found\n"), terminal);
		usage();
	} else if (!tigetstr("hts")) {
		(void) fprintf(stderr, gettext(
		"tabs: cannot set tabs on terminal type %s\n"), terminal);
		usage();
	}
	if (err <= 0 || columns <= 0 || set_tab == 0) {
		tt = termadj();
		if (strcmp(terminal, "ibm") == 0)
			(void) strcpy(settab, tsetibm);
		else
			(void) strcpy(settab, tsethp);
		(void) strcpy(clear_tabs, tt->tclr);
		maxtab = tt->tmaxtab;
	} else {
		maxtab = columns;
		(void) strcpy(settab, set_tab);
		(void) strcpy(clear_tabs, clear_all_tabs);
	}
	scantab(tabspec, tabvect, 0);
	if (!tabvect[0])
		repetab("8", tabvect);
	settabs(tabvect);
	endup();
	exit(0);
}

/*
 * return 1 if code option is valid, otherwise return 0
 */
int
chk_codes(char *code)
{
	if (*(code+1) == '\0' && (*code == 'a' || *code == 'c' ||
		*code == 'f' || *code == 'p' || *code == 's' || *code == 'u'))
			return (1);
	else if (*(code+1) == '2' && *(code+2) == '\0' &&
		(*code == 'a' || *code == 'c'))
			return (1);
	else if (*code == 'c' && *(code+1) == '3' && *(code+2) == '\0')
		return (1);
	return (0);
}

/*	scantab: scan 1 tabspec & return tab list for it */
void
scantab(char *scan, int tabvect[NTABS], int level)
{
	register char c;
	if (*scan == '-')
		if ((c = *++scan) == '-')
			filetab(++scan, tabvect, level);
		else if (c >= '0' && c <= '9')
			repetab(scan, tabvect);
		else if (stdtab(scan, tabvect)) {
			endup();
			(void) fprintf(stderr, gettext(
			"tabs: %s: unknown tab code\n"), scan);
			usage();
		} else;
	else
		arbitab(scan, tabvect);
}

/*	repetab: scan and set repetitve tabs, 1+n, 1+2*n, etc */

void
repetab(char *scan, int tabvect[NTABS])
{
	register incr, i, tabn;
	int limit;
	incr = getnum(&scan);
	tabn = 1;
	limit = (maxtab-1)/(incr?incr:1)-1; /* # last actual tab */
	if (limit > NTABS-2)
		limit = NTABS-2;
	for (i = 0; i <= limit; i++)
		tabvect[i] = tabn += incr;
	tabvect[i] = 0;
}

/*	arbitab: handle list of arbitrary tabs */

void
arbitab(char *scan, int tabvect[NTABS])
{
	char *scan_save;
	register i, t, last;

	scan_save = scan;
	last = 0;
	for (i = 0; i < NTABS-1; ) {
		if (*scan == '+') {
			scan++;		/* +n ==> increment, not absolute */
			if (t = getnum(&scan))
				tabvect[i++] = last += t;
			else {
				endup();
				(void) fprintf(stderr, gettext(
				"tabs: %s: invalid increment\n"), scan_save);
				usage();
			}
		} else {
			if ((t = getnum(&scan)) > last)
				tabvect[i++] = last = t;
			else {
				endup();
				(void) fprintf(stderr, gettext(
				"tabs: %s: invalid tab stop\n"), scan_save);
				usage();
			}
		}
		if (*scan++ != ',') break;
	}
	if (last > NCOLS) {
		endup();
		(void) fprintf(stderr, gettext(
	"tabs: %s: last tab stop would be set at a column greater than %d\n"),
		scan_save, NCOLS);
		usage();
	}
	tabvect[i] = 0;
}

/*	filetab: copy tabspec from existing file */
#define	CARDSIZ	132

void
filetab(char *scan, int tabvect[NTABS], int level)
{
	register length, i;
	register char c;
	int fildes;
	char card[CARDSIZ];	/* buffer area for 1st card in file */
	char state, found;
	char *temp;
	if (level) {
		endup();
		(void) fprintf(stderr, gettext(
		"tabs: %s points to another file: invalid file indirection\n"),
		scan);
		exit(1);
	}
	if ((fildes = open(scan, O_RDONLY)) < 0) {
		endup();
		(void) fprintf(stderr, gettext("tabs: %s: "), scan);
		perror("");
		exit(1);
	}
	length = read(fildes, card, CARDSIZ);
	(void) close(fildes);
	found = state = 0;
	scan = 0;
	for (i = 0; i < length && (c = card[i]) != '\n'; i++) {
		switch (state) {
		case 0:
			state = (c == '<'); break;
		case 1:
			state = (c == ':')?2:0; break;
		case 2:
			if (c == 't')
				state = 3;
			else if (c == ':')
				state = 6;
			else if (c != ' ')
				state = 5;
			break;
		case 3:
			if (c == ' ')
				state = 2;
			else {
				scan = &card[i];
				state = 4;
			}
			break;
		case 4:
			if (c == ' ') {
				card[i] = '\0';
				state = 5;
			} else if (c == ':') {
				card[i] = '\0';
				state = 6;
			}
			break;
		case 5:
			if (c == ' ')
				state = 2;
			else if (c == ':')
				state = 6;
			break;
		case 6:
			if (c == '>') {
				found = 1;
				goto done;
			} else state = 5;
			break;
		}
	}
done:
	if (found && scan != 0) {
		scantab(scan, tabvect, 1);
		temp = scan;
		while (*++temp);
		*temp = '\n';
	}
	else
		scantab("-8", tabvect, 1);
}

int
getmarg(char *term)
{
	if (strncmp(term, "1620", 4) == 0 ||
		strncmp(term, "1700", 4) == 0 || strncmp(term, "450", 3) == 0)
		return (DMG);
	else if (strncmp(term, "300s", 4) == 0)
		return (GMG);
	else if (strncmp(term, "4000a", 5) == 0)
		return (TRMG);
	else if (strcmp(term, "43") == 0)
		return (FMG);
	else if (strcmp(term, "tn300") == 0 || strcmp(term, "tn1200") == 0)
		return (TMG);
	else
		return (NMG);
}



struct ttab *
termadj()
{
	register struct ttab *t;

	if (strncmp(terminal, "40-2", 4) == 0 || strncmp(terminal,
			"40/2", 4) == 0 || strncmp(terminal, "4420", 4) == 0)
		(void) strcpy(terminal, "4424");
	else if (strncmp(terminal, "ibm", 3) == 0 || strcmp(terminal,
			"3101") == 0 || strcmp(terminal, "system1") == 0)
		(void) strcpy(terminal, "ibm");

	for (t = termtab; t->ttype; t++) {
		if (EQ(terminal, t->ttype))
			return (t);
	}
/* should have message */
	return (termtab);
}

char	*cleartabs();
/*
 *	settabs: set actual tabs at terminal
 *	note: this code caters to necessities of handling GSI and
 *	other terminals in a consistent way.
 */

void
settabs(int tabvect[NTABS])
{
	char setbuf[512];	/* 2+3*NTABS+2+NCOLS+NTABS (+ some extra) */
	register char *p;		/* ptr for assembly in setbuf */
	register *curtab;		/* ptr to tabvect item */
	int i, previous, nblanks;
	if (istty) {
		ttyold.c_iflag &= ~ICRNL;
		ttyold.c_oflag &= ~(ONLCR|OCRNL|ONOCR|ONLRET);
		(void) ioctl(1, TCSETAW, &ttyold);	/* turn off cr-lf map */
	}
	p = setbuf;
	*p++ = CR;
	p = cleartabs(p, clear_tabs);

	if (margflg) {
		tmarg = getmarg(terminal);
		switch (tmarg) {
		case GMG:	/* GSI300S */
		/*
		 * NOTE: the 300S appears somewhat odd, in that there is
		 * a column 0, but there is no way to do a direct tab to it.
		 * The sequence ESC 'T' '\0' jumps to column 27 and prints
		 * a '0', without changing the margin.
		 */
			*p++ = ESC;
			*p++ = 'T';	/* setup for direct tab */
			if (margin &= 0177)	/* normal case */
				*p++ = margin;
			else {			/* +m0 case */
				*p++ = 1;	/* column 1 */
				*p++ = '\b';	/* column 0 */
			}
			*p++ = margin;	/* direct horizontal tab */
			*p++ = ESC;
			*p++ = '0';	/* actual margin set */
			break;
		case TMG:	/* TERMINET 300 & 1200 */
			while (margin--)
				*p++ = ' ';
			break;
		case DMG:	/* DASI450/DIABLO 1620 */
			*p++ = ESC;	/* direct tab ignores margin */
			*p++ = '\t';
			if (margin == 3) {
				*p++ = (margin & 0177);
				*p++ = ' ';
			}
			else
				*p++ = (margin & 0177) + 1;
			*p++ = ESC;
			*p++ = '9';
			break;
		case FMG:	/* TTY 43 */
			p--;
			*p++ = ESC;
			*p++ = 'x';
			*p++ = CR;
			while (margin--)
				*p++ = ' ';
			*p++ = ESC;
			*p++ = 'l';
			*p++ = CR;
			(void) write(1, setbuf, p - setbuf);
			return;
		case TRMG:
			p--;
			*p++ = ESC;
			*p++ = 'N';
			while (margin--)
				*p++ = ' ';
			*p++ = ESC;
			*p++ = 'F';
			break;
		}
	}

/*
 *	actual setting: at least terminals do this consistently!
 */
	previous = 1; curtab = tabvect;
	while ((nblanks = *curtab-previous) >= 0 &&
		previous + nblanks <= maxtab) {
		for (i = 1; i <= nblanks; i++) *p++ = ' ';
		previous = *curtab++;
		(void) strcpy(p, settab);
		p += strlen(settab);
	}
	*p++ = CR;
	if (EQ(terminal, "4424"))
		*p++ = '\n';	/* TTY40/2 needs LF, not just CR */
	(void) write(1, setbuf, p - setbuf);
}


/*
 *	Set software tabs.  This only works on UNIX/370 using a series/1
 *	front-end processor.
 */


/*	cleartabs(pointer to buffer, pointer to clear sequence) */
char *
cleartabs(register char *p, char *qq)
{
	register i;
	register char *q;
	q = qq;
	if (clear_tabs == 0) {		/* if repetitive sequence */
		*p++ = CR;
		for (i = 0; i < NTABSCL - 1; i++) {
			*p++ = TAB;
			*p++ = ESC;
			*p++ = CLEAR;
		}
		*p++ = CR;
	} else {
		while (*p++ = *q++);	/* copy table sequence */
		p--;			/* adjust for null */
		if (EQ(terminal, "4424")) {	/* TTY40 extra delays needed */
			*p++ = '\0';
			*p++ = '\0';
			*p++ = '\0';
			*p++ = '\0';
		}
	}
	return (p);
}
/*	getnum: scan and convert number, return zero if none found */
/*	set scan ptr to addr of ending delimeter */
int
getnum(char **scan1)
{
	register n;
	register char c, *scan;
	n = 0;
	scan = *scan1;
	while ((c = *scan++) >= '0' && c <= '9') n = n * 10 + c -'0';
	*scan1 = --scan;
	return (n);
}

/*	usage: terminate processing with usage message */
void
usage()
{
	(void) fprintf(stderr, gettext(
"usage: tabs [ -n| --file| [[-code] -a| -a2| -c| -c2| -c3| -f| -p| -s| -u]] \
[+m[n]] [-T type]\n"));

	(void) fprintf(stderr, gettext(
"       tabs [-T type][+m[n]] n1[,n2,...]\n"));

	endup();
	exit(1);
}

/*	endup: make sure tty mode reset & exit */
void
endup()
{

	if (istty) {
		ttyold.c_iflag = ttyisave;
		ttyold.c_oflag = ttyosave;
		/* reset cr-lf to previous */
		(void) ioctl(1, TCSETAW, &ttyold);
		(void) chmod(devtty, statbuf.st_mode);
	}
	if (err > 0) {
		(void) resetterm();
	}
}

/*
 *	stdtabs: standard tabs table
	format: option code letter(s), null, tabs, null */
static char stdtabs[] = {
'a',	0, 1, 10, 16, 36, 72, 0,		/* IBM 370 Assembler */
'a', '2', 0, 1, 10, 16, 40, 72, 0,		/* IBM Assembler alternative */
'c',	0, 1, 8, 12, 16, 20, 55, 0,		/* COBOL, normal */
'c', '2', 0, 1, 6, 10, 14, 49, 0,		/* COBOL, crunched */
'c', '3', 0, 1, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 50, 54, 58, 62, 67,
	0,					/* crunched COBOL, many tabs */
'f',	0, 1, 7, 11, 15, 19, 23, 0,		/* FORTRAN */
'p',	0, 1, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 0,
						/* PL/I */
's',	0, 1, 10, 55, 0, 			/* SNOBOL */
'u',	0, 1, 12, 20, 44, 0,			/* UNIVAC ASM */
0};

/*
 *	stdtab: return tab list for any "canned" tab option.
 *	entry: option points to null-terminated option string
 *		tabvect points to vector to be filled in
 *	exit: return (0) if legal, tabvect filled, ending with zero
 *		return (-1) if unknown option
 */
int
stdtab(char option[], int tabvect[])
{
	register char *sp;
	tabvect[0] = 0;
	sp = stdtabs;
	while (*sp) {
		if (EQ(option, sp)) {
			while (*sp++);		/* skip to 1st tab value */
			while (*tabvect++ = *sp++);	/* copy, make int */
			return (0);
		}
		while (*sp++);	/* skip to 1st tab value */
		while (*sp++);		/* skip over tab list */
	}
	return (-1);
}
