Cleaned up the line display. Removed the clrtoeol, which seems to be useless (?)
[selector.git] / selector.c
index 0bdcd1e..ca9d392 100644 (file)
@@ -3,7 +3,7 @@
  *  selector is a simple command line utility for selection of strings
  *  with a dynamic pattern-matching.
  *
- *  Copyright (c) 2009 Francois Fleuret
+ *  Copyright (c) 2009, 2010, 2011 Francois Fleuret
  *  Written by Francois Fleuret <francois@fleuret.org>
  *
  *  This file is part of selector.
@@ -25,7 +25,7 @@
 /*
 
   To use it as a super-history-search for bash:
-  selector -q -b -i -d -v -w -l ${HISTSIZE} <(history)
+  selector --bash <(history)
 
 */
 
 #include <stdlib.h>
 #include <unistd.h>
 #include <string.h>
+#include <errno.h>
 #include <ncurses.h>
 #include <fcntl.h>
 #include <sys/ioctl.h>
 #include <termios.h>
 #include <regex.h>
 #include <locale.h>
+#include <getopt.h>
+#include <limits.h>
 
-#define VERSION "1.0"
+#define VERSION "1.1.3"
 
 #define BUFFER_SIZE 4096
 
@@ -62,12 +65,26 @@ int use_regexp = 0;
 int case_sensitive = 0;
 char *title = 0;
 int error_flash = 0;
+int exclamation_negates = 0;
 
 int attr_modeline, attr_focus_line, attr_error;
 
+/********************************************************************/
+
+/* malloc with error checking.  */
+
+void *safe_malloc(size_t n) {
+  void *p = malloc(n);
+  if (!p && n != 0) {
+    printf("Can not allocate memory: %s\n", strerror(errno));
+    exit(EXIT_FAILURE);
+  }
+  return p;
+}
+
 /*********************************************************************/
 
-void inject_into_tty_buffer(char *string) {
+void inject_into_tty_buffer(char *string, int add_control_qs) {
   struct termios oldtio, newtio;
   const char *k;
   const char control_q = '\021';
@@ -89,33 +106,52 @@ void inject_into_tty_buffer(char *string) {
 
 /*********************************************************************/
 
-void check_opt(int argc, char **argv, int n_opt, int n, const char *help) {
-  if(n_opt + n >= argc) {
-    fprintf(stderr, "Selector: Missing argument for %s, expecting %s.\n",
-            argv[n_opt], help);
-    exit(1);
-  }
-}
-
-int string_to_positive_integer(char *string) {
-  int error = 0;
-  int result = 0;
+void str_to_positive_integers(char *string, int *values, int nb) {
+  int current_value, gotone;
   char *s;
+  int n;
 
-  if(*string) {
-    for(s = string; *s; s++) {
-      if(*s >= '0' && *s <= '9') {
-        result = result * 10 + (int) (*s - '0');
-      } else error = 1;
+  n = 0;
+  current_value = 0;
+  gotone = 0;
+  s = string;
+
+  while(1) {
+    if(*s >= '0' && *s <= '9') {
+      current_value = current_value * 10 + (int) (*s - '0');
+      gotone = 1;
+    } else if(*s == ',' || *s == '\0') {
+      if(gotone) {
+        if(n < nb) {
+          values[n++] = current_value;
+          if(*s == '\0') {
+            if(n == nb) {
+              return;
+            } else {
+              fprintf(stderr,
+                      "selector: Missing value in `%s'.\n", string);
+              exit(EXIT_FAILURE);
+            }
+          }
+          current_value = 0;
+          gotone = 0;
+        } else {
+          fprintf(stderr,
+                  "selector: Too many values in `%s'.\n", string);
+          exit(EXIT_FAILURE);
+        }
+      } else {
+        fprintf(stderr,
+                "selector: Empty value in `%s'.\n", string);
+        exit(EXIT_FAILURE);
+      }
+    } else {
+      fprintf(stderr,
+              "selector: Syntax error in `%s'.\n", string);
+      exit(EXIT_FAILURE);
     }
-  } else error = 1;
-
-  if(error) {
-    fprintf(stderr, "Selector: Value `%s' is not a positive integer.\n", string);
-    exit(1);
+    s++;
   }
-
-  return result;
 }
 
 void error_feedback() {
@@ -126,25 +162,79 @@ void error_feedback() {
   }
 }
 
+void usage(FILE *out) {
+
+  fprintf(out, "Selector version %s (%s)\n", VERSION, UNAME);
+  fprintf(out, "Written by Francois Fleuret <francois@fleuret.org>.\n");
+  fprintf(out, "\n");
+  fprintf(out, "Usage: selector [options] [<filename1> [<filename2> ...]]\n");
+  fprintf(out, "\n");
+  fprintf(out, " -h, --help\n");
+  fprintf(out, "         show this help\n");
+  fprintf(out, " -v, --inject-in-tty\n");
+  fprintf(out, "         inject the selected line in the tty\n");
+  fprintf(out, " -w, --add-control-qs\n");
+  fprintf(out, "         quote control characters with ^Qs when using -v\n");
+  fprintf(out, " -d, --remove-duplicates\n");
+  fprintf(out, "         remove duplicated lines\n");
+  fprintf(out, " -b, --remove-bash-prefix\n");
+  fprintf(out, "         remove the bash history line prefix\n");
+  fprintf(out, " -z, --remove-zsh-prefix\n");
+  fprintf(out, "         remove the zsh history line prefix\n");
+  fprintf(out, " -i, --revert-order\n");
+  fprintf(out, "         invert the order of lines\n");
+  fprintf(out, " -e, --regexp\n");
+  fprintf(out, "         start in regexp mode\n");
+  fprintf(out, " -a, --case-sensitive\n");
+  fprintf(out, "         start in case sensitive mode\n");
+  fprintf(out, " -n, --exclamation-negates\n");
+  fprintf(out, "         exclamation points in substring requires the string to be absent\n");
+  fprintf(out, " -m, --monochrome\n");
+  fprintf(out, "         monochrome mode\n");
+  fprintf(out, " -q, --no-beep\n");
+  fprintf(out, "         make a flash instead of a beep on an edition error\n");
+  fprintf(out, " --bash\n");
+  fprintf(out, "         setting for bash history search, same as -b -i -d -v -w -l ${HISTSIZE}\n");
+  fprintf(out, " --      all following arguments are filenames\n");
+  fprintf(out, " -t <title>, --title <title>\n");
+  fprintf(out, "         add a title in the modeline\n");
+  fprintf(out, " -c <colors>, --colors <colors>\n");
+  fprintf(out, "         set the display colors with an argument of the form\n");
+  fprintf(out, "         <fg_modeline>,<bg_modeline>,<fg_highlight>,<bg_highlight>\n");
+  fprintf(out, " -o <output filename>, --output-file <output filename>\n");
+  fprintf(out, "         set a file to write the selected line to\n");
+  fprintf(out, " -s <pattern separator>, --pattern-separator <pattern separator>\n");
+  fprintf(out, "         set the symbol to separate substrings in the pattern\n");
+  fprintf(out, " -x <label separator>, --label-separator <label separator>\n");
+  fprintf(out, "         set the symbol to terminate the label\n");
+  fprintf(out, " -l <max number of lines>, --number-of-lines <max number of lines>\n");
+  fprintf(out, "         set the maximum number of lines to take into account\n");
+  fprintf(out, "\n");
+}
+
+/*********************************************************************/
+
 /* A quick and dirty hash table */
 
-/* The table itself stores indexes of the strings taken in a char
-   **table. When a string is added, if it was already in the table,
-   **the new index replaces the previous one.  */
+#define MAGIC_HASH_MULTIPLIER 387433
 
-typedef struct {
+/* The table itself stores indexes of the strings taken in a char**
+   table. When a string is added, if it was already in the table, the
+   new index replaces the previous one.  */
+
+struct hash_table_t {
   int size;
   int *entries;
-} hash_table_t;
+};
 
-hash_table_t *new_hash_table(int size) {
+struct hash_table_t *new_hash_table(int size) {
   int k;
-  hash_table_t *hash_table;
+  struct hash_table_t *hash_table;
 
-  hash_table = (hash_table_t *) malloc(sizeof(hash_table_t));
+  hash_table = safe_malloc(sizeof(struct hash_table_t));
 
   hash_table->size = size;
-  hash_table->entries = (int *) malloc(hash_table->size * sizeof(int));
+  hash_table->entries = safe_malloc(hash_table->size * sizeof(int));
 
   for(k = 0; k < hash_table->size; k++) {
     hash_table->entries[k] = -1;
@@ -153,7 +243,7 @@ hash_table_t *new_hash_table(int size) {
   return hash_table;
 }
 
-void free_hash_table(hash_table_t *hash_table) {
+void free_hash_table(struct hash_table_t *hash_table) {
   free(hash_table->entries);
   free(hash_table);
 }
@@ -162,21 +252,23 @@ void free_hash_table(hash_table_t *hash_table) {
    string was not already in the table, returns -1. Otherwise, returns
    the previous index it had. */
 
-int add_and_get_previous_index(hash_table_t *hash_table,
-                               const char *new_string, int new_index, char **strings) {
+int add_and_get_previous_index(struct hash_table_t *hash_table,
+                               const char *new_string, int new_index,
+                               char **strings) {
 
-  unsigned int code = 0;
+  unsigned int code = 0, start;
   int k;
 
   /* This is my recipe. I checked, it seems to work (as long as
-     hash_table->size is not a multiple of 387433 that should be
-     okay) */
+     hash_table->size is not a multiple of MAGIC_HASH_MULTIPLIER that
+     should be okay) */
 
   for(k = 0; new_string[k]; k++) {
-    code = code * 387433 + (unsigned int) (new_string[k]);
+    code = code * MAGIC_HASH_MULTIPLIER + (unsigned int) (new_string[k]);
   }
 
   code = code % hash_table->size;
+  start = code;
 
   while(hash_table->entries[code] >= 0) {
     /* There is a string with that code */
@@ -191,6 +283,12 @@ int add_and_get_previous_index(hash_table_t *hash_table,
     /* This collision was not the same string, let's move to the next
        in the table */
     code = (code + 1) % hash_table->size;
+    /* We came back to our original code, which means that the table
+       is full */
+    if(code == start) {
+      printf("Full hash table (that should not happen)\n");
+      exit(EXIT_FAILURE);
+   }
   }
 
   /* This string was not already in there, store the index in the
@@ -212,16 +310,36 @@ typedef struct {
   char *splitted_patterns, **patterns;
 } matcher_t;
 
-int match(char *string, matcher_t *matcher) {
+int match(matcher_t *matcher, char *string) {
   int n;
   if(matcher->nb_patterns >= 0) {
     if(matcher->case_sensitive) {
-      for(n = 0; n < matcher->nb_patterns; n++) {
-        if(strstr(string, matcher->patterns[n]) == 0) return 0;
+      if(exclamation_negates) {
+        for(n = 0; n < matcher->nb_patterns; n++) {
+          if(matcher->patterns[n][0] == '!') {
+            if(strstr(string, matcher->patterns[n] + 1) != 0) return 0;
+          } else {
+            if(strstr(string, matcher->patterns[n]) == 0) return 0;
+          }
+        }
+      } else {
+        for(n = 0; n < matcher->nb_patterns; n++) {
+          if(strstr(string, matcher->patterns[n]) == 0) return 0;
+        }
       }
     } else {
-      for(n = 0; n < matcher->nb_patterns; n++) {
-        if(strcasestr(string, matcher->patterns[n]) == 0) return 0;
+      if(exclamation_negates) {
+        for(n = 0; n < matcher->nb_patterns; n++) {
+          if(matcher->patterns[n][0] == '!') {
+            if(strcasestr(string, matcher->patterns[n] + 1) != 0) return 0;
+          } else {
+            if(strcasestr(string, matcher->patterns[n]) == 0) return 0;
+          }
+        }
+      } else {
+        for(n = 0; n < matcher->nb_patterns; n++) {
+          if(strcasestr(string, matcher->patterns[n]) == 0) return 0;
+        }
       }
     }
     return 1;
@@ -239,15 +357,17 @@ void free_matcher(matcher_t *matcher) {
   }
 }
 
-void initialize_matcher(int use_regexp, int case_sensitive,
-                        matcher_t *matcher, const char *pattern) {
+void initialize_matcher(matcher_t *matcher,
+                        int use_regexp, int case_sensitive,
+                        const char *pattern) {
   const char *s;
   char *t, *last_pattern_start;
   int n;
 
   if(use_regexp) {
     matcher->nb_patterns = -1;
-    matcher->regexp_error = regcomp(&matcher->preg, pattern, case_sensitive ? 0 : REG_ICASE);
+    matcher->regexp_error = regcomp(&matcher->preg, pattern,
+                                    case_sensitive ? 0 : REG_ICASE);
   } else {
     matcher->regexp_error = 0;
     matcher->nb_patterns = 1;
@@ -259,8 +379,11 @@ void initialize_matcher(int use_regexp, int case_sensitive,
       }
     }
 
-    matcher->splitted_patterns = (char *) malloc((strlen(pattern) + 1) * sizeof(char));
-    matcher->patterns = (char **) malloc(matcher->nb_patterns * sizeof(char *));
+    matcher->splitted_patterns =
+      safe_malloc((strlen(pattern) + 1) * sizeof(char));
+
+    matcher->patterns =
+      safe_malloc(matcher->nb_patterns * sizeof(char *));
 
     strcpy(matcher->splitted_patterns, pattern);
 
@@ -337,15 +460,16 @@ void kill_after_cursor(char *buffer, int *position) {
 
 /*********************************************************************/
 
-int previous_visible(int current_line, int nb_lines, char **lines, matcher_t *matcher) {
+int previous_visible(int current_line, char **lines, matcher_t *matcher) {
   int line = current_line - 1;
-  while(line >= 0 && !match(lines[line], matcher)) line--;
+  while(line >= 0 && !match(matcher, lines[line])) line--;
   return line;
 }
 
-int next_visible(int current_line, int nb_lines, char **lines, matcher_t *matcher) {
+int next_visible(int current_line, int nb_lines, char **lines,
+                 matcher_t *matcher) {
   int line = current_line + 1;
-  while(line < nb_lines && !match(lines[line], matcher)) line++;
+  while(line < nb_lines && !match(matcher, lines[line])) line++;
 
   if(line < nb_lines)
     return line;
@@ -380,7 +504,7 @@ void update_screen(int *current_focus_line, int *displayed_focus_line,
   int nb_printed_lines = 0;
   int cursor_x;
 
-  initialize_matcher(use_regexp, case_sensitive, &matcher, pattern);
+  initialize_matcher(&matcher, use_regexp, case_sensitive, pattern);
 
   console_width = getmaxx(stdscr);
   console_height = getmaxy(stdscr);
@@ -403,12 +527,13 @@ void update_screen(int *current_focus_line, int *displayed_focus_line,
 
   else if(nb_lines > 0) {
     int new_focus_line;
-    if(match(lines[*current_focus_line], &matcher)) {
+    if(match(&matcher, lines[*current_focus_line])) {
       new_focus_line = *current_focus_line;
     } else {
-      new_focus_line = next_visible(*current_focus_line, nb_lines, lines, &matcher);
+      new_focus_line = next_visible(*current_focus_line, nb_lines, lines,
+                                    &matcher);
       if(new_focus_line < 0) {
-        new_focus_line = previous_visible(*current_focus_line, nb_lines, lines, &matcher);
+        new_focus_line = previous_visible(*current_focus_line, lines, &matcher);
       }
     }
 
@@ -427,7 +552,7 @@ void update_screen(int *current_focus_line, int *displayed_focus_line,
       } else {
         /* We want to go up, let's find the first visible line above */
         for(m = 0; l >= 0 && m < -motion; m++) {
-          l = previous_visible(l, nb_lines, lines, &matcher);
+          l = previous_visible(l, lines, &matcher);
           if(l >= 0) {
             new_focus_line = l;
           }
@@ -435,35 +560,38 @@ void update_screen(int *current_focus_line, int *displayed_focus_line,
       }
     }
 
-    /* Here new_focus_line is either a line number matching the pattern, or -1 */
+    /* Here new_focus_line is either a line number matching the
+       pattern, or -1 */
 
     if(new_focus_line >= 0) {
 
-      int first_line = new_focus_line, last_line = new_focus_line, nb_match = 1;
+      int first_line = new_focus_line, last_line = new_focus_line;
+      int nb_match = 1;
 
-      /* We find the first and last line to show, so that the total of
-         visible lines between them (them included) is
+      /* We find the first and last lines to show, so that the total
+         of visible lines between them (them included) is
          console_height-1 */
 
-      while(nb_match < console_height-1 && (first_line > 0 || last_line < nb_lines - 1)) {
+      while(nb_match < console_height-1 &&
+            (first_line > 0 || last_line < nb_lines - 1)) {
 
         if(first_line > 0) {
           first_line--;
-          while(first_line > 0 && !match(lines[first_line], &matcher)) {
+          while(first_line > 0 && !match(&matcher, lines[first_line])) {
             first_line--;
           }
-          if(match(lines[first_line], &matcher)) {
+          if(match(&matcher, lines[first_line])) {
             nb_match++;
           }
         }
 
         if(nb_match < console_height - 1 && last_line < nb_lines - 1) {
           last_line++;
-          while(last_line < nb_lines - 1 && !match(lines[last_line], &matcher)) {
+          while(last_line < nb_lines - 1 && !match(&matcher, lines[last_line])) {
             last_line++;
           }
 
-          if(match(lines[last_line], &matcher)) {
+          if(match(&matcher, lines[last_line])) {
             nb_match++;
           }
         }
@@ -472,7 +600,7 @@ void update_screen(int *current_focus_line, int *displayed_focus_line,
       /* Now we display them */
 
       for(l = first_line; l <= last_line; l++) {
-        if(match(lines[l], &matcher)) {
+        if(match(&matcher, lines[l])) {
           int k = 0;
 
           while(lines[l][k] && k < BUFFER_SIZE - 2 && k < console_width - 2) {
@@ -480,27 +608,19 @@ void update_screen(int *current_focus_line, int *displayed_focus_line,
             k++;
           }
 
-          /* We fill the rest of the line with blanks if this is the
-             highlighted line */
+          /* Highlight the highlighted line ... */
 
           if(l == new_focus_line) {
             while(k < console_width) {
               buffer[k++] = ' ';
             }
-          }
-
-          buffer[k++] = '\n';
-          buffer[k++] = '\0';
-
-          clrtoeol();
-
-          /* Highlight the highlighted line ... */
-
-          if(l == new_focus_line) {
             attron(attr_focus_line);
             addnstr(buffer, console_width);
             attroff(attr_focus_line);
           } else {
+            buffer[k++] = '\n';
+            buffer[k++] = '\0';
+            /* clrtoeol(); */
             addnstr(buffer, console_width);
           }
 
@@ -601,7 +721,7 @@ void update_screen(int *current_focus_line, int *displayed_focus_line,
 
 /*********************************************************************/
 
-void store_line(hash_table_t *hash_table,
+void store_line(struct hash_table_t *hash_table,
                 const char *new_line,
                 int *nb_lines, char **lines) {
   int dup;
@@ -632,7 +752,7 @@ void store_line(hash_table_t *hash_table,
   }
 
   if(dup < 0) {
-    lines[*nb_lines] = (char *) malloc((strlen(new_line) + 1) * sizeof(char));
+    lines[*nb_lines] = safe_malloc((strlen(new_line) + 1) * sizeof(char));
     strcpy(lines[*nb_lines], new_line);
   } else {
     /* The string was already in there, so we do not allocate a new
@@ -644,7 +764,7 @@ void store_line(hash_table_t *hash_table,
   (*nb_lines)++;
 }
 
-void read_file(hash_table_t *hash_table,
+void read_file(struct hash_table_t *hash_table,
                const char *input_filename,
                int nb_lines_max, int *nb_lines, char **lines) {
 
@@ -655,8 +775,8 @@ void read_file(hash_table_t *hash_table,
   file = fopen(input_filename, "r");
 
   if(!file) {
-    fprintf(stderr, "Selector: Can not open `%s'.\n", input_filename);
-    exit(1);
+    fprintf(stderr, "selector: Can not open `%s'.\n", input_filename);
+    exit(EXIT_FAILURE);
   }
 
   start = 0;
@@ -688,10 +808,11 @@ void read_file(hash_table_t *hash_table,
 
     if(eol == BUFFER_SIZE) {
       raw_line[BUFFER_SIZE - 1] = '\0';
-      fprintf(stderr, "Selector: Line too long (max is %d characters):\n", BUFFER_SIZE);
-      fprintf(stderr, raw_line);
+      fprintf(stderr, "selector: Line too long (max is %d characters):\n",
+              BUFFER_SIZE);
+      fprintf(stderr, "%s", raw_line);
       fprintf(stderr, "\n");
-      exit(1);
+      exit(EXIT_FAILURE);
     }
 
     /* If we got a line, we replace the carriage return by a \0 to
@@ -710,27 +831,57 @@ void read_file(hash_table_t *hash_table,
 
 /*********************************************************************/
 
+/* For long options that have no equivalent short option, use a
+   non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
+enum
+{
+  OPT_BASH_MODE = CHAR_MAX + 1
+};
+
+static struct option long_options[] = {
+  { "output-file", 1, 0, 'o' },
+  { "pattern-separator", 1, 0, 's' },
+  { "label-separator", 1, 0, 'x' },
+  { "inject-in-tty", no_argument, 0, 'v' },
+  { "add-control-qs", no_argument, 0, 'w' },
+  { "monochrome", no_argument, 0, 'm' },
+  { "no-beep", no_argument, 0, 'q' },
+  { "revert-order", no_argument, 0, 'i' },
+  { "remove-bash-prefix", no_argument, 0, 'b' },
+  { "remove-zsh-prefix", no_argument, 0, 'z' },
+  { "remove-duplicates", no_argument, 0, 'd' },
+  { "regexp", no_argument, 0, 'e' },
+  { "case-sensitive", no_argument, 0, 'a' },
+  { "title", 1, 0, 't' },
+  { "number-of-lines", 1, 0, 'l' },
+  { "colors", 1, 0, 'c' },
+  { "bash", no_argument, 0, OPT_BASH_MODE },
+  { "help", no_argument, 0, 'h' },
+  { 0, 0, 0, 0 }
+};
+
 int main(int argc, char **argv) {
 
-  char input_filename[BUFFER_SIZE], output_filename[BUFFER_SIZE];
+  char output_filename[BUFFER_SIZE];
   char pattern[BUFFER_SIZE];
-  int i, k, l, n;
+  int c, k, l, n;
   int cursor_position;
-  int error = 0, show_help = 0;
-  int rest_are_files = 0;
+  int error = 0, show_help = 0, done = 0;
   int key;
   int current_focus_line, displayed_focus_line;
 
+  int colors[4];
   int color_fg_modeline, color_bg_modeline;
   int color_fg_highlight, color_bg_highlight;
 
   char **lines, **labels;
   int nb_lines;
-  hash_table_t *hash_table;
+  struct hash_table_t *hash_table;
+  char *bash_histsize;
 
-  if(!ttyname(STDIN_FILENO)) {
-    fprintf(stderr, "Selector: The standard input is not a tty.\n");
-    exit(1);
+  if(!isatty(STDIN_FILENO)) {
+    fprintf(stderr, "selector: The standard input is not a tty.\n");
+    exit(EXIT_FAILURE);
   }
 
   color_fg_modeline  = COLOR_WHITE;
@@ -740,167 +891,127 @@ int main(int argc, char **argv) {
 
   setlocale(LC_ALL, "");
 
-  strcpy(input_filename, "");
   strcpy(output_filename, "");
 
-  i = 1;
-  while(!error && !show_help && i < argc && argv[i][0] == '-' && !rest_are_files) {
+  while ((c = getopt_long(argc, argv, "o:s:x:vwmqf:ibzdeant:l:c:-h",
+                          long_options, NULL)) != -1) {
 
-    if(strcmp(argv[i], "-o") == 0) {
-      check_opt(argc, argv, i, 1, "<output filename>");
-      strncpy(output_filename, argv[i+1], BUFFER_SIZE);
-      i += 2;
-    }
+    switch(c) {
 
-    else if(strcmp(argv[i], "-s") == 0) {
-      check_opt(argc, argv, i, 1, "<pattern separator>");
-      pattern_separator = argv[i+1][0];
-      i += 2;
-    }
+    case 'o':
+      strncpy(output_filename, optarg, BUFFER_SIZE);
+      break;
 
-    else if(strcmp(argv[i], "-x") == 0) {
-      check_opt(argc, argv, i, 1, "<label separator>");
-      label_separator = argv[i+1][0];
-      i += 2;
-    }
+    case 's':
+      pattern_separator = optarg[0];
+      break;
+
+    case 'x':
+      label_separator = optarg[0];
+      break;
 
-    else if(strcmp(argv[i], "-v") == 0) {
+    case 'v':
       output_to_vt_buffer = 1;
-      i++;
-    }
+      break;
 
-    else if(strcmp(argv[i], "-w") == 0) {
+    case 'w':
       add_control_qs = 1;
-      i++;
-    }
+      break;
 
-    else if(strcmp(argv[i], "-m") == 0) {
+    case 'm':
       with_colors = 0;
-      i++;
-    }
+      break;
 
-    else if(strcmp(argv[i], "-q") == 0) {
+    case 'q':
       error_flash = 1;
-      i++;
-    }
-
-    else if(strcmp(argv[i], "-f") == 0) {
-      check_opt(argc, argv, i, 1, "<input filename>");
-      strncpy(input_filename, argv[i+1], BUFFER_SIZE);
-      i += 2;
-    }
+      break;
 
-    else if(strcmp(argv[i], "-i") == 0) {
+    case 'i':
       inverse_order = 1;
-      i++;
-    }
+      break;
 
-    else if(strcmp(argv[i], "-b") == 0) {
+    case 'b':
       bash_history = 1;
-      i++;
-    }
+      break;
 
-    else if(strcmp(argv[i], "-z") == 0) {
+    case 'z':
       zsh_history = 1;
-      i++;
-    }
+      break;
 
-    else if(strcmp(argv[i], "-d") == 0) {
+    case 'd':
       remove_duplicates = 1;
-      i++;
-    }
+      break;
 
-    else if(strcmp(argv[i], "-e") == 0) {
+    case 'e':
       use_regexp = 1;
-      i++;
-    }
+      break;
 
-    else if(strcmp(argv[i], "-a") == 0) {
+    case 'a':
       case_sensitive = 1;
-      i++;
-    }
-
-    else if(strcmp(argv[i], "-t") == 0) {
-      check_opt(argc, argv, i, 1, "<title>");
-      free(title);
-      title = (char *) malloc((strlen(argv[i+1]) + 1) * sizeof(char));
-      strcpy(title, argv[i+1]);
-      i += 2;
-    }
+      break;
 
-    else if(strcmp(argv[i], "-l") == 0) {
-      check_opt(argc, argv, i, 1, "<maximum number of lines>");
-      nb_lines_max = string_to_positive_integer(argv[i+1]);
-      i += 2;
-    }
-
-    else if(strcmp(argv[i], "-c") == 0) {
-      check_opt(argc, argv, i, 4, "<fg modeline> <bg modeline> <fg highlight> <bg highlight>");
-      color_fg_modeline = string_to_positive_integer(argv[i + 1]);
-      color_bg_modeline = string_to_positive_integer(argv[i + 2]);
-      color_fg_highlight = string_to_positive_integer(argv[i + 3]);
-      color_bg_highlight = string_to_positive_integer(argv[i + 4]);
-      i += 5;
-    }
+    case 'n':
+      exclamation_negates = 1;
+      break;
 
-    else if(strcmp(argv[i], "--") == 0) {
-      rest_are_files = 1;
-      i++;
-    }
-
-    else if(strcmp(argv[i], "-h") == 0) {
+    case 't':
+      free(title);
+      title = safe_malloc((strlen(optarg) + 1) * sizeof(char));
+      strcpy(title, optarg);
+      break;
+
+    case 'l':
+      str_to_positive_integers(optarg, &nb_lines_max, 1);
+      break;
+
+    case 'c':
+      str_to_positive_integers(optarg, colors, 4);
+      color_fg_modeline = colors[0];
+      color_bg_modeline = colors[1];
+      color_fg_highlight = colors[2];
+      color_bg_highlight = colors[3];
+      break;
+
+    case 'h':
       show_help = 1;
-      i++;
-    }
+      break;
+
+    case OPT_BASH_MODE:
+      /* Same as -c 7,4,0,3 -q */
+      /* color_fg_modeline = 7; */
+      /* color_bg_modeline = 4; */
+      /* color_fg_highlight = 0; */
+      /* color_bg_highlight = 3; */
+      /* error_flash = 1; */
+      /* Same as -b -i -d -v -w */
+      bash_history = 1;
+      inverse_order = 1;
+      remove_duplicates = 1;
+      output_to_vt_buffer = 1;
+      add_control_qs = 1;
+      bash_histsize = getenv("HISTSIZE");
+      if(bash_histsize) {
+        str_to_positive_integers(bash_histsize, &nb_lines_max, 1);
+      }
+      break;
 
-    else {
-      fprintf(stderr, "Selector: Unknown option %s.\n", argv[i]);
+    default:
       error = 1;
+      break;
     }
   }
 
-  if(show_help || error) {
-    FILE *out;
-    if(show_help) {
-      out = stdout;
-    } else {
-      out = stderr;
-    }
+  if(error) {
+    usage(stderr);
+    exit(EXIT_FAILURE);
+  }
 
-    fprintf(out, "Selector version %s-R%s\n", VERSION, REVISION_NUMBER);
-    fprintf(out, "Written by Francois Fleuret <francois@fleuret.org>.\n");
-    fprintf(out, "\n");
-    fprintf(out, "Usage: %s [options] [<filename1> [<filename2> ...]]\n", argv[0]);
-    fprintf(out, "\n");
-    fprintf(out, " -h      show this help\n");
-    fprintf(out, " -v      inject the selected line in the tty\n");
-    fprintf(out, " -w      quote control characters with ^Qs when using -v\n");
-    fprintf(out, " -d      remove duplicated lines\n");
-    fprintf(out, " -b      remove the bash history line prefix\n");
-    fprintf(out, " -z      remove the zsh history line prefix\n");
-    fprintf(out, " -i      invert the order of lines\n");
-    fprintf(out, " -e      start in regexp mode\n");
-    fprintf(out, " -a      start in case sensitive mode\n");
-    fprintf(out, " -m      monochrome mode\n");
-    fprintf(out, " -q      make a flash instead of a beep on an edition error\n");
-    fprintf(out, " --      all following arguments are filenames\n");
-    fprintf(out, " -t <title>\n");
-    fprintf(out, "         add a title in the modeline\n");
-    fprintf(out, " -c <fg modeline> <bg modeline> <fg highlight> <bg highlight>\n");
-    fprintf(out, "         set the display colors\n");
-    fprintf(out, " -o <output filename>\n");
-    fprintf(out, "         set a file to write the selected line to\n");
-    fprintf(out, " -s <pattern separator>\n");
-    fprintf(out, "         set the symbol to separate substrings in the pattern\n");
-    fprintf(out, " -x <label separator>\n");
-    fprintf(out, "         set the symbol to terminate the label\n");
-    fprintf(out, " -l <max number of lines>\n");
-    fprintf(out, "         set the maximum number of lines to take into account\n");
-    fprintf(out, "\n");
-    exit(error);
+  if(show_help) {
+    usage(stdout);
+    exit(EXIT_SUCCESS);
   }
 
-  lines = (char **) malloc(nb_lines_max * sizeof(char *));
+  lines = safe_malloc(nb_lines_max * sizeof(char *));
 
   nb_lines = 0;
 
@@ -910,17 +1021,11 @@ int main(int argc, char **argv) {
     hash_table = 0;
   }
 
-  if(input_filename[0]) {
+  while(optind < argc) {
     read_file(hash_table,
-              input_filename,
+              argv[optind],
               nb_lines_max, &nb_lines, lines);
-  }
-
-  while(i < argc) {
-    read_file(hash_table,
-              argv[i],
-              nb_lines_max, &nb_lines, lines);
-    i++;
+    optind++;
   }
 
   if(hash_table) {
@@ -939,10 +1044,10 @@ int main(int argc, char **argv) {
   nb_lines = n;
 
   if(inverse_order) {
-    for(i = 0; i < nb_lines / 2; i++) {
-      char *s = lines[nb_lines - 1 - i];
-      lines[nb_lines - 1 - i] = lines[i];
-      lines[i] = s;
+    for(l = 0; l < nb_lines / 2; l++) {
+      char *s = lines[nb_lines - 1 - l];
+      lines[nb_lines - 1 - l] = lines[l];
+      lines[l] = s;
     }
   }
 
@@ -950,7 +1055,7 @@ int main(int argc, char **argv) {
      label_separator and transform control characters to printable
      ones */
 
-  labels = (char **) malloc(nb_lines * sizeof(char *));
+  labels = safe_malloc(nb_lines * sizeof(char *));
 
   for(l = 0; l < nb_lines; l++) {
     char *s, *t;
@@ -963,7 +1068,7 @@ int main(int argc, char **argv) {
       e += strlen(u);
     }
 
-    labels[l] = (char *) malloc((e + 1) * sizeof(char));
+    labels[l] = safe_malloc((e + 1) * sizeof(char));
     t = lines[l];
     s = labels[l];
     while(*t && *t != label_separator) {
@@ -982,7 +1087,6 @@ int main(int argc, char **argv) {
   initscr();
   cbreak();
   noecho();
-  /* nonl(); */
   intrflush(stdscr, FALSE);
 
   /* So that the arrow keys work */
@@ -1002,8 +1106,9 @@ int main(int argc, char **argv) {
        color_bg_highlight < 0 || color_bg_highlight >= COLORS) {
       echo();
       endwin();
-      fprintf(stderr, "Selector: Color numbers have to be between 0 and %d.\n", COLORS - 1);
-      exit(1);
+      fprintf(stderr, "selector: Color numbers have to be between 0 and %d.\n",
+              COLORS - 1);
+      exit(EXIT_FAILURE);
     }
 
     init_pair(1, color_fg_modeline, color_bg_modeline);
@@ -1107,18 +1212,32 @@ int main(int argc, char **argv) {
     }
 
     else if(key == '\014') { /* ^L */
-      /* I suspect that we may sometime mess up the display */
+      /* I suspect that we may sometime mess up the display, so ^L is
+         here to force a full refresh */
       clear();
     }
 
+    else if(key == '\007' || /* ^G */
+            key == '\033' || /* ^[ (escape) */
+            key == '\n' ||
+            key == KEY_ENTER) {
+      done = 1;
+    }
+
+    else if(key == KEY_RESIZE || key == -1) {
+      /* Do nothing when the tty is resized */
+    }
+
+    else {
+      /* Unknown key */
+      error_feedback();
+    }
+
     update_screen(&current_focus_line, &displayed_focus_line,
                   motion,
                   nb_lines, labels, cursor_position, pattern);
 
-  } while(key != '\007' && /* ^G */
-          key != '\033' && /* ^[ (escape) */
-          key != '\n' &&
-          key != KEY_ENTER);
+  } while(!done);
 
   echo();
   endwin();
@@ -1140,19 +1259,21 @@ int main(int argc, char **argv) {
     }
 
     if(output_to_vt_buffer && t) {
-      inject_into_tty_buffer(t);
+      inject_into_tty_buffer(t, add_control_qs);
     }
 
     if(output_filename[0]) {
       FILE *out = fopen(output_filename, "w");
       if(out) {
         if(t) {
-          fprintf(out, t);
+          fprintf(out, "%s", t);
         }
         fprintf(out, "\n");
       } else {
-        fprintf(stderr, "Selector: Can not open %s for writing.\n", output_filename);
-        exit(1);
+        fprintf(stderr,
+                "selector: Can not open %s for writing.\n",
+                output_filename);
+        exit(EXIT_FAILURE);
       }
       fclose(out);
     }
@@ -1170,5 +1291,5 @@ int main(int argc, char **argv) {
   free(lines);
   free(title);
 
-  exit(0);
+  exit(EXIT_SUCCESS);
 }