7588e8b2f0f5821b0f58ca4d3e13f0c28c9a0874
[ncurses.git] / progs / tabs.c
1 /****************************************************************************
2  * Copyright (c) 2008 Free Software Foundation, Inc.                        *
3  *                                                                          *
4  * Permission is hereby granted, free of charge, to any person obtaining a  *
5  * copy of this software and associated documentation files (the            *
6  * "Software"), to deal in the Software without restriction, including      *
7  * without limitation the rights to use, copy, modify, merge, publish,      *
8  * distribute, distribute with modifications, sublicense, and/or sell       *
9  * copies of the Software, and to permit persons to whom the Software is    *
10  * furnished to do so, subject to the following conditions:                 *
11  *                                                                          *
12  * The above copyright notice and this permission notice shall be included  *
13  * in all copies or substantial portions of the Software.                   *
14  *                                                                          *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22  *                                                                          *
23  * Except as contained in this notice, the name(s) of the above copyright   *
24  * holders shall not be used in advertising or otherwise to promote the     *
25  * sale, use or other dealings in this Software without prior written       *
26  * authorization.                                                           *
27  ****************************************************************************/
28
29 /****************************************************************************
30  *  Author: Thomas E. Dickey                        2008                    *
31  ****************************************************************************/
32
33 /*
34  * tabs.c --  set terminal hard-tabstops
35  */
36
37 #define USE_LIBTINFO
38 #include <progs.priv.h>
39
40 MODULE_ID("$Id: tabs.c,v 1.11 2008/11/16 00:58:24 tom Exp $")
41
42 static void usage(void) GCC_NORETURN;
43
44 static int max_cols;
45
46 static int
47 putch(int c)
48 {
49     return putchar(c);
50 }
51
52 static void
53 do_tabs(int *tab_list)
54 {
55     int last = 1;
56     int stop;
57
58     putchar('\r');
59     while ((stop = *tab_list++) > 0) {
60         if (last < stop) {
61             while (last++ < stop) {
62                 if (last > max_cols)
63                     break;
64                 putchar(' ');
65             }
66         }
67         if (stop <= max_cols) {
68             tputs(tparm(set_tab, stop), 1, putch);
69             last = stop;
70         } else {
71             break;
72         }
73     }
74     putchar('\n');
75 }
76
77 static int *
78 decode_tabs(const char *tab_list)
79 {
80     int *result = typeCalloc(int, strlen(tab_list) + (unsigned) max_cols);
81     int n = 0;
82     int value = 0;
83     int prior = 0;
84     int ch;
85
86     while ((ch = *tab_list++) != '\0') {
87         if (isdigit(UChar(ch))) {
88             value *= 10;
89             value += (ch - '0');
90         } else if (ch == ',') {
91             result[n] = value + prior;
92             if (n > 0 && value <= result[n - 1]) {
93                 fprintf(stderr, "tab-stops are not in increasing order\n");
94                 ExitProgram(EXIT_FAILURE);
95             }
96             ++n;
97             value = 0;
98             prior = 0;
99         } else if (ch == '+') {
100             if (n)
101                 prior = result[n - 1];
102         }
103     }
104
105     /*
106      * If there is only one value, then it is an option such as "-8".
107      */
108     if ((n == 0) && (value > 0)) {
109         int step = value;
110         while (n < max_cols - 1) {
111             result[n++] = value;
112             value += step;
113         }
114     }
115
116     /*
117      * Add the last value, if any.
118      */
119     result[n++] = value;
120     result[n] = 0;
121     return result;
122 }
123
124 static void
125 print_ruler(int *tab_list)
126 {
127     int last = 0;
128     int stop;
129     int n;
130
131     /* first print a readable ruler */
132     for (n = 0; n < max_cols; n += 10) {
133         int ch = 1 + (n / 10);
134         char buffer[20];
135         sprintf(buffer, "----+----%c",
136                 ((ch < 10)
137                  ? (ch + '0')
138                  : (ch + 'A' - 10)));
139         printf("%.*s", ((max_cols - n) > 10) ? 10 : (max_cols - n), buffer);
140     }
141     putchar('\n');
142
143     /* now, print '*' for each stop */
144     for (n = 0, last = 0; (tab_list[n] > 0) && (last < max_cols); ++n) {
145         stop = tab_list[n];
146         while (++last < stop) {
147             if (last <= max_cols) {
148                 putchar('-');
149             } else {
150                 break;
151             }
152         }
153         if (last <= max_cols) {
154             putchar('*');
155             last = stop;
156         } else {
157             break;
158         }
159     }
160     while (++last <= max_cols)
161         putchar('-');
162     putchar('\n');
163 }
164
165 /*
166  * Write an '*' on each tabstop, to demonstrate whether it lines up with the
167  * ruler.
168  */
169 static void
170 write_tabs(int *tab_list)
171 {
172     int stop;
173
174     while ((stop = *tab_list++) > 0 && stop <= max_cols) {
175         fputs((stop == 1) ? "*" : "\t*", stdout);
176     };
177     /* also show a tab _past_ the stops */
178     if (stop < max_cols)
179         fputs("\t+", stdout);
180     putchar('\n');
181 }
182
183 static void
184 usage(void)
185 {
186     static const char *msg[] =
187     {
188         "Usage: tabs [options] [tabstop-list]"
189         ,""
190         ,"Options:"
191         ,"  -0       reset tabs"
192         ,"  -8       set tabs to standard interval"
193         ,"  -a       Assembler, IBM S/370, first format"
194         ,"  -a2      Assembler, IBM S/370, second format"
195         ,"  -c       COBOL, normal format"
196         ,"  -c2      COBOL compact format"
197         ,"  -c3      COBOL compact format extended"
198         ,"  -d       debug (show ruler with expected/actual tab positions)"
199         ,"  -f       FORTRAN"
200         ,"  -n       no-op (do not modify terminal settings)"
201         ,"  -p       PL/I"
202         ,"  -s       SNOBOL"
203         ,"  -u       UNIVAC 1100 Assembler"
204         ,"  -T name  use terminal type 'name'"
205         ,""
206         ,"A tabstop-list is an ordered list of column numbers, e.g., 1,11,21"
207         ,"or 1,+10,+10 which is the same."
208     };
209     unsigned n;
210
211     fflush(stdout);
212     for (n = 0; n < SIZEOF(msg); ++n) {
213         fprintf(stderr, "%s\n", msg[n]);
214     }
215     ExitProgram(EXIT_FAILURE);
216 }
217
218 int
219 main(int argc, char *argv[])
220 {
221     int rc = EXIT_SUCCESS;
222     bool debug = FALSE;
223     bool no_op = FALSE;
224     int n, ch;
225     NCURSES_CONST char *term_name = 0;
226     const char *mar_list = 0;   /* ignored */
227     char *append = 0;
228     const char *tab_list = 0;
229
230     if ((term_name = getenv("TERM")) == 0)
231         term_name = "ansi+tabs";
232
233     /* cannot use getopt, since some options are two-character */
234     for (n = 1; n < argc; ++n) {
235         char *option = argv[n];
236         switch (option[0]) {
237         case '-':
238             while ((ch = *++option) != '\0') {
239                 switch (ch) {
240                 case 'a':
241                     switch (*option) {
242                     case '\0':
243                         tab_list = "1,10,16,36,72";
244                         /* Assembler, IBM S/370, first format */
245                         break;
246                     case '2':
247                         tab_list = "1,10,16,40,72";
248                         /* Assembler, IBM S/370, second format */
249                         break;
250                     default:
251                         usage();
252                     }
253                     break;
254                 case 'c':
255                     switch (*option) {
256                     case '\0':
257                         tab_list = "1,8,12,16,20,55";
258                         /* COBOL, normal format */
259                         break;
260                     case '2':
261                         tab_list = "1,6,10,14,49";
262                         /* COBOL compact format */
263                         break;
264                     case '3':
265                         tab_list = "1,6,10,14,18,22,26,30,34,38,42,46,50,54,58,62,67";
266                         /* COBOL compact format extended */
267                         break;
268                     default:
269                         usage();
270                     }
271                     break;
272                 case 'd':       /* ncurses extension */
273                     debug = TRUE;
274                     break;
275                 case 'f':
276                     tab_list = "1,7,11,15,19,23";
277                     /* FORTRAN */
278                     break;
279                 case 'n':       /* ncurses extension */
280                     no_op = TRUE;
281                     break;
282                 case 'p':
283                     tab_list = "1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61";
284                     /* PL/I */
285                     break;
286                 case 's':
287                     tab_list = "1,10,55";
288                     /* SNOBOL */
289                     break;
290                 case 'u':
291                     tab_list = "1,12,20,44";
292                     /* UNIVAC 1100 Assembler */
293                     break;
294                 case 'T':
295                     ++n;
296                     if (*++option != '\0') {
297                         term_name = option;
298                     } else {
299                         term_name = argv[n++];
300                     }
301                     option += ((int) strlen(option)) - 1;
302                     continue;
303                 default:
304                     if (isdigit(UChar(*option))) {
305                         tab_list = option;
306                         ++n;
307                     } else {
308                         usage();
309                     }
310                     option += ((int) strlen(option)) - 1;
311                     break;
312                 }
313             }
314             break;
315         case '+':
316             while ((ch = *++option) != '\0') {
317                 switch (ch) {
318                 case 'm':
319                     mar_list = option;
320                     break;
321                 default:
322                     usage();
323                 }
324             }
325             break;
326         default:
327             if (isdigit(*option)) {
328                 if (append != 0) {
329                     if (tab_list != (const char *) append) {
330                         /* one of the predefined options was used */
331                         append = strdup(option);
332                         tab_list = append;
333                     } else {
334                         append = malloc(strlen(tab_list) + strlen(option) + 2);
335                         sprintf(append, "%s,%s", tab_list, option);
336                         free((char *) tab_list);
337                         tab_list = append;
338                     }
339                 } else {
340                     append = strdup(option);
341                     tab_list = append;
342                 }
343             } else {
344                 usage();
345             }
346             break;
347         }
348     }
349
350     setupterm(term_name, STDOUT_FILENO, (int *) 0);
351
352     max_cols = (columns > 0) ? columns : 80;
353
354     if (!VALID_STRING(clear_all_tabs)) {
355         fprintf(stderr,
356                 "%s: terminal type '%s' cannot reset tabs\n",
357                 argv[0], term_name);
358         rc = EXIT_FAILURE;
359     } else if (!VALID_STRING(set_tab)) {
360         fprintf(stderr,
361                 "%s: terminal type '%s' cannot set tabs\n",
362                 argv[0], term_name);
363         rc = EXIT_FAILURE;
364     } else {
365         int *list = decode_tabs(tab_list);
366
367         if (!no_op)
368             tputs(clear_all_tabs, 1, putch);
369
370         if (list != 0) {
371             if (!no_op)
372                 do_tabs(list);
373             if (debug) {
374                 print_ruler(list);
375                 write_tabs(list);
376             }
377             free(list);
378         }
379     }
380     if (append)
381         free(append);
382     ExitProgram(rc);
383 }