ncurses 6.0 - patch 20161231
[ncurses.git] / progs / tabs.c
1 /****************************************************************************
2  * Copyright (c) 2008-2015,2016 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 #include <tty_settings.h>
40
41 MODULE_ID("$Id: tabs.c,v 1.38 2016/12/31 15:10:52 tom Exp $")
42
43 static void usage(void) GCC_NORETURN;
44
45 const char *_nc_progname;
46 static int max_cols;
47
48 static void
49 failed(const char *s)
50 {
51     perror(s);
52     ExitProgram(EXIT_FAILURE);
53 }
54
55 static int
56 putch(int c)
57 {
58     return putchar(c);
59 }
60
61 static void
62 do_tabs(int *tab_list)
63 {
64     int last = 1;
65     int stop;
66
67     putchar('\r');
68     while ((stop = *tab_list++) > 0) {
69         if (last < stop) {
70             while (last++ < stop) {
71                 if (last > max_cols)
72                     break;
73                 putchar(' ');
74             }
75         }
76         if (stop <= max_cols) {
77             tputs(tparm(set_tab, stop), 1, putch);
78             last = stop;
79         } else {
80             break;
81         }
82     }
83     putchar('\r');
84 }
85
86 static int *
87 decode_tabs(const char *tab_list)
88 {
89     int *result = typeCalloc(int, strlen(tab_list) + (unsigned) max_cols);
90     int n = 0;
91     int value = 0;
92     int prior = 0;
93     int ch;
94
95     if (result == 0)
96         failed("decode_tabs");
97
98     while ((ch = *tab_list++) != '\0') {
99         if (isdigit(UChar(ch))) {
100             value *= 10;
101             value += (ch - '0');
102         } else if (ch == ',') {
103             result[n] = value + prior;
104             if (n > 0 && result[n] <= result[n - 1]) {
105                 fprintf(stderr,
106                         "%s: tab-stops are not in increasing order: %d %d\n",
107                         _nc_progname, value, result[n - 1]);
108                 free(result);
109                 result = 0;
110                 break;
111             }
112             ++n;
113             value = 0;
114             prior = 0;
115         } else if (ch == '+') {
116             if (n)
117                 prior = result[n - 1];
118         }
119     }
120
121     if (result != 0) {
122         /*
123          * If there is only one value, then it is an option such as "-8".
124          */
125         if ((n == 0) && (value > 0)) {
126             int step = value;
127             value = 1;
128             while (n < max_cols - 1) {
129                 result[n++] = value;
130                 value += step;
131             }
132         }
133
134         /*
135          * Add the last value, if any.
136          */
137         result[n++] = value + prior;
138         result[n] = 0;
139     }
140
141     return result;
142 }
143
144 static void
145 print_ruler(int *tab_list)
146 {
147     int last = 0;
148     int stop;
149     int n;
150
151     /* first print a readable ruler */
152     for (n = 0; n < max_cols; n += 10) {
153         int ch = 1 + (n / 10);
154         char buffer[20];
155         _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
156                     "----+----%c",
157                     ((ch < 10)
158                      ? (ch + '0')
159                      : (ch + 'A' - 10)));
160         printf("%.*s", ((max_cols - n) > 10) ? 10 : (max_cols - n), buffer);
161     }
162     putchar('\n');
163
164     /* now, print '*' for each stop */
165     for (n = 0, last = 0; (tab_list[n] > 0) && (last < max_cols); ++n) {
166         stop = tab_list[n];
167         while (++last < stop) {
168             if (last <= max_cols) {
169                 putchar('-');
170             } else {
171                 break;
172             }
173         }
174         if (last <= max_cols) {
175             putchar('*');
176             last = stop;
177         } else {
178             break;
179         }
180     }
181     while (++last <= max_cols)
182         putchar('-');
183     putchar('\n');
184 }
185
186 /*
187  * Write an '*' on each tabstop, to demonstrate whether it lines up with the
188  * ruler.
189  */
190 static void
191 write_tabs(int *tab_list)
192 {
193     int stop;
194
195     while ((stop = *tab_list++) > 0 && stop <= max_cols) {
196         fputs((stop == 1) ? "*" : "\t*", stdout);
197     };
198     /* also show a tab _past_ the stops */
199     if (stop < max_cols)
200         fputs("\t+", stdout);
201     putchar('\n');
202 }
203
204 /*
205  * Trim leading/trailing blanks, as well as blanks after a comma.
206  * Convert embedded blanks to commas.
207  */
208 static char *
209 trimmed_tab_list(const char *source)
210 {
211     char *result = strdup(source);
212     int ch, j, k, last;
213
214     if (result != 0) {
215         for (j = k = last = 0; result[j] != 0; ++j) {
216             ch = UChar(result[j]);
217             if (isspace(ch)) {
218                 if (last == '\0') {
219                     continue;
220                 } else if (isdigit(last) || last == ',') {
221                     ch = ',';
222                 }
223             } else if (ch == ',') {
224                 ;
225             } else {
226                 if (last == ',')
227                     result[k++] = (char) last;
228                 result[k++] = (char) ch;
229             }
230             last = ch;
231         }
232         result[k] = '\0';
233     }
234     return result;
235 }
236
237 static bool
238 comma_is_needed(const char *source)
239 {
240     bool result = FALSE;
241
242     if (source != 0) {
243         size_t len = strlen(source);
244         if (len != 0)
245             result = (source[len - 1] != ',');
246     } else {
247         result = FALSE;
248     }
249     return result;
250 }
251
252 /*
253  * Add a command-line parameter to the tab-list.  It can be blank- or comma-
254  * separated (or a mixture).  For simplicity, empty tabs are ignored, e.g.,
255  *      tabs 1,,6,11
256  *      tabs 1,6,11
257  * are treated the same.
258  */
259 static const char *
260 add_to_tab_list(char **append, const char *value)
261 {
262     char *result = *append;
263     char *copied = trimmed_tab_list(value);
264
265     if (copied != 0 && *copied != '\0') {
266         const char *comma = ",";
267         size_t need = 1 + strlen(copied);
268
269         if (*copied == ',')
270             comma = "";
271         else if (!comma_is_needed(*append))
272             comma = "";
273
274         need += strlen(comma);
275         if (*append != 0)
276             need += strlen(*append);
277
278         result = malloc(need);
279         if (result == 0)
280             failed("add_to_tab_list");
281
282         *result = '\0';
283         if (*append != 0) {
284             _nc_STRCPY(result, *append, need);
285             free(*append);
286         }
287         _nc_STRCAT(result, comma, need);
288         _nc_STRCAT(result, copied, need);
289
290         *append = result;
291     }
292     free(copied);
293     return result;
294 }
295
296 /*
297  * Check for illegal characters in the tab-list.
298  */
299 static bool
300 legal_tab_list(const char *tab_list)
301 {
302     bool result = TRUE;
303
304     if (tab_list != 0 && *tab_list != '\0') {
305         if (comma_is_needed(tab_list)) {
306             int n, ch;
307             for (n = 0; tab_list[n] != '\0'; ++n) {
308                 ch = UChar(tab_list[n]);
309                 if (!(isdigit(ch) || ch == ',' || ch == '+')) {
310                     fprintf(stderr,
311                             "%s: unexpected character found '%c'\n",
312                             _nc_progname, ch);
313                     result = FALSE;
314                     break;
315                 }
316             }
317         } else {
318             fprintf(stderr, "%s: trailing comma found '%s'\n", _nc_progname, tab_list);
319             result = FALSE;
320         }
321     } else {
322         fprintf(stderr, "%s: no tab-list given\n", _nc_progname);
323         result = FALSE;
324     }
325     return result;
326 }
327
328 static char *
329 skip_list(char *value)
330 {
331     while (*value != '\0' &&
332            (isdigit(UChar(*value)) ||
333             isspace(UChar(*value)) ||
334             strchr("+,", UChar(*value)) != 0)) {
335         ++value;
336     }
337     return value;
338 }
339
340 static void
341 usage(void)
342 {
343 #define DATA(s) s "\n"
344     static const char msg[] =
345     {
346         DATA("Usage: tabs [options] [tabstop-list]")
347         DATA("")
348         DATA("Options:")
349         DATA("  -0       reset tabs")
350         DATA("  -8       set tabs to standard interval")
351         DATA("  -a       Assembler, IBM S/370, first format")
352         DATA("  -a2      Assembler, IBM S/370, second format")
353         DATA("  -c       COBOL, normal format")
354         DATA("  -c2      COBOL compact format")
355         DATA("  -c3      COBOL compact format extended")
356         DATA("  -d       debug (show ruler with expected/actual tab positions)")
357         DATA("  -f       FORTRAN")
358         DATA("  -n       no-op (do not modify terminal settings)")
359         DATA("  -p       PL/I")
360         DATA("  -s       SNOBOL")
361         DATA("  -u       UNIVAC 1100 Assembler")
362         DATA("  -T name  use terminal type 'name'")
363         DATA("  -V       print version")
364         DATA("")
365         DATA("A tabstop-list is an ordered list of column numbers, e.g., 1,11,21")
366         DATA("or 1,+10,+10 which is the same.")
367     };
368 #undef DATA
369
370     fflush(stdout);
371     fputs(msg, stderr);
372     ExitProgram(EXIT_FAILURE);
373 }
374
375 int
376 main(int argc, char *argv[])
377 {
378     int rc = EXIT_FAILURE;
379     bool debug = FALSE;
380     bool no_op = FALSE;
381     int n, ch;
382     NCURSES_CONST char *term_name = 0;
383     char *append = 0;
384     const char *tab_list = 0;
385     TTY tty_settings;
386     int fd;
387
388     _nc_progname = _nc_rootname(argv[0]);
389
390     fd = save_tty_settings(&tty_settings);
391
392     if ((term_name = getenv("TERM")) == 0)
393         term_name = "ansi+tabs";
394
395     /* cannot use getopt, since some options are two-character */
396     for (n = 1; n < argc; ++n) {
397         char *option = argv[n];
398         switch (option[0]) {
399         case '-':
400             while ((ch = *++option) != '\0') {
401                 switch (ch) {
402                 case 'a':
403                     switch (*++option) {
404                     default:
405                     case '\0':
406                         tab_list = "1,10,16,36,72";
407                         option--;
408                         /* Assembler, IBM S/370, first format */
409                         break;
410                     case '2':
411                         tab_list = "1,10,16,40,72";
412                         /* Assembler, IBM S/370, second format */
413                         break;
414                     }
415                     break;
416                 case 'c':
417                     switch (*++option) {
418                     default:
419                     case '\0':
420                         tab_list = "1,8,12,16,20,55";
421                         option--;
422                         /* COBOL, normal format */
423                         break;
424                     case '2':
425                         tab_list = "1,6,10,14,49";
426                         /* COBOL compact format */
427                         break;
428                     case '3':
429                         tab_list = "1,6,10,14,18,22,26,30,34,38,42,46,50,54,58,62,67";
430                         /* COBOL compact format extended */
431                         break;
432                     }
433                     break;
434                 case 'd':       /* ncurses extension */
435                     debug = TRUE;
436                     break;
437                 case 'f':
438                     tab_list = "1,7,11,15,19,23";
439                     /* FORTRAN */
440                     break;
441                 case 'n':       /* ncurses extension */
442                     no_op = TRUE;
443                     break;
444                 case 'p':
445                     tab_list = "1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61";
446                     /* PL/I */
447                     break;
448                 case 's':
449                     tab_list = "1,10,55";
450                     /* SNOBOL */
451                     break;
452                 case 'u':
453                     tab_list = "1,12,20,44";
454                     /* UNIVAC 1100 Assembler */
455                     break;
456                 case 'T':
457                     ++n;
458                     if (*++option != '\0') {
459                         term_name = option;
460                     } else {
461                         term_name = argv[n++];
462                         option--;
463                     }
464                     option += ((int) strlen(option)) - 1;
465                     continue;
466                 case 'V':
467                     puts(curses_version());
468                     ExitProgram(EXIT_SUCCESS);
469                 default:
470                     if (isdigit(UChar(*option))) {
471                         char *copy = strdup(option);
472                         *skip_list(copy) = '\0';
473                         tab_list = copy;
474                         option = skip_list(option) - 1;
475                     } else {
476                         usage();
477                     }
478                     break;
479                 }
480             }
481             break;
482         case '+':
483             while ((ch = *++option) != '\0') {
484                 switch (ch) {
485                 case 'm':
486                     /*
487                      * The "+mXXX" option is unimplemented because only the long-obsolete
488                      * att510d implements smgl, which is needed to support
489                      * this option.
490                      */
491                     break;
492                 default:
493                     /* special case of relative stops separated by spaces? */
494                     if (option == argv[n] + 1) {
495                         tab_list = add_to_tab_list(&append, argv[n]);
496                     }
497                     break;
498                 }
499             }
500             break;
501         default:
502             if (append != 0) {
503                 if (tab_list != (const char *) append) {
504                     /* one of the predefined options was used */
505                     free(append);
506                     append = 0;
507                 }
508             }
509             tab_list = add_to_tab_list(&append, option);
510             break;
511         }
512     }
513
514     setupterm(term_name, fd, (int *) 0);
515
516     max_cols = (columns > 0) ? columns : 80;
517
518     if (!VALID_STRING(clear_all_tabs)) {
519         fprintf(stderr,
520                 "%s: terminal type '%s' cannot reset tabs\n",
521                 _nc_progname, term_name);
522     } else if (!VALID_STRING(set_tab)) {
523         fprintf(stderr,
524                 "%s: terminal type '%s' cannot set tabs\n",
525                 _nc_progname, term_name);
526     } else if (legal_tab_list(tab_list)) {
527         int *list = decode_tabs(tab_list);
528
529         if (!no_op)
530             tputs(clear_all_tabs, 1, putch);
531
532         if (list != 0) {
533             if (!no_op)
534                 do_tabs(list);
535             if (debug) {
536                 fflush(stderr);
537                 printf("tabs %s\n", tab_list);
538                 print_ruler(list);
539                 write_tabs(list);
540             }
541             free(list);
542         } else if (debug) {
543             fflush(stderr);
544             printf("tabs %s\n", tab_list);
545         }
546         rc = EXIT_SUCCESS;
547     }
548     if (append != 0)
549         free(append);
550     ExitProgram(rc);
551 }