]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
Apply color ls changes from Drepper.
authorJim Meyering <jim@meyering.net>
Mon, 29 Apr 1996 04:24:07 +0000 (04:24 +0000)
committerJim Meyering <jim@meyering.net>
Mon, 29 Apr 1996 04:24:07 +0000 (04:24 +0000)
src/ls.c

index c31d2b0ba7275d8adfb3b942198152a91e3d93f6..86579ae95479bb50c82164428b6bacf08a753b2f 100644 (file)
--- a/src/ls.c
+++ b/src/ls.c
    device is a terminal.
    This is for the `ls' program. */
 
-/* Written by Richard Stallman and David MacKenzie. */
+/* Written by Richard Stallman and David MacKenzie.  */
+
+/* Colour support by Peter Anvin <Peter.Anvin@linux.org> and Dennis
+   Flaherty <dennisf@denix.elk.miles.com> based on original patches by
+   Greg Lee <lee@uhunix.uhcc.hawaii.edu>.  */
 
 #ifdef _AIX
  #pragma alloca
@@ -52,7 +56,7 @@
 /* limits.h must come before system.h because limits.h on some systems
    undefs PATH_MAX, whereas system.h includes pathmax.h which sets
    PATH_MAX.  */
-#include <limits.h>
+# include <limits.h>
 #endif
 
 #include "system.h"
 #define obstack_chunk_free free
 
 #ifndef INT_MAX
-#define INT_MAX 2147483647
+# define INT_MAX 2147483647
 #endif
 
 /* Return an int indicating the result of comparing two longs. */
 #if (INT_MAX <= 65535)
-#define longdiff(a, b) ((a) < (b) ? -1 : (a) > (b) ? 1 : 0)
+# define longdiff(a, b) ((a) < (b) ? -1 : (a) > (b) ? 1 : 0)
 #else
-#define longdiff(a, b) ((a) - (b))
+# define longdiff(a, b) ((a) - (b))
 #endif
 
 /* The maximum number of digits required to print an inode number
    in an unsigned format.  */
 #ifndef INODE_DIGITS
-#define INODE_DIGITS 7
+# define INODE_DIGITS 7
 #endif
 
 enum filetype
@@ -106,9 +110,41 @@ struct fileinfo
        zero. */
     unsigned int linkmode;
 
+    /* For symbolic link and color printing, 1 if linked-to file
+       exits, otherwise 0.  */
+    int linkok;
+
     enum filetype filetype;
   };
 
+/* Null is a valid character in a color indicator (think about Epson
+   printers, for example) so we have to use a length/buffer string
+   type.  */
+
+struct bin_str
+  {
+    unsigned int len;          /* Number of bytes */
+    char *string;              /* Pointer to the same */
+  };
+
+struct bin_str color_indicator[] =
+  {
+    { 2, "\033[" },            /* lc: Left of color sequence */
+    { 1, "m" },                        /* rc: Right of color sequence */
+    { 0, NULL },               /* ec: End color (replaces lc+no+rc) */
+    { 1, "0" },                        /* no: Normal */
+    { 1, "0" },                        /* fi: File: default */
+    { 2, "32" },               /* di: Directory: green */
+    { 2, "36" },               /* ln: Symlink: cyan */
+    { 2, "31" },               /* pi: Pipe: red */
+    { 2, "33" },               /* so: Socket: yellow/brown */
+    { 5, "44;37" },            /* bd: Block device: white on blue */
+    { 5, "44;37" },            /* cd: Char device: white on blue */
+    { 0, NULL },               /* mi: Missing file: undefined */
+    { 0, NULL },               /* or: Orphanned symlink: undefined */
+    { 2, "35" }                        /* ex: Executable: purple */
+  };
+
 #ifndef STDC_HEADERS
 char *ctime ();
 time_t time ();
@@ -155,6 +191,9 @@ static int file_interesting __P ((register struct dirent *next));
 static int gobble_file __P ((const char *name, int explicit_arg,
                             const char *dirname));
 static int is_not_dot_or_dotdot __P ((char *name));
+static void print_color_indicator __P ((char *name, unsigned int mode,
+                                       int linkok));
+static void put_indicator __P ((struct bin_str *ind));
 static int length_of_file_name_and_frills __P ((struct fileinfo *f));
 static void add_ignore_pattern __P ((char *pattern));
 static void attach __P ((char *dest, const char *dirname, const char *name));
@@ -168,11 +207,14 @@ static void print_file_name_and_frills __P ((struct fileinfo *f));
 static void print_horizontal __P ((void));
 static void print_long_format __P ((struct fileinfo *f));
 static void print_many_per_line __P ((void));
-static void print_name_with_quoting __P ((register char *p));
+static void print_name_with_quoting __P ((register char *p, unsigned int mode,
+                                         int linkok));
 static void print_type_indicator __P ((unsigned int mode));
 static void print_with_commas __P ((void));
 static void queue_directory __P ((char *name, char *realname));
 static void sort_files __P ((void));
+int get_funky_string __P ((char **dest, const char **src, int equals_end));
+static void parse_ls_color __P ((void));
 static void usage __P ((int status));
 \f
 /* The name the program was run with, stripped of any leading path. */
@@ -302,9 +344,9 @@ static int kilobyte_blocks;
    strange characters in file names.  */
 static int dired;
 
-/* none means don't mention the type of files.
-   all means mention the types of all files.
-   not_programs means do so except for executables.
+/* `none' means don't mention the type of files.
+   `all' means mention the types of all files.
+   `not_programs' means do so except for executables.
 
    Controlled by -F and -p.  */
 
@@ -317,6 +359,44 @@ enum indicator_style
 
 static enum indicator_style indicator_style;
 
+/* Nonzero means use colors to mark types.  Also define the different
+   colors as well as the stuff for the LS_COLORS environment variable.
+   The LS_COLORS variable is now in a termcap-like format.  */
+
+static int print_with_color;
+
+enum color_type
+  {
+    color_no,                  /* 0: default or --color=no */
+    color_yes,                 /* 1: --color=yes */
+    color_if_tty               /* 2: --color=tty */
+  };
+
+/* Note that color_no and color_yes equals boolean values; they will
+   be assigned to print_with_color which is a boolean variable.  */
+
+enum indicator_no
+  {
+    C_LEFT, C_RIGHT, C_END, C_NORM, C_FILE, C_DIR, C_LINK, C_FIFO, C_SOCK,
+    C_BLK, C_CHR, C_MISSING, C_ORPHAN, C_EXEC
+  };
+
+char *indicator_name[]=
+  {
+    "lc", "rc", "ec", "no", "fi", "di", "ln", "pi", "so",
+    "bd", "cd", "mi", "or", "ex", NULL
+  };
+
+struct col_ext_type
+  {
+    struct bin_str ext;                /* The extension we're looking for */
+    struct bin_str seq;                /* The sequence to output when we do */
+    struct col_ext_type *next; /* Next in list */
+  };
+
+struct col_ext_type *col_ext_list = NULL;
+char *color_buf;               /* Buffer for color sequences */
+
 /* Nonzero means mention the inode number of each file.  -i  */
 
 static int print_inode;
@@ -437,7 +517,9 @@ static struct option const long_options[] =
   {"time", required_argument, 0, 11},
   {"help", no_argument, &show_help, 1},
   {"version", no_argument, &show_version, 1},
-  {0, 0, 0, 0}
+  {"color", optional_argument, 0, 13},
+  {"colour", optional_argument, 0, 13},
+  {NULL, 0, NULL, 0}
 };
 
 static char const *const format_args[] =
@@ -518,6 +600,18 @@ static enum time_type const time_types[] =
   time_atime, time_atime, time_atime, time_ctime, time_ctime
 };
 
+static char const* color_args[] =
+  {
+    /* Note: "no" is a prefix of "none" so we don't include it.  */
+    /* force and none are for compatibility with another color-ls version */
+    "yes", "force", "none", "tty", "if-tty", 0
+  };
+
+static enum color_type const color_types[] =
+  {
+    color_yes, color_yes, color_no, color_if_tty, color_if_tty
+  };
+
 \f
 /* Write to standard output the string PREFIX followed by a space-separated
    list of the integers stored in OS all on one line.  */
@@ -569,10 +663,13 @@ main (int argc, char **argv)
   if (show_help)
     usage (0);
 
+  if (print_with_color)
+    parse_ls_color ();
+
   format_needs_stat = sort_type == sort_time || sort_type == sort_size
     || format == long_format
     || trace_links || trace_dirs || indicator_style != none
-    || print_block_size || print_inode;
+    || print_block_size || print_inode || print_with_color;
 
   if (dired && format == long_format)
     {
@@ -782,6 +879,7 @@ decode_switches (int argc, char **argv)
          if (format == long_format)
            format = (isatty (1) ? many_per_line : one_per_line);
          print_block_size = 0; /* disable -s */
+         print_with_color = 0; /* disable ---color */
          break;
 
        case 'g':
@@ -950,6 +1048,31 @@ decode_switches (int argc, char **argv)
          format = formats[i];
          break;
 
+       case 13:                /* --color */
+         if (optarg)
+           {
+             i = argmatch (optarg, color_args);
+             if (i < 0)
+               {
+                 invalid_arg (_("colorization criterion"), optarg, i);
+                 usage (1);
+               }
+             i = color_types[i];
+           }
+         else
+           i = color_yes;      /* Only --color -> --color=yes */
+
+         if (i == color_if_tty)
+           print_with_color = isatty (1);
+         else
+           print_with_color = i;
+
+         /* XXX Shouldn't this be an autoconf check?  */
+         if (print_with_color)
+           tabsize = line_length;      /* Some systems don't like tabs and
+                                          color codes in combination */
+         break;
+
        default:
          usage (1);
        }
@@ -957,7 +1080,331 @@ decode_switches (int argc, char **argv)
 
   return optind;
 }
+\f
 
+static void
+parse_ls_color (void)
+{
+  const char *p;               /* Pointer to character being parsed */
+  char *whichvar;              /* LS_COLORS or LS_COLOURS? */
+  char *buf;                   /* color_buf buffer pointer */
+  int state;                   /* State of parser */
+  int ind_no;                  /* Indicator number */
+  char label[3] = "??";                /* Indicator label */
+  struct col_ext_type *ext;    /* Extension we are working on */
+  struct col_ext_type *ext2;   /* Extra pointer */
+
+  if (((p = getenv (whichvar = "LS_COLORS")) != NULL && *p != '\0')
+      || ((p = getenv (whichvar = "LS_COLOURS")) != NULL && *p != '\0'))
+    {
+      buf = color_buf = xstrdup (p);
+      /* This is an overly conservative estimate, but any possible
+        LS_COLORS string will *not* generate a color_buf longer than
+        itself, so it is a safe way of allocating a buffer in
+        advance.  */
+
+      state = 1;
+      while (state > 0)
+       {
+         switch (state)
+           {
+           case 1:             /* First label character */
+             switch (*p)
+               {
+               case ':':
+                 ++p;
+                 break;
+
+               case '*':
+                 /* Allocate new extension block and add to head of
+                    linked list (this way a later definition will
+                    override an earlier one, which can be useful for
+                    having terminal-specific defs override global).  */
+
+                 ext = (struct col_ext_type *)
+                   xmalloc (sizeof (struct col_ext_type));
+                 ext->next = col_ext_list;
+                 col_ext_list = ext;
+
+                 ++p;
+                 ext->ext.string = buf;
+
+                 state = (ext->ext.len =
+                          get_funky_string (&buf, &p, 1)) < 0 ? -1 : 4;
+                 break;
+
+               case '\0':
+                 state = 0;    /* Done! */
+                 break;
+
+               default:        /* Assume it is file type label */
+                 label[0] = *(p++);
+                 state = 2;
+                 break;
+               }
+             break;
+
+           case 2:             /* Second label character */
+             if (*p)
+               {
+                 label[1] = *(p++);
+                 state = 3;
+               }
+             else
+               state = -1;     /* Error */
+             break;
+
+           case 3:             /* Equal sign after indicator label */
+             state = -1;       /* Assume failure... */
+             if (*(p++) == '=')/* It *should* be... */
+               {
+                 for (ind_no = 0; indicator_name[ind_no] != NULL; ++ind_no)
+                   {
+                     if (strcmp (label, indicator_name[ind_no]) == 0)
+                       {
+                         color_indicator[ind_no].string = buf;
+                         state = ((color_indicator[ind_no].len =
+                                   get_funky_string (&buf, &p, 0)) < 0 ?
+                                  -1 : 1);
+                         break;
+                       }
+                   }
+                 if (state == -1)
+                   fprintf (stderr, _("Unknown prefix: %s\n"), label);
+               }
+             break;
+
+           case 4:             /* Equal sign after *.ext */
+             if (*(p++) == '=')
+               {
+                 ext->seq.string = buf;
+                 state = (ext->seq.len =
+                          get_funky_string (&buf, &p, 0)) < 0 ? -1 : 1;
+               }
+             else
+               state = -1;
+             break;
+           }
+       }
+
+      if (state < 0)
+       {
+         fprintf (stderr, _("Bad %s variable\n"), whichvar);
+         free (color_buf);
+         for (ext = col_ext_list; ext != NULL ; )
+           {
+             ext2 = ext;
+             ext = ext->next;
+             free (ext2);
+           }
+         print_with_color = 0;
+       }
+    }
+}
+
+/* Parse a string as part of the LS_COLO(U)RS variable; this may involve
+   decoding all kinds of escape characters.  If equals_end is set an
+   unescaped equal sign ends the string, otherwise only a : or \0
+   does.  Returns the number of characters output, or -1 on failure.
+
+   The resulting string is *not* null-terminated, but may contain
+   embedded nulls.
+
+   Note that both dest and src are char **; on return they point to
+   the first free byte after the array and the character that ended
+   the input string, respectively.  */
+
+int
+get_funky_string (char **dest, const char **src, int equals_end)
+{
+  int num;                     /* For numerical codes */
+  int count;                   /* Something to count with */
+  enum {
+    st_gnd, st_backslash, st_octal, st_hex, st_caret, st_end, st_error
+  } state;
+  const char *p;
+  char *q;
+
+  p = *src;                    /* We don't want to double-indirect */
+  q = *dest;                   /* the whole darn time.  */
+
+  count = 0;                   /* No characters counted in yet.  */
+  num = 0;
+
+  state = st_gnd;              /* Start in ground state.  */
+  while (state < st_end)
+    {
+      switch (state)
+       {
+       case st_gnd:            /* Ground state (no escapes) */
+         switch (*p)
+           {
+           case ':':
+           case '\0':
+             state = st_end;   /* End of string */
+             break;
+           case '\\':
+             state = st_backslash; /* Backslash scape sequence */
+             ++p;
+             break;
+           case '^':
+             state = st_caret; /* Caret escape */
+             ++p;
+             break;
+           case '=':
+             if (equals_end)
+               {
+                 state = st_end; /* End */
+                 break;
+               }
+             /* else fall through */
+           default:
+             *(q++) = *(p++);
+             ++count;
+             break;
+           }
+         break;
+
+       case st_backslash:      /* Backslash escaped character */
+         switch (*p)
+           {
+           case '0':
+           case '1':
+           case '2':
+           case '3':
+           case '4':
+           case '5':
+           case '6':
+           case '7':
+             state = st_octal; /* Octal sequence */
+             num = *p - '0';
+             break;
+           case 'x':
+           case 'X':
+             state = st_hex;   /* Hex sequence */
+             num = 0;
+             break;
+           case 'a':           /* Bell */
+             num = 7;          /* Not all C compilers know what \a means */
+             break;
+           case 'b':           /* Backspace */
+             num = '\b';
+             break;
+           case 'e':           /* Escape */
+             num = 27;
+             break;
+           case 'f':           /* Form feed */
+             num = '\f';
+             break;
+           case 'n':           /* Newline */
+             num = '\n';
+             break;
+           case 'r':           /* Carriage return */
+             num = '\r';
+             break;
+           case 't':           /* Tab */
+             num = '\t';
+             break;
+           case 'v':           /* Vtab */
+             num = '\v';
+             break;
+           case '?':           /* Delete */
+              num = 127;
+             break;
+           case '_':           /* Space */
+             num = ' ';
+             break;
+           case '\0':          /* End of string */
+             state = st_error; /* Error! */
+             break;
+           default:            /* Escaped character like \ ^ : = */
+             num = *p;
+             break;
+           }
+         if (state == st_backslash)
+           {
+             *(q++) = num;
+             ++count;
+             state = st_gnd;
+           }
+         ++p;
+         break;
+
+       case st_octal:          /* Octal sequence */
+         if (*p < '0' || *p > '7')
+           {
+             *(q++) = num;
+             ++count;
+             state = st_gnd;
+           }
+         else
+           num = (num << 3) + (*(p++) - '0');
+         break;
+
+       case st_hex:            /* Hex sequence */
+         switch (*p)
+           {
+           case '0':
+           case '1':
+           case '2':
+           case '3':
+           case '4':
+           case '5':
+           case '6':
+           case '7':
+           case '8':
+           case '9':
+             num = (num << 4) + (*(p++) - '0');
+             break;
+           case 'a':
+           case 'b':
+           case 'c':
+           case 'd':
+           case 'e':
+           case 'f':
+             num = (num << 4) + (*(p++) - 'a') + 10;
+             break;
+           case 'A':
+           case 'B':
+           case 'C':
+           case 'D':
+           case 'E':
+           case 'F':
+             num = (num << 4) + (*(p++) - 'A') + 10;
+             break;
+           default:
+             *(q++) = num;
+             ++count;
+             state = st_gnd;
+             break;
+           }
+         break;
+
+       case st_caret:          /* Caret escape */
+         state = st_gnd;       /* Should be the next state... */
+         if (*p >= '@' && *p <= '~')
+           {
+             *(q++) = *(p++) & 037;
+             ++count;
+           }
+         else if ( *p == '?' )
+           {
+             *(q++) = 127;
+             ++count;
+           }
+         else
+           state = st_error;
+         break;
+       }
+    }
+
+  *dest = q;
+  *src = p;
+
+  return state == st_error ? -1 : count;
+}
+
+\f
 /* Request that the directory named `name' have its contents listed later.
    If `realname' is nonzero, it will be used instead of `name' when the
    directory name is printed.  This allows symbolic links to directories
@@ -1644,7 +2091,7 @@ print_long_format (struct fileinfo *f)
   DIRED_INDENT ();
   FPUTS (bigbuf, stdout, p - bigbuf);
   PUSH_CURRENT_DIRED_POS (&dired_obstack);
-  print_name_with_quoting (f->name);
+  print_name_with_quoting (f->name, f->stat.st_mode, f->linkok);
   PUSH_CURRENT_DIRED_POS (&dired_obstack);
 
   if (f->filetype == symbolic_link)
@@ -1652,7 +2099,7 @@ print_long_format (struct fileinfo *f)
       if (f->linkname)
        {
          FPUTS_LITERAL (" -> ", stdout);
-         print_name_with_quoting (f->linkname);
+         print_name_with_quoting (f->linkname, f->linkmode, f->linkok-1);
          if (indicator_style != none)
            print_type_indicator (f->linkmode);
        }
@@ -1801,15 +2248,28 @@ quote_filename (register const char *p, size_t *quoted_length)
 }
 
 static void
-print_name_with_quoting (register char *p)
+print_name_with_quoting (register char *p, unsigned int mode, int linkok)
 {
   char *quoted;
   size_t quoted_length;
 
+  if (print_with_color)
+    print_color_indicator (p, mode, linkok);
+
   quoted = quote_filename (p, &quoted_length);
   FPUTS (quoted != NULL ? quoted : p, stdout, quoted_length);
   if (quoted)
     free (quoted);
+
+  if (print_with_color)
+    if (color_indicator[C_END].string != NULL)
+      put_indicator (&color_indicator[C_END]);
+    else
+      {
+       put_indicator (&color_indicator[C_LEFT]);
+       put_indicator (&color_indicator[C_NORM]);
+       put_indicator (&color_indicator[C_RIGHT]);
+      }
 }
 \f
 /* Print the file name of `f' with appropriate quoting.
@@ -1827,7 +2287,7 @@ print_file_name_and_frills (struct fileinfo *f)
            (unsigned) convert_blocks (ST_NBLOCKS (f->stat),
                                       kilobyte_blocks));
 
-  print_name_with_quoting (f->name);
+  print_name_with_quoting (f->name, f->stat.st_mode, f->linkok);
 
   if (indicator_style != none)
     print_type_indicator (f->stat.st_mode);
@@ -1859,6 +2319,85 @@ print_type_indicator (unsigned int mode)
     PUTCHAR ('*');
 }
 
+static void
+print_color_indicator (char *name, unsigned int mode, int linkok)
+{
+  int type = C_FILE;
+  struct col_ext_type *ext;    /* Color extension */
+  int len;                     /* Length of name */
+
+  /* Is this a nonexistent file?  If so, linkok == -1.  */
+
+  if (linkok == -1 && color_indicator[C_MISSING].string != NULL)
+    {
+      ext = NULL;
+      type = C_MISSING;
+    }
+  else
+    {
+      /* Test if is is a recognized extension.  */
+
+      len = strlen (name);
+      name += len;             /* Pointer to final \0.  */
+      for (ext = col_ext_list; ext != NULL; ext = ext->next)
+       if (ext->ext.len <= len
+           && strncmp (name-ext->ext.len, ext->ext.string, ext->ext.len) == 0)
+         break;
+
+      if (ext == NULL)
+       {
+         if (S_ISDIR (mode))
+           type = C_DIR;
+
+#ifdef S_ISLNK
+         else if (S_ISLNK (mode))
+           type = (!linkok && color_indicator[C_ORPHAN].string) ?
+             C_ORPHAN : C_LINK;
+#endif
+
+#ifdef S_ISFIFO
+         else if (S_ISFIFO (mode))
+           type = C_FIFO;
+#endif
+
+#ifdef S_ISSOCK
+         else if (S_ISSOCK (mode))
+           type = C_SOCK;
+#endif
+
+#ifdef S_ISBLK
+         else if (S_ISBLK (mode))
+           type = C_BLK;
+#endif
+
+#ifdef S_ISCHR
+         else if (S_ISCHR (mode))
+           type = C_CHR;
+#endif
+
+         if (type == C_FILE && (mode & (S_IEXEC|S_IEXEC>>3|S_IEXEC>>6)) != 0)
+           type = C_EXEC;
+       }
+    }
+
+  put_indicator (&color_indicator[C_LEFT]);
+  put_indicator (ext ? &(ext->seq) : &color_indicator[type]);
+  put_indicator (&color_indicator[C_RIGHT]);
+}
+
+/* Output a color indicator (which may contain nulls).  */
+static void
+put_indicator (struct bin_str *ind)
+{
+  register int i;
+  register char *p;
+
+  p = ind->string;
+
+  for (i = ind->len; i > 0; --i)
+    putchar (*(p++));
+}
+
 static int
 length_of_file_name_and_frills (struct fileinfo *f)
 {
@@ -2134,6 +2673,8 @@ Sort entries alphabetically if none of -cftuSUX nor --sort.\n\
   -b, --escape               print octal escapes for nongraphic characters\n\
   -C                         list entries by columns\n\
   -c                         sort by change time; with -l: show ctime\n\
+      --color[=WORD]         colorize entries according to WORD\n\
+      --colour[=WORD]        yes, no, or tty (if output is terminal)\n\
   -D, --dired                generate output well suited to Emacs' dired mode\n\
   -d, --directory            list directory entries instead of contents\n\
   -F, --classify             append a character for typing each entry\n\