]> ncurses.scripts.mit.edu Git - ncurses.git/blobdiff - progs/tabs.c
ncurses 6.3 - patch 20220924
[ncurses.git] / progs / tabs.c
index aafaa2d8f37cf702a224a1ca8ad44e7889b7783e..7378d116f70c8f48c90df1357a6aa49f036bd771 100644 (file)
@@ -1,5 +1,6 @@
 /****************************************************************************
- * Copyright (c) 2008,2009 Free Software Foundation, Inc.                   *
+ * Copyright 2020-2021,2022 Thomas E. Dickey                                *
+ * Copyright 2008-2016,2017 Free Software Foundation, Inc.                  *
  *                                                                          *
  * Permission is hereby granted, free of charge, to any person obtaining a  *
  * copy of this software and associated documentation files (the            *
 
 #define USE_LIBTINFO
 #include <progs.priv.h>
+#include <tty_settings.h>
 
-MODULE_ID("$Id: tabs.c,v 1.16 2009/10/10 16:15:37 tom Exp $")
+MODULE_ID("$Id: tabs.c,v 1.51 2022/02/26 22:44:44 tom Exp $")
 
-static void usage(void) GCC_NORETURN;
+static GCC_NORETURN void usage(void);
 
+const char *_nc_progname;
 static int max_cols;
 
+static void
+failed(const char *s)
+{
+    perror(s);
+    ExitProgram(EXIT_FAILURE);
+}
+
 static int
 putch(int c)
 {
     return putchar(c);
 }
 
+static char *
+skip_csi(char *value)
+{
+    if (UChar(*value) == 0x9b)
+       ++value;
+    else if (!strncmp(value, "\033[", 2))
+       value += 2;
+    return value;
+}
+
+/*
+ * If the terminal uses ANSI clear_all_tabs, then it is not necessary to first
+ * move to the left margin before clearing tabs.
+ */
+static bool
+ansi_clear_tabs(void)
+{
+    bool result = FALSE;
+    if (VALID_STRING(clear_all_tabs)) {
+       char *param = skip_csi(clear_all_tabs);
+       if (!strcmp(param, "3g"))
+           result = TRUE;
+    }
+    return result;
+}
+
 static void
 do_tabs(int *tab_list)
 {
     int last = 1;
     int stop;
+    bool first = TRUE;
 
-    putchar('\r');
     while ((stop = *tab_list++) > 0) {
+       if (first) {
+           first = FALSE;
+           putchar('\r');
+       }
        if (last < stop) {
            while (last++ < stop) {
                if (last > max_cols)
@@ -65,17 +105,22 @@ do_tabs(int *tab_list)
            }
        }
        if (stop <= max_cols) {
-           tputs(tparm(set_tab, stop), 1, putch);
+           tputs(set_tab, 1, putch);
            last = stop;
        } else {
            break;
        }
     }
-    putchar('\n');
+    putchar('\r');
 }
 
+/*
+ * Decode a list of tab-stops from a string, returning an array of integers.
+ * If the margin is positive (because the terminal does not support margins),
+ * work around this by adding the margin to the decoded values.
+ */
 static int *
-decode_tabs(const char *tab_list)
+decode_tabs(const char *tab_list, int margin)
 {
     int *result = typeCalloc(int, strlen(tab_list) + (unsigned) max_cols);
     int n = 0;
@@ -83,27 +128,34 @@ decode_tabs(const char *tab_list)
     int prior = 0;
     int ch;
 
-    if (result != 0) {
-       while ((ch = *tab_list++) != '\0') {
-           if (isdigit(UChar(ch))) {
-               value *= 10;
-               value += (ch - '0');
-           } else if (ch == ',') {
-               result[n] = value + prior;
-               if (n > 0 && value <= result[n - 1]) {
-                   fprintf(stderr,
-                           "tab-stops are not in increasing order\n");
-                   free(result);
-                   result = 0;
-                   break;
-               }
-               ++n;
-               value = 0;
-               prior = 0;
-           } else if (ch == '+') {
-               if (n)
-                   prior = result[n - 1];
+    if (result == NULL)
+       failed("decode_tabs");
+
+    if (margin < 0)
+       margin = 0;
+
+    while ((ch = *tab_list++) != '\0') {
+       if (isdigit(UChar(ch))) {
+           value *= 10;
+           value += (ch - '0');
+           if (value > max_cols)
+               value = max_cols;
+       } else if (ch == ',') {
+           result[n] = value + prior + margin;
+           if (n > 0 && result[n] <= result[n - 1]) {
+               fprintf(stderr,
+                       "%s: tab-stops are not in increasing order: %d %d\n",
+                       _nc_progname, value, result[n - 1]);
+               free(result);
+               result = 0;
+               break;
            }
+           ++n;
+           value = 0;
+           prior = 0;
+       } else if (ch == '+') {
+           if (n)
+               prior = result[n - 1];
        }
     }
 
@@ -113,8 +165,9 @@ decode_tabs(const char *tab_list)
         */
        if ((n == 0) && (value > 0)) {
            int step = value;
+           value = 1;
            while (n < max_cols - 1) {
-               result[n++] = value;
+               result[n++] = value + margin;
                value += step;
            }
        }
@@ -122,34 +175,36 @@ decode_tabs(const char *tab_list)
        /*
         * Add the last value, if any.
         */
-       result[n++] = value;
+       result[n++] = value + prior + margin;
        result[n] = 0;
     }
+
     return result;
 }
 
 static void
-print_ruler(int *tab_list)
+print_ruler(int *tab_list, const char *new_line)
 {
     int last = 0;
-    int stop;
     int n;
 
     /* first print a readable ruler */
     for (n = 0; n < max_cols; n += 10) {
        int ch = 1 + (n / 10);
        char buffer[20];
-       sprintf(buffer, "----+----%c",
-               ((ch < 10)
-                ? (ch + '0')
-                : (ch + 'A' - 10)));
+       _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer))
+                   "----+----%c",
+                   ((ch < 10)
+                    ? (ch + '0')
+                    : (ch + 'A' - 10)));
        printf("%.*s", ((max_cols - n) > 10) ? 10 : (max_cols - n), buffer);
     }
-    putchar('\n');
+    printf("%s", new_line);
 
     /* now, print '*' for each stop */
     for (n = 0, last = 0; (tab_list[n] > 0) && (last < max_cols); ++n) {
-       stop = tab_list[n];
+       int stop = tab_list[n];
+
        while (++last < stop) {
            if (last <= max_cols) {
                putchar('-');
@@ -166,7 +221,7 @@ print_ruler(int *tab_list)
     }
     while (++last <= max_cols)
        putchar('-');
-    putchar('\n');
+    printf("%s", new_line);
 }
 
 /*
@@ -174,7 +229,7 @@ print_ruler(int *tab_list)
  * ruler.
  */
 static void
-write_tabs(int *tab_list)
+write_tabs(int *tab_list, const char *new_line)
 {
     int stop;
 
@@ -184,7 +239,7 @@ write_tabs(int *tab_list)
     /* also show a tab _past_ the stops */
     if (stop < max_cols)
        fputs("\t+", stdout);
-    putchar('\n');
+    fputs(new_line, stdout);
 }
 
 /*
@@ -195,11 +250,11 @@ static char *
 trimmed_tab_list(const char *source)
 {
     char *result = strdup(source);
-    int ch, j, k, last;
-
     if (result != 0) {
+       int j, k, last;
+
        for (j = k = last = 0; result[j] != 0; ++j) {
-           ch = UChar(result[j]);
+           int ch = UChar(result[j]);
            if (isspace(ch)) {
                if (last == '\0') {
                    continue;
@@ -226,7 +281,7 @@ comma_is_needed(const char *source)
     bool result = FALSE;
 
     if (source != 0) {
-       unsigned len = strlen(source);
+       size_t len = strlen(source);
        if (len != 0)
            result = (source[len - 1] != ',');
     } else {
@@ -250,7 +305,7 @@ add_to_tab_list(char **append, const char *value)
 
     if (copied != 0 && *copied != '\0') {
        const char *comma = ",";
-       unsigned need = 1 + strlen(copied);
+       size_t need = 1 + strlen(copied);
 
        if (*copied == ',')
            comma = "";
@@ -262,18 +317,74 @@ add_to_tab_list(char **append, const char *value)
            need += strlen(*append);
 
        result = malloc(need);
-       if (result != 0) {
-           *result = '\0';
-           if (*append != 0) {
-               strcpy(result, *append);
-               free(*append);
-           }
-           strcat(result, comma);
-           strcat(result, copied);
+       if (result == 0)
+           failed("add_to_tab_list");
+
+       *result = '\0';
+       if (*append != 0) {
+           _nc_STRCPY(result, *append, need);
+           free(*append);
        }
+       _nc_STRCAT(result, comma, need);
+       _nc_STRCAT(result, copied, need);
 
        *append = result;
     }
+    free(copied);
+    return result;
+}
+
+/*
+ * If the terminal supports it, (re)set the left margin and return true.
+ * Otherwise, return false.
+ */
+static bool
+do_set_margin(int margin, bool no_op)
+{
+    bool result = FALSE;
+
+    if (margin == 0) {         /* 0 is special case for resetting */
+       if (VALID_STRING(clear_margins)) {
+           result = TRUE;
+           if (!no_op)
+               tputs(clear_margins, 1, putch);
+       }
+    } else if (margin-- < 0) { /* margin will be 0-based from here on */
+       result = TRUE;
+    } else if (VALID_STRING(set_left_margin)) {
+       result = TRUE;
+       if (!no_op) {
+           /*
+            * assuming we're on the first column of the line, move the cursor
+            * to the column at which we will set a margin.
+            */
+           if (VALID_STRING(column_address)) {
+               tputs(TIPARM_1(column_address, margin), 1, putch);
+           } else if (margin >= 1) {
+               if (VALID_STRING(parm_right_cursor)) {
+                   tputs(TIPARM_1(parm_right_cursor, margin), 1, putch);
+               } else {
+                   while (margin-- > 0)
+                       putch(' ');
+               }
+           }
+           tputs(set_left_margin, 1, putch);
+       }
+    } else if (VALID_STRING(set_left_margin_parm)) {
+       result = TRUE;
+       if (!no_op) {
+           if (VALID_STRING(set_right_margin_parm)) {
+               tputs(TIPARM_1(set_left_margin_parm, margin), 1, putch);
+           } else {
+               tputs(TIPARM_2(set_left_margin_parm, margin, max_cols), 1, putch);
+           }
+       }
+    } else if (VALID_STRING(set_lr_margin)) {
+       result = TRUE;
+       if (!no_op) {
+           tputs(TIPARM_2(set_lr_margin, margin, max_cols), 1, putch);
+       }
+    }
     return result;
 }
 
@@ -281,66 +392,79 @@ add_to_tab_list(char **append, const char *value)
  * Check for illegal characters in the tab-list.
  */
 static bool
-legal_tab_list(const char *program, const char *tab_list)
+legal_tab_list(const char *tab_list)
 {
     bool result = TRUE;
 
     if (tab_list != 0 && *tab_list != '\0') {
        if (comma_is_needed(tab_list)) {
-           int n, ch;
+           int n;
+
            for (n = 0; tab_list[n] != '\0'; ++n) {
-               ch = UChar(tab_list[n]);
-               if (!(isdigit(ch) || ch == ',')) {
+               int ch = UChar(tab_list[n]);
+
+               if (!(isdigit(ch) || ch == ',' || ch == '+')) {
                    fprintf(stderr,
                            "%s: unexpected character found '%c'\n",
-                           program, ch);
+                           _nc_progname, ch);
                    result = FALSE;
                    break;
                }
            }
        } else {
-           fprintf(stderr, "%s: trailing comma found '%s'\n", program, tab_list);
+           fprintf(stderr, "%s: trailing comma found '%s'\n", _nc_progname, tab_list);
            result = FALSE;
        }
     } else {
-       fprintf(stderr, "%s: no tab-list given\n", program);
-       result = FALSE;
+       /* if no list given, default to "tabs -8" */
     }
     return result;
 }
 
+static char *
+skip_list(char *value)
+{
+    while (*value != '\0' &&
+          (isdigit(UChar(*value)) ||
+           isspace(UChar(*value)) ||
+           strchr("+,", UChar(*value)) != 0)) {
+       ++value;
+    }
+    return value;
+}
+
 static void
 usage(void)
 {
-    static const char *msg[] =
+#define DATA(s) s "\n"
+    static const char msg[] =
     {
-       "Usage: tabs [options] [tabstop-list]"
-       ,""
-       ,"Options:"
-       ,"  -0       reset tabs"
-       ,"  -8       set tabs to standard interval"
-       ,"  -a       Assembler, IBM S/370, first format"
-       ,"  -a2      Assembler, IBM S/370, second format"
-       ,"  -c       COBOL, normal format"
-       ,"  -c2      COBOL compact format"
-       ,"  -c3      COBOL compact format extended"
-       ,"  -d       debug (show ruler with expected/actual tab positions)"
-       ,"  -f       FORTRAN"
-       ,"  -n       no-op (do not modify terminal settings)"
-       ,"  -p       PL/I"
-       ,"  -s       SNOBOL"
-       ,"  -u       UNIVAC 1100 Assembler"
-       ,"  -T name  use terminal type 'name'"
-       ,""
-       ,"A tabstop-list is an ordered list of column numbers, e.g., 1,11,21"
-       ,"or 1,+10,+10 which is the same."
+       DATA("Usage: tabs [options] [tabstop-list]")
+       DATA("")
+       DATA("Options:")
+       DATA("  -0       reset tabs")
+       DATA("  -8       set tabs to standard interval")
+       DATA("  -a       Assembler, IBM S/370, first format")
+       DATA("  -a2      Assembler, IBM S/370, second format")
+       DATA("  -c       COBOL, normal format")
+       DATA("  -c2      COBOL compact format")
+       DATA("  -c3      COBOL compact format extended")
+       DATA("  -d       debug (show ruler with expected/actual tab positions)")
+       DATA("  -f       FORTRAN")
+       DATA("  -n       no-op (do not modify terminal settings)")
+       DATA("  -p       PL/I")
+       DATA("  -s       SNOBOL")
+       DATA("  -u       UNIVAC 1100 Assembler")
+       DATA("  -T name  use terminal type 'name'")
+       DATA("  -V       print version")
+       DATA("")
+       DATA("A tabstop-list is an ordered list of column numbers, e.g., 1,11,21")
+       DATA("or 1,+10,+10 which is the same.")
     };
-    unsigned n;
+#undef DATA
 
     fflush(stdout);
-    for (n = 0; n < SIZEOF(msg); ++n) {
-       fprintf(stderr, "%s\n", msg[n]);
-    }
+    fputs(msg, stderr);
     ExitProgram(EXIT_FAILURE);
 }
 
@@ -350,11 +474,17 @@ main(int argc, char *argv[])
     int rc = EXIT_FAILURE;
     bool debug = FALSE;
     bool no_op = FALSE;
+    bool change_tty = FALSE;
     int n, ch;
     NCURSES_CONST char *term_name = 0;
-    const char *mar_list = 0;  /* ignored */
     char *append = 0;
     const char *tab_list = 0;
+    const char *new_line = "\n";
+    int margin = -1;
+    TTY tty_settings;
+    int fd;
+
+    _nc_progname = _nc_rootname(argv[0]);
 
     if ((term_name = getenv("TERM")) == 0)
        term_name = "ansi+tabs";
@@ -367,23 +497,25 @@ main(int argc, char *argv[])
            while ((ch = *++option) != '\0') {
                switch (ch) {
                case 'a':
-                   switch (*option) {
+                   switch (*++option) {
+                   default:
                    case '\0':
                        tab_list = "1,10,16,36,72";
+                       option--;
                        /* Assembler, IBM S/370, first format */
                        break;
                    case '2':
                        tab_list = "1,10,16,40,72";
                        /* Assembler, IBM S/370, second format */
                        break;
-                   default:
-                       usage();
                    }
                    break;
                case 'c':
-                   switch (*option) {
+                   switch (*++option) {
+                   default:
                    case '\0':
                        tab_list = "1,8,12,16,20,55";
+                       option--;
                        /* COBOL, normal format */
                        break;
                    case '2':
@@ -394,8 +526,6 @@ main(int argc, char *argv[])
                        tab_list = "1,6,10,14,18,22,26,30,34,38,42,46,50,54,58,62,67";
                        /* COBOL compact format extended */
                        break;
-                   default:
-                       usage();
                    }
                    break;
                case 'd':       /* ncurses extension */
@@ -425,30 +555,57 @@ main(int argc, char *argv[])
                    if (*++option != '\0') {
                        term_name = option;
                    } else {
-                       term_name = argv[n++];
+                       term_name = argv[n];
+                       option--;
                    }
                    option += ((int) strlen(option)) - 1;
                    continue;
+               case 'V':
+                   puts(curses_version());
+                   ExitProgram(EXIT_SUCCESS);
                default:
                    if (isdigit(UChar(*option))) {
-                       tab_list = option;
-                       ++n;
+                       char *copy = strdup(option);
+                       *skip_list(copy) = '\0';
+                       tab_list = copy;
+                       option = skip_list(option) - 1;
                    } else {
                        usage();
                    }
-                   option += ((int) strlen(option)) - 1;
                    break;
                }
            }
            break;
        case '+':
-           while ((ch = *++option) != '\0') {
+           if ((ch = *++option) != '\0') {
+               int digits = 0;
+               int number = 0;
+
                switch (ch) {
                case 'm':
-                   mar_list = option;
+                   /*
+                    * The "+mXXX" option is unimplemented because only the long-obsolete
+                    * att510d implements smgl, which is needed to support
+                    * this option.
+                    */
+                   while ((ch = *++option) != '\0') {
+                       if (isdigit(UChar(ch))) {
+                           ++digits;
+                           number = number * 10 + (ch - '0');
+                       } else {
+                           usage();
+                       }
+                   }
+                   if (digits == 0)
+                       number = 10;
+                   margin = number;
                    break;
                default:
-                   usage();
+                   /* special case of relative stops separated by spaces? */
+                   if (option == argv[n] + 1) {
+                       tab_list = add_to_tab_list(&append, argv[n]);
+                   }
+                   break;
                }
            }
            break;
@@ -461,42 +618,82 @@ main(int argc, char *argv[])
                }
            }
            tab_list = add_to_tab_list(&append, option);
-           option += ((int) strlen(option)) - 1;
            break;
        }
     }
 
-    setupterm(term_name, STDOUT_FILENO, (int *) 0);
+    fd = save_tty_settings(&tty_settings, FALSE);
+
+    setupterm(term_name, fd, (int *) 0);
 
     max_cols = (columns > 0) ? columns : 80;
+    if (margin > 0)
+       max_cols -= margin;
 
     if (!VALID_STRING(clear_all_tabs)) {
        fprintf(stderr,
                "%s: terminal type '%s' cannot reset tabs\n",
-               argv[0], term_name);
+               _nc_progname, term_name);
     } else if (!VALID_STRING(set_tab)) {
        fprintf(stderr,
                "%s: terminal type '%s' cannot set tabs\n",
-               argv[0], term_name);
-    } else if (legal_tab_list(argv[0], tab_list)) {
-       int *list = decode_tabs(tab_list);
+               _nc_progname, term_name);
+    } else if (legal_tab_list(tab_list)) {
+       int *list;
+
+       if (tab_list == NULL)
+           tab_list = add_to_tab_list(&append, "8");
+
+       if (!no_op) {
+#if defined(TERMIOS) && defined(OCRNL)
+           /* set tty modes to -ocrnl to allow \r */
+           if (isatty(STDOUT_FILENO)) {
+               TTY new_settings = tty_settings;
+               new_settings.c_oflag &= (unsigned)~OCRNL;
+               update_tty_settings(&tty_settings, &new_settings);
+               change_tty = TRUE;
+               new_line = "\r\n";
+           }
+#endif
 
-       if (!no_op)
+           if (!ansi_clear_tabs())
+               putch('\r');
            tputs(clear_all_tabs, 1, putch);
+       }
+
+       if (margin >= 0) {
+           putch('\r');
+           if (margin > 0) {
+               /* reset existing margin before setting margin, to reduce
+                * problems moving left of the current margin.
+                */
+               if (do_set_margin(0, no_op))
+                   putch('\r');
+           }
+           if (do_set_margin(margin, no_op))
+               margin = -1;
+       }
+
+       list = decode_tabs(tab_list, margin);
 
        if (list != 0) {
            if (!no_op)
                do_tabs(list);
            if (debug) {
                fflush(stderr);
-               printf("tabs %s\n", tab_list);
-               print_ruler(list);
-               write_tabs(list);
+               printf("tabs %s%s", tab_list, new_line);
+               print_ruler(list, new_line);
+               write_tabs(list, new_line);
            }
            free(list);
        } else if (debug) {
            fflush(stderr);
-           printf("tabs %s\n", tab_list);
+           printf("tabs %s%s", tab_list, new_line);
+       }
+       if (!no_op) {
+           if (change_tty) {
+               restore_tty_settings();
+           }
        }
        rc = EXIT_SUCCESS;
     }