xref: /freebsd-src/contrib/ncurses/progs/tabs.c (revision 21817992b3314c908ab50f0bb88d2ee750b9c4ac)
106bfebdeSXin LI /****************************************************************************
2*21817992SBaptiste Daroussin  * Copyright 2020-2022,2023 Thomas E. Dickey                                *
3e1865124SBaptiste Daroussin  * Copyright 2008-2016,2017 Free Software Foundation, Inc.                  *
406bfebdeSXin LI  *                                                                          *
506bfebdeSXin LI  * Permission is hereby granted, free of charge, to any person obtaining a  *
606bfebdeSXin LI  * copy of this software and associated documentation files (the            *
706bfebdeSXin LI  * "Software"), to deal in the Software without restriction, including      *
806bfebdeSXin LI  * without limitation the rights to use, copy, modify, merge, publish,      *
906bfebdeSXin LI  * distribute, distribute with modifications, sublicense, and/or sell       *
1006bfebdeSXin LI  * copies of the Software, and to permit persons to whom the Software is    *
1106bfebdeSXin LI  * furnished to do so, subject to the following conditions:                 *
1206bfebdeSXin LI  *                                                                          *
1306bfebdeSXin LI  * The above copyright notice and this permission notice shall be included  *
1406bfebdeSXin LI  * in all copies or substantial portions of the Software.                   *
1506bfebdeSXin LI  *                                                                          *
1606bfebdeSXin LI  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
1706bfebdeSXin LI  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
1806bfebdeSXin LI  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
1906bfebdeSXin LI  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
2006bfebdeSXin LI  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
2106bfebdeSXin LI  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
2206bfebdeSXin LI  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
2306bfebdeSXin LI  *                                                                          *
2406bfebdeSXin LI  * Except as contained in this notice, the name(s) of the above copyright   *
2506bfebdeSXin LI  * holders shall not be used in advertising or otherwise to promote the     *
2606bfebdeSXin LI  * sale, use or other dealings in this Software without prior written       *
2706bfebdeSXin LI  * authorization.                                                           *
2806bfebdeSXin LI  ****************************************************************************/
2906bfebdeSXin LI 
3006bfebdeSXin LI /****************************************************************************
3106bfebdeSXin LI  *  Author: Thomas E. Dickey                        2008                    *
3206bfebdeSXin LI  ****************************************************************************/
3306bfebdeSXin LI 
3406bfebdeSXin LI /*
3506bfebdeSXin LI  * tabs.c --  set terminal hard-tabstops
3606bfebdeSXin LI  */
3706bfebdeSXin LI 
3806bfebdeSXin LI #define USE_LIBTINFO
3906bfebdeSXin LI #include <progs.priv.h>
40aae38d10SBaptiste Daroussin #include <tty_settings.h>
4106bfebdeSXin LI 
42*21817992SBaptiste Daroussin MODULE_ID("$Id: tabs.c,v 1.53 2023/11/04 20:46:09 tom Exp $")
4306bfebdeSXin LI 
44*21817992SBaptiste Daroussin static GCC_NORETURN void usage(void);
4506bfebdeSXin LI 
46aae38d10SBaptiste Daroussin const char *_nc_progname;
4706bfebdeSXin LI static int max_cols;
4806bfebdeSXin LI 
4973f0a83dSXin LI static void
failed(const char * s)5073f0a83dSXin LI failed(const char *s)
5173f0a83dSXin LI {
5273f0a83dSXin LI     perror(s);
5373f0a83dSXin LI     ExitProgram(EXIT_FAILURE);
5473f0a83dSXin LI }
5573f0a83dSXin LI 
5606bfebdeSXin LI static int
putch(int c)5706bfebdeSXin LI putch(int c)
5806bfebdeSXin LI {
5906bfebdeSXin LI     return putchar(c);
6006bfebdeSXin LI }
6106bfebdeSXin LI 
62*21817992SBaptiste Daroussin static char *
skip_csi(char * value)63*21817992SBaptiste Daroussin skip_csi(char *value)
64*21817992SBaptiste Daroussin {
65*21817992SBaptiste Daroussin     if (UChar(*value) == 0x9b)
66*21817992SBaptiste Daroussin 	++value;
67*21817992SBaptiste Daroussin     else if (!strncmp(value, "\033[", 2))
68*21817992SBaptiste Daroussin 	value += 2;
69*21817992SBaptiste Daroussin     return value;
70*21817992SBaptiste Daroussin }
71*21817992SBaptiste Daroussin 
72*21817992SBaptiste Daroussin /*
73*21817992SBaptiste Daroussin  * If the terminal uses ANSI clear_all_tabs, then it is not necessary to first
74*21817992SBaptiste Daroussin  * move to the left margin before clearing tabs.
75*21817992SBaptiste Daroussin  */
76*21817992SBaptiste Daroussin static bool
ansi_clear_tabs(void)77*21817992SBaptiste Daroussin ansi_clear_tabs(void)
78*21817992SBaptiste Daroussin {
79*21817992SBaptiste Daroussin     bool result = FALSE;
80*21817992SBaptiste Daroussin     if (VALID_STRING(clear_all_tabs)) {
81*21817992SBaptiste Daroussin 	char *param = skip_csi(clear_all_tabs);
82*21817992SBaptiste Daroussin 	if (!strcmp(param, "3g"))
83*21817992SBaptiste Daroussin 	    result = TRUE;
84*21817992SBaptiste Daroussin     }
85*21817992SBaptiste Daroussin     return result;
86*21817992SBaptiste Daroussin }
87*21817992SBaptiste Daroussin 
8806bfebdeSXin LI static void
do_tabs(int * tab_list)8906bfebdeSXin LI do_tabs(int *tab_list)
9006bfebdeSXin LI {
9106bfebdeSXin LI     int last = 1;
9206bfebdeSXin LI     int stop;
93*21817992SBaptiste Daroussin     bool first = TRUE;
9406bfebdeSXin LI 
9506bfebdeSXin LI     while ((stop = *tab_list++) > 0) {
96*21817992SBaptiste Daroussin 	if (first) {
97*21817992SBaptiste Daroussin 	    first = FALSE;
98*21817992SBaptiste Daroussin 	    putchar('\r');
99*21817992SBaptiste Daroussin 	}
10006bfebdeSXin LI 	if (last < stop) {
10106bfebdeSXin LI 	    while (last++ < stop) {
10206bfebdeSXin LI 		if (last > max_cols)
10306bfebdeSXin LI 		    break;
10406bfebdeSXin LI 		putchar(' ');
10506bfebdeSXin LI 	    }
10606bfebdeSXin LI 	}
10706bfebdeSXin LI 	if (stop <= max_cols) {
108*21817992SBaptiste Daroussin 	    tputs(set_tab, 1, putch);
10906bfebdeSXin LI 	    last = stop;
11006bfebdeSXin LI 	} else {
11106bfebdeSXin LI 	    break;
11206bfebdeSXin LI 	}
11306bfebdeSXin LI     }
114aae38d10SBaptiste Daroussin     putchar('\r');
11506bfebdeSXin LI }
11606bfebdeSXin LI 
117*21817992SBaptiste Daroussin /*
118*21817992SBaptiste Daroussin  * Decode a list of tab-stops from a string, returning an array of integers.
119*21817992SBaptiste Daroussin  * If the margin is positive (because the terminal does not support margins),
120*21817992SBaptiste Daroussin  * work around this by adding the margin to the decoded values.
121*21817992SBaptiste Daroussin  */
12206bfebdeSXin LI static int *
decode_tabs(const char * tab_list,int margin)123*21817992SBaptiste Daroussin decode_tabs(const char *tab_list, int margin)
12406bfebdeSXin LI {
12506bfebdeSXin LI     int *result = typeCalloc(int, strlen(tab_list) + (unsigned) max_cols);
12606bfebdeSXin LI     int n = 0;
12706bfebdeSXin LI     int value = 0;
12806bfebdeSXin LI     int prior = 0;
12906bfebdeSXin LI     int ch;
13006bfebdeSXin LI 
131*21817992SBaptiste Daroussin     if (result == NULL)
13273f0a83dSXin LI 	failed("decode_tabs");
13373f0a83dSXin LI 
134*21817992SBaptiste Daroussin     if (margin < 0)
135*21817992SBaptiste Daroussin 	margin = 0;
136*21817992SBaptiste Daroussin 
13706bfebdeSXin LI     while ((ch = *tab_list++) != '\0') {
13806bfebdeSXin LI 	if (isdigit(UChar(ch))) {
13906bfebdeSXin LI 	    value *= 10;
14006bfebdeSXin LI 	    value += (ch - '0');
141*21817992SBaptiste Daroussin 	    if (value > max_cols)
142*21817992SBaptiste Daroussin 		value = max_cols;
14306bfebdeSXin LI 	} else if (ch == ',') {
144*21817992SBaptiste Daroussin 	    result[n] = value + prior + margin;
14506bfebdeSXin LI 	    if (n > 0 && result[n] <= result[n - 1]) {
14606bfebdeSXin LI 		fprintf(stderr,
14773f0a83dSXin LI 			"%s: tab-stops are not in increasing order: %d %d\n",
148aae38d10SBaptiste Daroussin 			_nc_progname, value, result[n - 1]);
14906bfebdeSXin LI 		free(result);
15006bfebdeSXin LI 		result = 0;
15106bfebdeSXin LI 		break;
15206bfebdeSXin LI 	    }
15306bfebdeSXin LI 	    ++n;
15406bfebdeSXin LI 	    value = 0;
15506bfebdeSXin LI 	    prior = 0;
15606bfebdeSXin LI 	} else if (ch == '+') {
15706bfebdeSXin LI 	    if (n)
15806bfebdeSXin LI 		prior = result[n - 1];
15906bfebdeSXin LI 	}
16006bfebdeSXin LI     }
16106bfebdeSXin LI 
16206bfebdeSXin LI     if (result != 0) {
16306bfebdeSXin LI 	/*
16406bfebdeSXin LI 	 * If there is only one value, then it is an option such as "-8".
16506bfebdeSXin LI 	 */
16606bfebdeSXin LI 	if ((n == 0) && (value > 0)) {
16706bfebdeSXin LI 	    int step = value;
16873f0a83dSXin LI 	    value = 1;
16906bfebdeSXin LI 	    while (n < max_cols - 1) {
170*21817992SBaptiste Daroussin 		result[n++] = value + margin;
17106bfebdeSXin LI 		value += step;
17206bfebdeSXin LI 	    }
17306bfebdeSXin LI 	}
17406bfebdeSXin LI 
17506bfebdeSXin LI 	/*
17606bfebdeSXin LI 	 * Add the last value, if any.
17706bfebdeSXin LI 	 */
178*21817992SBaptiste Daroussin 	result[n++] = value + prior + margin;
17906bfebdeSXin LI 	result[n] = 0;
18006bfebdeSXin LI     }
18173f0a83dSXin LI 
18206bfebdeSXin LI     return result;
18306bfebdeSXin LI }
18406bfebdeSXin LI 
18506bfebdeSXin LI static void
print_ruler(const int * const tab_list,const char * new_line)186*21817992SBaptiste Daroussin print_ruler(const int *const tab_list, const char *new_line)
18706bfebdeSXin LI {
18806bfebdeSXin LI     int last = 0;
18906bfebdeSXin LI     int n;
19006bfebdeSXin LI 
19106bfebdeSXin LI     /* first print a readable ruler */
19206bfebdeSXin LI     for (n = 0; n < max_cols; n += 10) {
19306bfebdeSXin LI 	int ch = 1 + (n / 10);
19406bfebdeSXin LI 	char buffer[20];
19573f0a83dSXin LI 	_nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
19673f0a83dSXin LI 		    "----+----%c",
19706bfebdeSXin LI 		    ((ch < 10)
19806bfebdeSXin LI 		     ? (ch + '0')
19906bfebdeSXin LI 		     : (ch + 'A' - 10)));
20006bfebdeSXin LI 	printf("%.*s", ((max_cols - n) > 10) ? 10 : (max_cols - n), buffer);
20106bfebdeSXin LI     }
202*21817992SBaptiste Daroussin     printf("%s", new_line);
20306bfebdeSXin LI 
20406bfebdeSXin LI     /* now, print '*' for each stop */
20506bfebdeSXin LI     for (n = 0, last = 0; (tab_list[n] > 0) && (last < max_cols); ++n) {
206*21817992SBaptiste Daroussin 	int stop = tab_list[n];
207*21817992SBaptiste Daroussin 
20806bfebdeSXin LI 	while (++last < stop) {
20906bfebdeSXin LI 	    if (last <= max_cols) {
21006bfebdeSXin LI 		putchar('-');
21106bfebdeSXin LI 	    } else {
21206bfebdeSXin LI 		break;
21306bfebdeSXin LI 	    }
21406bfebdeSXin LI 	}
21506bfebdeSXin LI 	if (last <= max_cols) {
21606bfebdeSXin LI 	    putchar('*');
21706bfebdeSXin LI 	    last = stop;
21806bfebdeSXin LI 	} else {
21906bfebdeSXin LI 	    break;
22006bfebdeSXin LI 	}
22106bfebdeSXin LI     }
22206bfebdeSXin LI     while (++last <= max_cols)
22306bfebdeSXin LI 	putchar('-');
224*21817992SBaptiste Daroussin     printf("%s", new_line);
22506bfebdeSXin LI }
22606bfebdeSXin LI 
22706bfebdeSXin LI /*
22806bfebdeSXin LI  * Write an '*' on each tabstop, to demonstrate whether it lines up with the
22906bfebdeSXin LI  * ruler.
23006bfebdeSXin LI  */
23106bfebdeSXin LI static void
write_tabs(int * tab_list,const char * new_line)232*21817992SBaptiste Daroussin write_tabs(int *tab_list, const char *new_line)
23306bfebdeSXin LI {
23406bfebdeSXin LI     int stop;
23506bfebdeSXin LI 
23606bfebdeSXin LI     while ((stop = *tab_list++) > 0 && stop <= max_cols) {
23706bfebdeSXin LI 	fputs((stop == 1) ? "*" : "\t*", stdout);
23806bfebdeSXin LI     };
23906bfebdeSXin LI     /* also show a tab _past_ the stops */
24006bfebdeSXin LI     if (stop < max_cols)
24106bfebdeSXin LI 	fputs("\t+", stdout);
242*21817992SBaptiste Daroussin     fputs(new_line, stdout);
24306bfebdeSXin LI }
24406bfebdeSXin LI 
24506bfebdeSXin LI /*
24606bfebdeSXin LI  * Trim leading/trailing blanks, as well as blanks after a comma.
24706bfebdeSXin LI  * Convert embedded blanks to commas.
24806bfebdeSXin LI  */
24906bfebdeSXin LI static char *
trimmed_tab_list(const char * source)25006bfebdeSXin LI trimmed_tab_list(const char *source)
25106bfebdeSXin LI {
25206bfebdeSXin LI     char *result = strdup(source);
25306bfebdeSXin LI     if (result != 0) {
254*21817992SBaptiste Daroussin 	int j, k, last;
255*21817992SBaptiste Daroussin 
25606bfebdeSXin LI 	for (j = k = last = 0; result[j] != 0; ++j) {
257*21817992SBaptiste Daroussin 	    int ch = UChar(result[j]);
25806bfebdeSXin LI 	    if (isspace(ch)) {
25906bfebdeSXin LI 		if (last == '\0') {
26006bfebdeSXin LI 		    continue;
26106bfebdeSXin LI 		} else if (isdigit(last) || last == ',') {
26206bfebdeSXin LI 		    ch = ',';
26306bfebdeSXin LI 		}
26406bfebdeSXin LI 	    } else if (ch == ',') {
26506bfebdeSXin LI 		;
26606bfebdeSXin LI 	    } else {
26706bfebdeSXin LI 		if (last == ',')
26806bfebdeSXin LI 		    result[k++] = (char) last;
26906bfebdeSXin LI 		result[k++] = (char) ch;
27006bfebdeSXin LI 	    }
27106bfebdeSXin LI 	    last = ch;
27206bfebdeSXin LI 	}
27306bfebdeSXin LI 	result[k] = '\0';
27406bfebdeSXin LI     }
27506bfebdeSXin LI     return result;
27606bfebdeSXin LI }
27706bfebdeSXin LI 
27806bfebdeSXin LI static bool
comma_is_needed(const char * source)27906bfebdeSXin LI comma_is_needed(const char *source)
28006bfebdeSXin LI {
28106bfebdeSXin LI     bool result = FALSE;
28206bfebdeSXin LI 
28306bfebdeSXin LI     if (source != 0) {
28473f0a83dSXin LI 	size_t len = strlen(source);
28506bfebdeSXin LI 	if (len != 0)
28606bfebdeSXin LI 	    result = (source[len - 1] != ',');
28706bfebdeSXin LI     } else {
28806bfebdeSXin LI 	result = FALSE;
28906bfebdeSXin LI     }
29006bfebdeSXin LI     return result;
29106bfebdeSXin LI }
29206bfebdeSXin LI 
29306bfebdeSXin LI /*
29406bfebdeSXin LI  * Add a command-line parameter to the tab-list.  It can be blank- or comma-
29506bfebdeSXin LI  * separated (or a mixture).  For simplicity, empty tabs are ignored, e.g.,
29606bfebdeSXin LI  *	tabs 1,,6,11
29706bfebdeSXin LI  *	tabs 1,6,11
29806bfebdeSXin LI  * are treated the same.
29906bfebdeSXin LI  */
30006bfebdeSXin LI static const char *
add_to_tab_list(char ** append,const char * value)30106bfebdeSXin LI add_to_tab_list(char **append, const char *value)
30206bfebdeSXin LI {
30306bfebdeSXin LI     char *result = *append;
30406bfebdeSXin LI     char *copied = trimmed_tab_list(value);
30506bfebdeSXin LI 
30606bfebdeSXin LI     if (copied != 0 && *copied != '\0') {
30706bfebdeSXin LI 	const char *comma = ",";
30873f0a83dSXin LI 	size_t need = 1 + strlen(copied);
30906bfebdeSXin LI 
31006bfebdeSXin LI 	if (*copied == ',')
31106bfebdeSXin LI 	    comma = "";
31206bfebdeSXin LI 	else if (!comma_is_needed(*append))
31306bfebdeSXin LI 	    comma = "";
31406bfebdeSXin LI 
31506bfebdeSXin LI 	need += strlen(comma);
31606bfebdeSXin LI 	if (*append != 0)
31706bfebdeSXin LI 	    need += strlen(*append);
31806bfebdeSXin LI 
31906bfebdeSXin LI 	result = malloc(need);
32073f0a83dSXin LI 	if (result == 0)
32173f0a83dSXin LI 	    failed("add_to_tab_list");
32273f0a83dSXin LI 
32306bfebdeSXin LI 	*result = '\0';
32406bfebdeSXin LI 	if (*append != 0) {
32573f0a83dSXin LI 	    _nc_STRCPY(result, *append, need);
32606bfebdeSXin LI 	    free(*append);
32706bfebdeSXin LI 	}
32873f0a83dSXin LI 	_nc_STRCAT(result, comma, need);
32973f0a83dSXin LI 	_nc_STRCAT(result, copied, need);
33006bfebdeSXin LI 
33106bfebdeSXin LI 	*append = result;
33206bfebdeSXin LI     }
333aae38d10SBaptiste Daroussin     free(copied);
33406bfebdeSXin LI     return result;
33506bfebdeSXin LI }
33606bfebdeSXin LI 
33706bfebdeSXin LI /*
338*21817992SBaptiste Daroussin  * If the terminal supports it, (re)set the left margin and return true.
339*21817992SBaptiste Daroussin  * Otherwise, return false.
340*21817992SBaptiste Daroussin  */
341*21817992SBaptiste Daroussin static bool
do_set_margin(int margin,bool no_op)342*21817992SBaptiste Daroussin do_set_margin(int margin, bool no_op)
343*21817992SBaptiste Daroussin {
344*21817992SBaptiste Daroussin     bool result = FALSE;
345*21817992SBaptiste Daroussin 
346*21817992SBaptiste Daroussin     if (margin == 0) {		/* 0 is special case for resetting */
347*21817992SBaptiste Daroussin 	if (VALID_STRING(clear_margins)) {
348*21817992SBaptiste Daroussin 	    result = TRUE;
349*21817992SBaptiste Daroussin 	    if (!no_op)
350*21817992SBaptiste Daroussin 		tputs(clear_margins, 1, putch);
351*21817992SBaptiste Daroussin 	}
352*21817992SBaptiste Daroussin     } else if (margin-- < 0) {	/* margin will be 0-based from here on */
353*21817992SBaptiste Daroussin 	result = TRUE;
354*21817992SBaptiste Daroussin     } else if (VALID_STRING(set_left_margin)) {
355*21817992SBaptiste Daroussin 	result = TRUE;
356*21817992SBaptiste Daroussin 	if (!no_op) {
357*21817992SBaptiste Daroussin 	    /*
358*21817992SBaptiste Daroussin 	     * assuming we're on the first column of the line, move the cursor
359*21817992SBaptiste Daroussin 	     * to the column at which we will set a margin.
360*21817992SBaptiste Daroussin 	     */
361*21817992SBaptiste Daroussin 	    if (VALID_STRING(column_address)) {
362*21817992SBaptiste Daroussin 		tputs(TIPARM_1(column_address, margin), 1, putch);
363*21817992SBaptiste Daroussin 	    } else if (margin >= 1) {
364*21817992SBaptiste Daroussin 		if (VALID_STRING(parm_right_cursor)) {
365*21817992SBaptiste Daroussin 		    tputs(TIPARM_1(parm_right_cursor, margin), 1, putch);
366*21817992SBaptiste Daroussin 		} else {
367*21817992SBaptiste Daroussin 		    while (margin-- > 0)
368*21817992SBaptiste Daroussin 			putch(' ');
369*21817992SBaptiste Daroussin 		}
370*21817992SBaptiste Daroussin 	    }
371*21817992SBaptiste Daroussin 	    tputs(set_left_margin, 1, putch);
372*21817992SBaptiste Daroussin 	}
373*21817992SBaptiste Daroussin     }
374*21817992SBaptiste Daroussin #if defined(set_left_margin_parm) && defined(set_right_margin_parm)
375*21817992SBaptiste Daroussin     else if (VALID_STRING(set_left_margin_parm)) {
376*21817992SBaptiste Daroussin 	result = TRUE;
377*21817992SBaptiste Daroussin 	if (!no_op) {
378*21817992SBaptiste Daroussin 	    if (VALID_STRING(set_right_margin_parm)) {
379*21817992SBaptiste Daroussin 		tputs(TIPARM_1(set_left_margin_parm, margin), 1, putch);
380*21817992SBaptiste Daroussin 	    } else {
381*21817992SBaptiste Daroussin 		tputs(TIPARM_2(set_left_margin_parm, margin, max_cols), 1, putch);
382*21817992SBaptiste Daroussin 	    }
383*21817992SBaptiste Daroussin 	}
384*21817992SBaptiste Daroussin     }
385*21817992SBaptiste Daroussin #endif
386*21817992SBaptiste Daroussin #if defined(set_lr_margin)
387*21817992SBaptiste Daroussin     else if (VALID_STRING(set_lr_margin)) {
388*21817992SBaptiste Daroussin 	result = TRUE;
389*21817992SBaptiste Daroussin 	if (!no_op) {
390*21817992SBaptiste Daroussin 	    tputs(TIPARM_2(set_lr_margin, margin, max_cols), 1, putch);
391*21817992SBaptiste Daroussin 	}
392*21817992SBaptiste Daroussin     }
393*21817992SBaptiste Daroussin #endif
394*21817992SBaptiste Daroussin     return result;
395*21817992SBaptiste Daroussin }
396*21817992SBaptiste Daroussin 
397*21817992SBaptiste Daroussin /*
39806bfebdeSXin LI  * Check for illegal characters in the tab-list.
39906bfebdeSXin LI  */
40006bfebdeSXin LI static bool
legal_tab_list(const char * tab_list)40173f0a83dSXin LI legal_tab_list(const char *tab_list)
40206bfebdeSXin LI {
40306bfebdeSXin LI     bool result = TRUE;
40406bfebdeSXin LI 
40506bfebdeSXin LI     if (tab_list != 0 && *tab_list != '\0') {
40606bfebdeSXin LI 	if (comma_is_needed(tab_list)) {
407*21817992SBaptiste Daroussin 	    int n;
408*21817992SBaptiste Daroussin 
40906bfebdeSXin LI 	    for (n = 0; tab_list[n] != '\0'; ++n) {
410*21817992SBaptiste Daroussin 		int ch = UChar(tab_list[n]);
411*21817992SBaptiste Daroussin 
41206bfebdeSXin LI 		if (!(isdigit(ch) || ch == ',' || ch == '+')) {
41306bfebdeSXin LI 		    fprintf(stderr,
41406bfebdeSXin LI 			    "%s: unexpected character found '%c'\n",
415aae38d10SBaptiste Daroussin 			    _nc_progname, ch);
41606bfebdeSXin LI 		    result = FALSE;
41706bfebdeSXin LI 		    break;
41806bfebdeSXin LI 		}
41906bfebdeSXin LI 	    }
42006bfebdeSXin LI 	} else {
421aae38d10SBaptiste Daroussin 	    fprintf(stderr, "%s: trailing comma found '%s'\n", _nc_progname, tab_list);
42206bfebdeSXin LI 	    result = FALSE;
42306bfebdeSXin LI 	}
42406bfebdeSXin LI     } else {
425*21817992SBaptiste Daroussin 	/* if no list given, default to "tabs -8" */
42606bfebdeSXin LI     }
42706bfebdeSXin LI     return result;
42806bfebdeSXin LI }
42906bfebdeSXin LI 
43073f0a83dSXin LI static char *
skip_list(char * value)43173f0a83dSXin LI skip_list(char *value)
43273f0a83dSXin LI {
43373f0a83dSXin LI     while (*value != '\0' &&
43473f0a83dSXin LI 	   (isdigit(UChar(*value)) ||
43573f0a83dSXin LI 	    isspace(UChar(*value)) ||
43673f0a83dSXin LI 	    strchr("+,", UChar(*value)) != 0)) {
43773f0a83dSXin LI 	++value;
43873f0a83dSXin LI     }
43973f0a83dSXin LI     return value;
44073f0a83dSXin LI }
44173f0a83dSXin LI 
44206bfebdeSXin LI static void
usage(void)44306bfebdeSXin LI usage(void)
44406bfebdeSXin LI {
445aae38d10SBaptiste Daroussin #define DATA(s) s "\n"
446aae38d10SBaptiste Daroussin     static const char msg[] =
44706bfebdeSXin LI     {
448aae38d10SBaptiste Daroussin 	DATA("Usage: tabs [options] [tabstop-list]")
449aae38d10SBaptiste Daroussin 	DATA("")
450aae38d10SBaptiste Daroussin 	DATA("Options:")
451aae38d10SBaptiste Daroussin 	DATA("  -0       reset tabs")
452aae38d10SBaptiste Daroussin 	DATA("  -8       set tabs to standard interval")
453aae38d10SBaptiste Daroussin 	DATA("  -a       Assembler, IBM S/370, first format")
454aae38d10SBaptiste Daroussin 	DATA("  -a2      Assembler, IBM S/370, second format")
455aae38d10SBaptiste Daroussin 	DATA("  -c       COBOL, normal format")
456aae38d10SBaptiste Daroussin 	DATA("  -c2      COBOL compact format")
457aae38d10SBaptiste Daroussin 	DATA("  -c3      COBOL compact format extended")
458aae38d10SBaptiste Daroussin 	DATA("  -d       debug (show ruler with expected/actual tab positions)")
459aae38d10SBaptiste Daroussin 	DATA("  -f       FORTRAN")
460aae38d10SBaptiste Daroussin 	DATA("  -n       no-op (do not modify terminal settings)")
461aae38d10SBaptiste Daroussin 	DATA("  -p       PL/I")
462aae38d10SBaptiste Daroussin 	DATA("  -s       SNOBOL")
463aae38d10SBaptiste Daroussin 	DATA("  -u       UNIVAC 1100 Assembler")
464aae38d10SBaptiste Daroussin 	DATA("  -T name  use terminal type 'name'")
465aae38d10SBaptiste Daroussin 	DATA("  -V       print version")
466aae38d10SBaptiste Daroussin 	DATA("")
467aae38d10SBaptiste Daroussin 	DATA("A tabstop-list is an ordered list of column numbers, e.g., 1,11,21")
468aae38d10SBaptiste Daroussin 	DATA("or 1,+10,+10 which is the same.")
46906bfebdeSXin LI     };
470aae38d10SBaptiste Daroussin #undef DATA
47106bfebdeSXin LI 
47206bfebdeSXin LI     fflush(stdout);
473aae38d10SBaptiste Daroussin     fputs(msg, stderr);
47406bfebdeSXin LI     ExitProgram(EXIT_FAILURE);
47506bfebdeSXin LI }
47606bfebdeSXin LI 
47706bfebdeSXin LI int
main(int argc,char * argv[])47806bfebdeSXin LI main(int argc, char *argv[])
47906bfebdeSXin LI {
48006bfebdeSXin LI     int rc = EXIT_FAILURE;
48106bfebdeSXin LI     bool debug = FALSE;
48206bfebdeSXin LI     bool no_op = FALSE;
483*21817992SBaptiste Daroussin     bool change_tty = FALSE;
48406bfebdeSXin LI     int n, ch;
48506bfebdeSXin LI     NCURSES_CONST char *term_name = 0;
48606bfebdeSXin LI     char *append = 0;
48706bfebdeSXin LI     const char *tab_list = 0;
488*21817992SBaptiste Daroussin     const char *new_line = "\n";
489*21817992SBaptiste Daroussin     int margin = -1;
490aae38d10SBaptiste Daroussin     TTY tty_settings;
491aae38d10SBaptiste Daroussin     int fd;
49206bfebdeSXin LI 
493aae38d10SBaptiste Daroussin     _nc_progname = _nc_rootname(argv[0]);
494aae38d10SBaptiste Daroussin 
49506bfebdeSXin LI     if ((term_name = getenv("TERM")) == 0)
49606bfebdeSXin LI 	term_name = "ansi+tabs";
49706bfebdeSXin LI 
49806bfebdeSXin LI     /* cannot use getopt, since some options are two-character */
49906bfebdeSXin LI     for (n = 1; n < argc; ++n) {
50006bfebdeSXin LI 	char *option = argv[n];
50106bfebdeSXin LI 	switch (option[0]) {
50206bfebdeSXin LI 	case '-':
50306bfebdeSXin LI 	    while ((ch = *++option) != '\0') {
50406bfebdeSXin LI 		switch (ch) {
50506bfebdeSXin LI 		case 'a':
50673f0a83dSXin LI 		    switch (*++option) {
50773f0a83dSXin LI 		    default:
50806bfebdeSXin LI 		    case '\0':
50906bfebdeSXin LI 			tab_list = "1,10,16,36,72";
51073f0a83dSXin LI 			option--;
51106bfebdeSXin LI 			/* Assembler, IBM S/370, first format */
51206bfebdeSXin LI 			break;
51306bfebdeSXin LI 		    case '2':
51406bfebdeSXin LI 			tab_list = "1,10,16,40,72";
51506bfebdeSXin LI 			/* Assembler, IBM S/370, second format */
51606bfebdeSXin LI 			break;
51706bfebdeSXin LI 		    }
51806bfebdeSXin LI 		    break;
51906bfebdeSXin LI 		case 'c':
52073f0a83dSXin LI 		    switch (*++option) {
52173f0a83dSXin LI 		    default:
52206bfebdeSXin LI 		    case '\0':
52306bfebdeSXin LI 			tab_list = "1,8,12,16,20,55";
52473f0a83dSXin LI 			option--;
52506bfebdeSXin LI 			/* COBOL, normal format */
52606bfebdeSXin LI 			break;
52706bfebdeSXin LI 		    case '2':
52806bfebdeSXin LI 			tab_list = "1,6,10,14,49";
52906bfebdeSXin LI 			/* COBOL compact format */
53006bfebdeSXin LI 			break;
53106bfebdeSXin LI 		    case '3':
53206bfebdeSXin LI 			tab_list = "1,6,10,14,18,22,26,30,34,38,42,46,50,54,58,62,67";
53306bfebdeSXin LI 			/* COBOL compact format extended */
53406bfebdeSXin LI 			break;
53506bfebdeSXin LI 		    }
53606bfebdeSXin LI 		    break;
53706bfebdeSXin LI 		case 'd':	/* ncurses extension */
53806bfebdeSXin LI 		    debug = TRUE;
53906bfebdeSXin LI 		    break;
54006bfebdeSXin LI 		case 'f':
54106bfebdeSXin LI 		    tab_list = "1,7,11,15,19,23";
54206bfebdeSXin LI 		    /* FORTRAN */
54306bfebdeSXin LI 		    break;
54406bfebdeSXin LI 		case 'n':	/* ncurses extension */
54506bfebdeSXin LI 		    no_op = TRUE;
54606bfebdeSXin LI 		    break;
54706bfebdeSXin LI 		case 'p':
54806bfebdeSXin LI 		    tab_list = "1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61";
54906bfebdeSXin LI 		    /* PL/I */
55006bfebdeSXin LI 		    break;
55106bfebdeSXin LI 		case 's':
55206bfebdeSXin LI 		    tab_list = "1,10,55";
55306bfebdeSXin LI 		    /* SNOBOL */
55406bfebdeSXin LI 		    break;
55506bfebdeSXin LI 		case 'u':
55606bfebdeSXin LI 		    tab_list = "1,12,20,44";
55706bfebdeSXin LI 		    /* UNIVAC 1100 Assembler */
55806bfebdeSXin LI 		    break;
55906bfebdeSXin LI 		case 'T':
56006bfebdeSXin LI 		    ++n;
56106bfebdeSXin LI 		    if (*++option != '\0') {
56206bfebdeSXin LI 			term_name = option;
56306bfebdeSXin LI 		    } else {
564aae38d10SBaptiste Daroussin 			term_name = argv[n];
56573f0a83dSXin LI 			option--;
56606bfebdeSXin LI 		    }
56706bfebdeSXin LI 		    option += ((int) strlen(option)) - 1;
56806bfebdeSXin LI 		    continue;
56973f0a83dSXin LI 		case 'V':
57073f0a83dSXin LI 		    puts(curses_version());
57173f0a83dSXin LI 		    ExitProgram(EXIT_SUCCESS);
57206bfebdeSXin LI 		default:
57306bfebdeSXin LI 		    if (isdigit(UChar(*option))) {
57473f0a83dSXin LI 			char *copy = strdup(option);
57573f0a83dSXin LI 			*skip_list(copy) = '\0';
57673f0a83dSXin LI 			tab_list = copy;
57773f0a83dSXin LI 			option = skip_list(option) - 1;
57806bfebdeSXin LI 		    } else {
57906bfebdeSXin LI 			usage();
58006bfebdeSXin LI 		    }
58106bfebdeSXin LI 		    break;
58206bfebdeSXin LI 		}
58306bfebdeSXin LI 	    }
58406bfebdeSXin LI 	    break;
58506bfebdeSXin LI 	case '+':
586*21817992SBaptiste Daroussin 	    if ((ch = *++option) != '\0') {
587*21817992SBaptiste Daroussin 		int digits = 0;
588*21817992SBaptiste Daroussin 		int number = 0;
589*21817992SBaptiste Daroussin 
59006bfebdeSXin LI 		switch (ch) {
59106bfebdeSXin LI 		case 'm':
59273f0a83dSXin LI 		    /*
59373f0a83dSXin LI 		     * The "+mXXX" option is unimplemented because only the long-obsolete
59473f0a83dSXin LI 		     * att510d implements smgl, which is needed to support
59573f0a83dSXin LI 		     * this option.
59673f0a83dSXin LI 		     */
597*21817992SBaptiste Daroussin 		    while ((ch = *++option) != '\0') {
598*21817992SBaptiste Daroussin 			if (isdigit(UChar(ch))) {
599*21817992SBaptiste Daroussin 			    ++digits;
600*21817992SBaptiste Daroussin 			    number = number * 10 + (ch - '0');
601*21817992SBaptiste Daroussin 			} else {
602*21817992SBaptiste Daroussin 			    usage();
603*21817992SBaptiste Daroussin 			}
604*21817992SBaptiste Daroussin 		    }
605*21817992SBaptiste Daroussin 		    if (digits == 0)
606*21817992SBaptiste Daroussin 			number = 10;
607*21817992SBaptiste Daroussin 		    margin = number;
60806bfebdeSXin LI 		    break;
60906bfebdeSXin LI 		default:
61006bfebdeSXin LI 		    /* special case of relative stops separated by spaces? */
61106bfebdeSXin LI 		    if (option == argv[n] + 1) {
61206bfebdeSXin LI 			tab_list = add_to_tab_list(&append, argv[n]);
61306bfebdeSXin LI 		    }
61406bfebdeSXin LI 		    break;
61506bfebdeSXin LI 		}
61606bfebdeSXin LI 	    }
61706bfebdeSXin LI 	    break;
61806bfebdeSXin LI 	default:
61906bfebdeSXin LI 	    if (append != 0) {
62006bfebdeSXin LI 		if (tab_list != (const char *) append) {
62106bfebdeSXin LI 		    /* one of the predefined options was used */
62206bfebdeSXin LI 		    free(append);
62306bfebdeSXin LI 		    append = 0;
62406bfebdeSXin LI 		}
62506bfebdeSXin LI 	    }
62606bfebdeSXin LI 	    tab_list = add_to_tab_list(&append, option);
62706bfebdeSXin LI 	    break;
62806bfebdeSXin LI 	}
62906bfebdeSXin LI     }
63006bfebdeSXin LI 
631*21817992SBaptiste Daroussin     fd = save_tty_settings(&tty_settings, FALSE);
632*21817992SBaptiste Daroussin 
633aae38d10SBaptiste Daroussin     setupterm(term_name, fd, (int *) 0);
63406bfebdeSXin LI 
63506bfebdeSXin LI     max_cols = (columns > 0) ? columns : 80;
636*21817992SBaptiste Daroussin     if (margin > 0)
637*21817992SBaptiste Daroussin 	max_cols -= margin;
63806bfebdeSXin LI 
63906bfebdeSXin LI     if (!VALID_STRING(clear_all_tabs)) {
64006bfebdeSXin LI 	fprintf(stderr,
64106bfebdeSXin LI 		"%s: terminal type '%s' cannot reset tabs\n",
642aae38d10SBaptiste Daroussin 		_nc_progname, term_name);
64306bfebdeSXin LI     } else if (!VALID_STRING(set_tab)) {
64406bfebdeSXin LI 	fprintf(stderr,
64506bfebdeSXin LI 		"%s: terminal type '%s' cannot set tabs\n",
646aae38d10SBaptiste Daroussin 		_nc_progname, term_name);
64773f0a83dSXin LI     } else if (legal_tab_list(tab_list)) {
648*21817992SBaptiste Daroussin 	int *list;
64906bfebdeSXin LI 
650*21817992SBaptiste Daroussin 	if (tab_list == NULL)
651*21817992SBaptiste Daroussin 	    tab_list = add_to_tab_list(&append, "8");
652*21817992SBaptiste Daroussin 
653*21817992SBaptiste Daroussin 	if (!no_op) {
654*21817992SBaptiste Daroussin #if defined(TERMIOS) && defined(OCRNL)
655*21817992SBaptiste Daroussin 	    /* set tty modes to -ocrnl to allow \r */
656*21817992SBaptiste Daroussin 	    if (isatty(STDOUT_FILENO)) {
657*21817992SBaptiste Daroussin 		TTY new_settings = tty_settings;
658*21817992SBaptiste Daroussin 		new_settings.c_oflag &= (unsigned) ~OCRNL;
659*21817992SBaptiste Daroussin 		update_tty_settings(&tty_settings, &new_settings);
660*21817992SBaptiste Daroussin 		change_tty = TRUE;
661*21817992SBaptiste Daroussin 		new_line = "\r\n";
662*21817992SBaptiste Daroussin 	    }
663*21817992SBaptiste Daroussin #endif
664*21817992SBaptiste Daroussin 
665*21817992SBaptiste Daroussin 	    if (!ansi_clear_tabs())
666*21817992SBaptiste Daroussin 		putch('\r');
66706bfebdeSXin LI 	    tputs(clear_all_tabs, 1, putch);
668*21817992SBaptiste Daroussin 	}
669*21817992SBaptiste Daroussin 
670*21817992SBaptiste Daroussin 	if (margin >= 0) {
671*21817992SBaptiste Daroussin 	    putch('\r');
672*21817992SBaptiste Daroussin 	    if (margin > 0) {
673*21817992SBaptiste Daroussin 		/* reset existing margin before setting margin, to reduce
674*21817992SBaptiste Daroussin 		 * problems moving left of the current margin.
675*21817992SBaptiste Daroussin 		 */
676*21817992SBaptiste Daroussin 		if (do_set_margin(0, no_op))
677*21817992SBaptiste Daroussin 		    putch('\r');
678*21817992SBaptiste Daroussin 	    }
679*21817992SBaptiste Daroussin 	    if (do_set_margin(margin, no_op))
680*21817992SBaptiste Daroussin 		margin = -1;
681*21817992SBaptiste Daroussin 	}
682*21817992SBaptiste Daroussin 
683*21817992SBaptiste Daroussin 	list = decode_tabs(tab_list, margin);
68406bfebdeSXin LI 
68506bfebdeSXin LI 	if (list != 0) {
68606bfebdeSXin LI 	    if (!no_op)
68706bfebdeSXin LI 		do_tabs(list);
68806bfebdeSXin LI 	    if (debug) {
68906bfebdeSXin LI 		fflush(stderr);
690*21817992SBaptiste Daroussin 		printf("tabs %s%s", tab_list, new_line);
691*21817992SBaptiste Daroussin 		print_ruler(list, new_line);
692*21817992SBaptiste Daroussin 		write_tabs(list, new_line);
69306bfebdeSXin LI 	    }
69406bfebdeSXin LI 	    free(list);
69506bfebdeSXin LI 	} else if (debug) {
69606bfebdeSXin LI 	    fflush(stderr);
697*21817992SBaptiste Daroussin 	    printf("tabs %s%s", tab_list, new_line);
698*21817992SBaptiste Daroussin 	}
699*21817992SBaptiste Daroussin 	if (!no_op) {
700*21817992SBaptiste Daroussin 	    if (change_tty) {
701*21817992SBaptiste Daroussin 		restore_tty_settings();
702*21817992SBaptiste Daroussin 	    }
70306bfebdeSXin LI 	}
70406bfebdeSXin LI 	rc = EXIT_SUCCESS;
70506bfebdeSXin LI     }
70606bfebdeSXin LI     if (append != 0)
70706bfebdeSXin LI 	free(append);
70806bfebdeSXin LI     ExitProgram(rc);
70906bfebdeSXin LI }
710