Moved the help in its own function.
[selector.git] / selector.c
1
2 /*
3  *  selector is a simple command line utility for selection of strings
4  *  with a dynamic pattern-matching.
5  *
6  *  Copyright (c) 2009 Francois Fleuret
7  *  Written by Francois Fleuret <francois@fleuret.org>
8  *
9  *  This file is part of selector.
10  *
11  *  selector is free software: you can redistribute it and/or modify
12  *  it under the terms of the GNU General Public License version 3 as
13  *  published by the Free Software Foundation.
14  *
15  *  selector is distributed in the hope that it will be useful, but
16  *  WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  *  General Public License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with selector.  If not, see <http://www.gnu.org/licenses/>.
22  *
23  */
24
25 /*
26
27   To use it as a super-history-search for bash:
28   selector -q -b -i -d -v -w -l ${HISTSIZE} <(history)
29
30 */
31
32 #define _GNU_SOURCE
33
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <string.h>
38 #include <errno.h>
39 #include <ncurses.h>
40 #include <fcntl.h>
41 #include <sys/ioctl.h>
42 #include <termios.h>
43 #include <regex.h>
44 #include <locale.h>
45
46 #define VERSION "1.0"
47
48 #define BUFFER_SIZE 4096
49
50 /* Yeah, global variables! */
51
52 int nb_lines_max = 1000;
53 char pattern_separator = ';';
54 char label_separator = '\0';
55 int output_to_vt_buffer = 0;
56 int add_control_qs = 0;
57 int with_colors = 1;
58 int zsh_history = 0;
59 int bash_history = 0;
60 int inverse_order = 0;
61 int remove_duplicates = 0;
62 int use_regexp = 0;
63 int case_sensitive = 0;
64 char *title = 0;
65 int error_flash = 0;
66
67 int attr_modeline, attr_focus_line, attr_error;
68
69 /********************************************************************/
70
71 /* malloc with error checking.  */
72
73 void *safe_malloc(size_t n) {
74   void *p = malloc(n);
75   if (!p && n != 0) {
76     printf("Can not allocate memory: %s\n", strerror(errno));
77     exit(EXIT_FAILURE);
78   }
79   return p;
80 }
81
82 /*********************************************************************/
83
84 void inject_into_tty_buffer(char *string, int add_control_qs) {
85   struct termios oldtio, newtio;
86   const char *k;
87   const char control_q = '\021';
88   tcgetattr(STDIN_FILENO, &oldtio);
89   memset(&newtio, 0, sizeof(newtio));
90   /* Set input mode (non-canonical, *no echo*,...) */
91   tcsetattr(STDIN_FILENO, TCSANOW, &newtio);
92   /* Put the selected string in the tty input buffer */
93   for(k = string; *k; k++) {
94     if(add_control_qs && !(*k >= ' ' && *k <= '~')) {
95       /* Add ^Q to quote control characters */
96       ioctl(STDIN_FILENO, TIOCSTI, &control_q);
97     }
98     ioctl(STDIN_FILENO, TIOCSTI, k);
99   }
100   /* Restore the old settings */
101   tcsetattr(STDIN_FILENO, TCSANOW, &oldtio);
102 }
103
104 /*********************************************************************/
105
106 void check_opt(int argc, char **argv, int n_opt, int n, const char *help) {
107   if(n_opt + n >= argc) {
108     fprintf(stderr, "Selector: Missing argument for %s, expecting %s.\n",
109             argv[n_opt], help);
110     exit(EXIT_FAILURE);
111   }
112 }
113
114 int string_to_positive_integer(char *string) {
115   int error = 0;
116   int result = 0;
117   char *s;
118
119   if(*string) {
120     for(s = string; *s; s++) {
121       if(*s >= '0' && *s <= '9') {
122         result = result * 10 + (int) (*s - '0');
123       } else error = 1;
124     }
125   } else error = 1;
126
127   if(error) {
128     fprintf(stderr,
129             "Selector: Value `%s' is not a positive integer.\n",
130             string);
131     exit(EXIT_FAILURE);
132   }
133
134   return result;
135 }
136
137 void error_feedback() {
138   if(error_flash) {
139     flash();
140   } else {
141     beep();
142   }
143 }
144
145 void print_help(FILE *out) {
146
147   fprintf(out, "Selector version %s-R%s (%s)\n", VERSION, REVISION_NUMBER, UNAME);
148   fprintf(out, "Written by Francois Fleuret <francois@fleuret.org>.\n");
149   fprintf(out, "\n");
150   fprintf(out, "Usage: selector [options] [<filename1> [<filename2> ...]]\n");
151   fprintf(out, "\n");
152   fprintf(out, " -h      show this help\n");
153   fprintf(out, " -v      inject the selected line in the tty\n");
154   fprintf(out, " -w      quote control characters with ^Qs when using -v\n");
155   fprintf(out, " -d      remove duplicated lines\n");
156   fprintf(out, " -b      remove the bash history line prefix\n");
157   fprintf(out, " -z      remove the zsh history line prefix\n");
158   fprintf(out, " -i      invert the order of lines\n");
159   fprintf(out, " -e      start in regexp mode\n");
160   fprintf(out, " -a      start in case sensitive mode\n");
161   fprintf(out, " -m      monochrome mode\n");
162   fprintf(out, " -q      make a flash instead of a beep on an edition error\n");
163   fprintf(out, " --      all following arguments are filenames\n");
164   fprintf(out, " -t <title>\n");
165   fprintf(out, "         add a title in the modeline\n");
166   fprintf(out, " -c <fg modeline> <bg modeline> <fg highlight> <bg highlight>\n");
167   fprintf(out, "         set the display colors\n");
168   fprintf(out, " -o <output filename>\n");
169   fprintf(out, "         set a file to write the selected line to\n");
170   fprintf(out, " -s <pattern separator>\n");
171   fprintf(out, "         set the symbol to separate substrings in the pattern\n");
172   fprintf(out, " -x <label separator>\n");
173   fprintf(out, "         set the symbol to terminate the label\n");
174   fprintf(out, " -l <max number of lines>\n");
175   fprintf(out, "         set the maximum number of lines to take into account\n");
176   fprintf(out, "\n");
177 }
178
179 /*********************************************************************/
180
181 /* A quick and dirty hash table */
182
183 /* The table itself stores indexes of the strings taken in a char**
184    table. When a string is added, if it was already in the table, the
185    new index replaces the previous one.  */
186
187 struct hash_table_t {
188   int size;
189   int *entries;
190 };
191
192 struct hash_table_t *new_hash_table(int size) {
193   int k;
194   struct hash_table_t *hash_table;
195
196   hash_table = safe_malloc(sizeof(struct hash_table_t));
197
198   hash_table->size = size;
199   hash_table->entries = safe_malloc(hash_table->size * sizeof(int));
200
201   for(k = 0; k < hash_table->size; k++) {
202     hash_table->entries[k] = -1;
203   }
204
205   return hash_table;
206 }
207
208 void free_hash_table(struct hash_table_t *hash_table) {
209   free(hash_table->entries);
210   free(hash_table);
211 }
212
213 /* Adds new_string in the table, associated to new_index. If this
214    string was not already in the table, returns -1. Otherwise, returns
215    the previous index it had. */
216
217 int add_and_get_previous_index(struct hash_table_t *hash_table,
218                                const char *new_string, int new_index,
219                                char **strings) {
220
221   unsigned int code = 0;
222   int k;
223
224   /* This is my recipe. I checked, it seems to work (as long as
225      hash_table->size is not a multiple of 387433 that should be
226      okay) */
227
228   for(k = 0; new_string[k]; k++) {
229     code = code * 387433 + (unsigned int) (new_string[k]);
230   }
231
232   code = code % hash_table->size;
233
234   while(hash_table->entries[code] >= 0) {
235     /* There is a string with that code */
236     if(strcmp(new_string, strings[hash_table->entries[code]]) == 0) {
237       /* It is the same string, we keep a copy of the stored index */
238       int result = hash_table->entries[code];
239       /* Put the new one */
240       hash_table->entries[code] = new_index;
241       /* And return the previous one */
242       return result;
243     }
244     /* This collision was not the same string, let's move to the next
245        in the table */
246     code = (code + 1) % hash_table->size;
247   }
248
249   /* This string was not already in there, store the index in the
250      table and return -1 */
251
252   hash_table->entries[code] = new_index;
253   return -1;
254 }
255
256 /*********************************************************************
257  A matcher matches either with a collection of substrings, or with a
258  regexp */
259
260 typedef struct {
261   regex_t preg;
262   int regexp_error;
263   int nb_patterns;
264   int case_sensitive;
265   char *splitted_patterns, **patterns;
266 } matcher_t;
267
268 int match(char *string, matcher_t *matcher) {
269   int n;
270   if(matcher->nb_patterns >= 0) {
271     if(matcher->case_sensitive) {
272       for(n = 0; n < matcher->nb_patterns; n++) {
273         if(strstr(string, matcher->patterns[n]) == 0) return 0;
274       }
275     } else {
276       for(n = 0; n < matcher->nb_patterns; n++) {
277         if(strcasestr(string, matcher->patterns[n]) == 0) return 0;
278       }
279     }
280     return 1;
281   } else {
282     return regexec(&matcher->preg, string, 0, 0, 0) == 0;
283   }
284 }
285
286 void free_matcher(matcher_t *matcher) {
287   if(matcher->nb_patterns < 0) {
288     if(!matcher->regexp_error) regfree(&matcher->preg);
289   } else {
290     free(matcher->splitted_patterns);
291     free(matcher->patterns);
292   }
293 }
294
295 void initialize_matcher(int use_regexp, int case_sensitive,
296                         matcher_t *matcher, const char *pattern) {
297   const char *s;
298   char *t, *last_pattern_start;
299   int n;
300
301   if(use_regexp) {
302     matcher->nb_patterns = -1;
303     matcher->regexp_error = regcomp(&matcher->preg, pattern,
304                                     case_sensitive ? 0 : REG_ICASE);
305   } else {
306     matcher->regexp_error = 0;
307     matcher->nb_patterns = 1;
308     matcher->case_sensitive = case_sensitive;
309
310     for(s = pattern; *s; s++) {
311       if(*s == pattern_separator) {
312         matcher->nb_patterns++;
313       }
314     }
315
316     matcher->splitted_patterns =
317       safe_malloc((strlen(pattern) + 1) * sizeof(char));
318
319     matcher->patterns =
320       safe_malloc(matcher->nb_patterns * sizeof(char *));
321
322     strcpy(matcher->splitted_patterns, pattern);
323
324     n = 0;
325     last_pattern_start = matcher->splitted_patterns;
326     for(t = matcher->splitted_patterns; n < matcher->nb_patterns; t++) {
327       if(*t == pattern_separator || *t == '\0') {
328         *t = '\0';
329         matcher->patterns[n++] = last_pattern_start;
330         last_pattern_start = t + 1;
331       }
332     }
333   }
334 }
335
336 /*********************************************************************
337  Buffer edition */
338
339 void delete_char(char *buffer, int *position) {
340   if(buffer[*position]) {
341     int c = *position;
342     while(c < BUFFER_SIZE && buffer[c]) {
343       buffer[c] = buffer[c+1];
344       c++;
345     }
346   } else error_feedback();
347 }
348
349 void backspace_char(char *buffer, int *position) {
350   if(*position > 0) {
351     if(buffer[*position]) {
352       int c = *position - 1;
353       while(buffer[c]) {
354         buffer[c] = buffer[c+1];
355         c++;
356       }
357     } else {
358       buffer[*position - 1] = '\0';
359     }
360
361     (*position)--;
362   } else error_feedback();
363 }
364
365 void insert_char(char *buffer, int *position, char character) {
366   if(strlen(buffer) < BUFFER_SIZE - 1) {
367     int c = *position;
368     char t = buffer[c], u;
369     while(t) {
370       c++;
371       u = buffer[c];
372       buffer[c] = t;
373       t = u;
374     }
375     c++;
376     buffer[c] = '\0';
377     buffer[(*position)++] = character;
378   } else error_feedback();
379 }
380
381 void kill_before_cursor(char *buffer, int *position) {
382   int s = 0;
383   while(buffer[*position + s]) {
384     buffer[s] = buffer[*position + s];
385     s++;
386   }
387   buffer[s] = '\0';
388   *position = 0;
389 }
390
391 void kill_after_cursor(char *buffer, int *position) {
392   buffer[*position] = '\0';
393 }
394
395 /*********************************************************************/
396
397 int previous_visible(int current_line, int nb_lines, char **lines,
398                      matcher_t *matcher) {
399   int line = current_line - 1;
400   while(line >= 0 && !match(lines[line], matcher)) line--;
401   return line;
402 }
403
404 int next_visible(int current_line, int nb_lines, char **lines,
405                  matcher_t *matcher) {
406   int line = current_line + 1;
407   while(line < nb_lines && !match(lines[line], matcher)) line++;
408
409   if(line < nb_lines)
410     return line;
411   else
412     return -1;
413 }
414
415 /*********************************************************************/
416
417 /* The line highlighted is the first one matching the matcher in that
418    order: (1) current_focus_line after motion, if it does not match,
419    then (2) the first with a greater index, if none matches, then (3)
420    the first with a lesser index.
421
422    The index of the line actually shown highlighted is written in
423    displayed_focus_line (it can be -1 if no line at all matches the
424    matcher)
425
426    If there is a motion and a line is actually shown highlighted, its
427    value is written in current_focus_line. */
428
429 void update_screen(int *current_focus_line, int *displayed_focus_line,
430                    int motion,
431                    int nb_lines, char **lines,
432                    int cursor_position,
433                    char *pattern) {
434
435   char buffer[BUFFER_SIZE];
436   matcher_t matcher;
437   int k, l, m;
438   int console_width, console_height;
439   int nb_printed_lines = 0;
440   int cursor_x;
441
442   initialize_matcher(use_regexp, case_sensitive, &matcher, pattern);
443
444   console_width = getmaxx(stdscr);
445   console_height = getmaxy(stdscr);
446
447   use_default_colors();
448
449   /* Add an empty line where we will print the modeline at the end */
450
451   addstr("\n");
452
453   /* If the regexp is erroneous, print a message saying so */
454
455   if(matcher.regexp_error) {
456     attron(attr_error);
457     addnstr("Regexp syntax error", console_width);
458     attroff(attr_error);
459   }
460
461   /* Else, and we do have lines to select from, find a visible line. */
462
463   else if(nb_lines > 0) {
464     int new_focus_line;
465     if(match(lines[*current_focus_line], &matcher)) {
466       new_focus_line = *current_focus_line;
467     } else {
468       new_focus_line = next_visible(*current_focus_line, nb_lines, lines,
469                                     &matcher);
470       if(new_focus_line < 0) {
471         new_focus_line = previous_visible(*current_focus_line, nb_lines, lines,
472                                           &matcher);
473       }
474     }
475
476     /* If we found a visible line and we should move, let's move */
477
478     if(new_focus_line >= 0 && motion != 0) {
479       int l = new_focus_line;
480       if(motion > 0) {
481         /* We want to go down, let's find the first visible line below */
482         for(m = 0; l >= 0 && m < motion; m++) {
483           l = next_visible(l, nb_lines, lines, &matcher);
484           if(l >= 0) {
485             new_focus_line = l;
486           }
487         }
488       } else {
489         /* We want to go up, let's find the first visible line above */
490         for(m = 0; l >= 0 && m < -motion; m++) {
491           l = previous_visible(l, nb_lines, lines, &matcher);
492           if(l >= 0) {
493             new_focus_line = l;
494           }
495         }
496       }
497     }
498
499     /* Here new_focus_line is either a line number matching the
500        pattern, or -1 */
501
502     if(new_focus_line >= 0) {
503
504       int first_line = new_focus_line, last_line = new_focus_line;
505       int nb_match = 1;
506
507       /* We find the first and last line to show, so that the total of
508          visible lines between them (them included) is
509          console_height-1 */
510
511       while(nb_match < console_height-1 &&
512             (first_line > 0 || last_line < nb_lines - 1)) {
513
514         if(first_line > 0) {
515           first_line--;
516           while(first_line > 0 && !match(lines[first_line], &matcher)) {
517             first_line--;
518           }
519           if(match(lines[first_line], &matcher)) {
520             nb_match++;
521           }
522         }
523
524         if(nb_match < console_height - 1 && last_line < nb_lines - 1) {
525           last_line++;
526           while(last_line < nb_lines - 1 && !match(lines[last_line], &matcher)) {
527             last_line++;
528           }
529
530           if(match(lines[last_line], &matcher)) {
531             nb_match++;
532           }
533         }
534       }
535
536       /* Now we display them */
537
538       for(l = first_line; l <= last_line; l++) {
539         if(match(lines[l], &matcher)) {
540           int k = 0;
541
542           while(lines[l][k] && k < BUFFER_SIZE - 2 && k < console_width - 2) {
543             buffer[k] = lines[l][k];
544             k++;
545           }
546
547           /* We fill the rest of the line with blanks if this is the
548              highlighted line */
549
550           if(l == new_focus_line) {
551             while(k < console_width) {
552               buffer[k++] = ' ';
553             }
554           }
555
556           buffer[k++] = '\n';
557           buffer[k++] = '\0';
558
559           clrtoeol();
560
561           /* Highlight the highlighted line ... */
562
563           if(l == new_focus_line) {
564             attron(attr_focus_line);
565             addnstr(buffer, console_width);
566             attroff(attr_focus_line);
567           } else {
568             addnstr(buffer, console_width);
569           }
570
571           nb_printed_lines++;
572         }
573       }
574
575       /* If we are on a focused line and we moved, this become the new
576          focus line */
577
578       if(motion != 0) {
579         *current_focus_line = new_focus_line;
580       }
581     }
582
583     *displayed_focus_line = new_focus_line;
584
585     if(nb_printed_lines == 0) {
586       attron(attr_error);
587       addnstr("No selection", console_width);
588       attroff(attr_error);
589     }
590   }
591
592   /* Else, print a message saying that there are no lines to select from */
593
594   else {
595     attron(attr_error);
596     addnstr("Empty choice", console_width);
597     attroff(attr_error);
598   }
599
600   clrtobot();
601
602   /* Draw the modeline */
603
604   move(0, 0);
605
606   attron(attr_modeline);
607
608   for(k = 0; k < console_width; k++) buffer[k] = ' ';
609   buffer[console_width] = '\0';
610   addnstr(buffer, console_width);
611
612   move(0, 0);
613
614   /* There must be a more elegant way of moving the cursor at a
615      location met during display */
616
617   cursor_x = 0;
618
619   if(title) {
620     addstr(title);
621     addstr(" ");
622     cursor_x += strlen(title) + 1;
623   }
624
625   sprintf(buffer, "%d/%d ", nb_printed_lines, nb_lines);
626   addstr(buffer);
627   cursor_x += strlen(buffer);
628
629   addnstr(pattern, cursor_position);
630   cursor_x += cursor_position;
631
632   if(pattern[cursor_position]) {
633     addstr(pattern + cursor_position);
634   } else {
635     addstr(" ");
636   }
637
638   /* Add a few info about the mode we are in (regexp and/or case
639      sensitive) */
640
641   if(use_regexp || case_sensitive) {
642     addstr(" [");
643     if(use_regexp) {
644       addstr("regexp");
645     }
646
647     if(case_sensitive) {
648       if(use_regexp) {
649         addstr(",");
650       }
651       addstr("case");
652     }
653     addstr("]");
654   }
655
656   move(0, cursor_x);
657
658   attroff(attr_modeline);
659
660   /* We are done */
661
662   refresh();
663   free_matcher(&matcher);
664 }
665
666 /*********************************************************************/
667
668 void store_line(struct hash_table_t *hash_table,
669                 const char *new_line,
670                 int *nb_lines, char **lines) {
671   int dup;
672
673   /* Remove the zsh history prefix */
674
675   if(zsh_history && *new_line == ':') {
676     while(*new_line && *new_line != ';') new_line++;
677     if(*new_line == ';') new_line++;
678   }
679
680   /* Remove the bash history prefix */
681
682   if(bash_history) {
683     while(*new_line == ' ') new_line++;
684     while(*new_line >= '0' && *new_line <= '9') new_line++;
685     while(*new_line == ' ') new_line++;
686   }
687
688   /* Check for duplicates with the hash table and insert the line in
689      the list if necessary */
690
691   if(hash_table) {
692     dup = add_and_get_previous_index(hash_table,
693                                      new_line, *nb_lines, lines);
694   } else {
695     dup = -1;
696   }
697
698   if(dup < 0) {
699     lines[*nb_lines] = safe_malloc((strlen(new_line) + 1) * sizeof(char));
700     strcpy(lines[*nb_lines], new_line);
701   } else {
702     /* The string was already in there, so we do not allocate a new
703        string but use the pointer to the first occurence of it */
704     lines[*nb_lines] = lines[dup];
705     lines[dup] = 0;
706   }
707
708   (*nb_lines)++;
709 }
710
711 void read_file(struct hash_table_t *hash_table,
712                const char *input_filename,
713                int nb_lines_max, int *nb_lines, char **lines) {
714
715   char raw_line[BUFFER_SIZE];
716   int start, end, eol, k;
717   FILE *file;
718
719   file = fopen(input_filename, "r");
720
721   if(!file) {
722     fprintf(stderr, "Selector: Can not open `%s'.\n", input_filename);
723     exit(EXIT_FAILURE);
724   }
725
726   start = 0;
727   end = 0;
728
729   while(*nb_lines < nb_lines_max && (end > start || !feof(file))) {
730     eol = start;
731
732     /* Look for the end of a line in what is already in the buffer */
733     while(eol < end && raw_line[eol] != '\n') eol++;
734
735     /* if we did not find the of a line, move what has not been
736        processed and is in the buffer to the beginning of the buffer,
737        fill the buffer with new data from the file, and look for the
738        end of a line */
739     if(eol == end) {
740       for(k = 0; k < end - start; k++) {
741         raw_line[k] = raw_line[k + start];
742       }
743       end -= start;
744       eol -= start;
745       start = 0;
746       end += fread(raw_line + end, sizeof(char), BUFFER_SIZE - end, file);
747       while(eol < end && raw_line[eol] != '\n') eol++;
748     }
749
750     /* The end of the line is the buffer size, which means the line is
751        too long */
752
753     if(eol == BUFFER_SIZE) {
754       raw_line[BUFFER_SIZE - 1] = '\0';
755       fprintf(stderr, "Selector: Line too long (max is %d characters):\n",
756               BUFFER_SIZE);
757       fprintf(stderr, raw_line);
758       fprintf(stderr, "\n");
759       exit(EXIT_FAILURE);
760     }
761
762     /* If we got a line, we replace the carriage return by a \0 to
763        finish the string */
764
765     raw_line[eol] = '\0';
766
767     store_line(hash_table, raw_line + start,
768                nb_lines, lines);
769
770     start = eol + 1;
771   }
772
773   fclose(file);
774 }
775
776 /*********************************************************************/
777
778 int main(int argc, char **argv) {
779
780   char input_filename[BUFFER_SIZE], output_filename[BUFFER_SIZE];
781   char pattern[BUFFER_SIZE];
782   int i, k, l, n;
783   int cursor_position;
784   int error = 0, show_help = 0;
785   int rest_are_files = 0;
786   int key;
787   int current_focus_line, displayed_focus_line;
788
789   int color_fg_modeline, color_bg_modeline;
790   int color_fg_highlight, color_bg_highlight;
791
792   char **lines, **labels;
793   int nb_lines;
794   struct hash_table_t *hash_table;
795
796   if(!isatty(STDIN_FILENO)) {
797     fprintf(stderr, "Selector: The standard input is not a tty.\n");
798     exit(EXIT_FAILURE);
799   }
800
801   color_fg_modeline  = COLOR_WHITE;
802   color_bg_modeline  = COLOR_BLACK;
803   color_fg_highlight = COLOR_BLACK;
804   color_bg_highlight = COLOR_YELLOW;
805
806   setlocale(LC_ALL, "");
807
808   strcpy(input_filename, "");
809   strcpy(output_filename, "");
810
811   i = 1;
812
813   while(!error && !show_help &&
814         i < argc &&
815         argv[i][0] == '-' && !rest_are_files) {
816
817     if(strcmp(argv[i], "-o") == 0) {
818       check_opt(argc, argv, i, 1, "<output filename>");
819       strncpy(output_filename, argv[i+1], BUFFER_SIZE);
820       i += 2;
821     }
822
823     else if(strcmp(argv[i], "-s") == 0) {
824       check_opt(argc, argv, i, 1, "<pattern separator>");
825       pattern_separator = argv[i+1][0];
826       i += 2;
827     }
828
829     else if(strcmp(argv[i], "-x") == 0) {
830       check_opt(argc, argv, i, 1, "<label separator>");
831       label_separator = argv[i+1][0];
832       i += 2;
833     }
834
835     else if(strcmp(argv[i], "-v") == 0) {
836       output_to_vt_buffer = 1;
837       i++;
838     }
839
840     else if(strcmp(argv[i], "-w") == 0) {
841       add_control_qs = 1;
842       i++;
843     }
844
845     else if(strcmp(argv[i], "-m") == 0) {
846       with_colors = 0;
847       i++;
848     }
849
850     else if(strcmp(argv[i], "-q") == 0) {
851       error_flash = 1;
852       i++;
853     }
854
855     else if(strcmp(argv[i], "-f") == 0) {
856       check_opt(argc, argv, i, 1, "<input filename>");
857       strncpy(input_filename, argv[i+1], BUFFER_SIZE);
858       i += 2;
859     }
860
861     else if(strcmp(argv[i], "-i") == 0) {
862       inverse_order = 1;
863       i++;
864     }
865
866     else if(strcmp(argv[i], "-b") == 0) {
867       bash_history = 1;
868       i++;
869     }
870
871     else if(strcmp(argv[i], "-z") == 0) {
872       zsh_history = 1;
873       i++;
874     }
875
876     else if(strcmp(argv[i], "-d") == 0) {
877       remove_duplicates = 1;
878       i++;
879     }
880
881     else if(strcmp(argv[i], "-e") == 0) {
882       use_regexp = 1;
883       i++;
884     }
885
886     else if(strcmp(argv[i], "-a") == 0) {
887       case_sensitive = 1;
888       i++;
889     }
890
891     else if(strcmp(argv[i], "-t") == 0) {
892       check_opt(argc, argv, i, 1, "<title>");
893       free(title);
894       title = safe_malloc((strlen(argv[i+1]) + 1) * sizeof(char));
895       strcpy(title, argv[i+1]);
896       i += 2;
897     }
898
899     else if(strcmp(argv[i], "-l") == 0) {
900       check_opt(argc, argv, i, 1, "<maximum number of lines>");
901       nb_lines_max = string_to_positive_integer(argv[i+1]);
902       i += 2;
903     }
904
905     else if(strcmp(argv[i], "-c") == 0) {
906       check_opt(argc, argv, i, 4,
907                 "<fg modeline> <bg modeline> <fg highlight> <bg highlight>");
908       color_fg_modeline = string_to_positive_integer(argv[i + 1]);
909       color_bg_modeline = string_to_positive_integer(argv[i + 2]);
910       color_fg_highlight = string_to_positive_integer(argv[i + 3]);
911       color_bg_highlight = string_to_positive_integer(argv[i + 4]);
912       i += 5;
913     }
914
915     else if(strcmp(argv[i], "--") == 0) {
916       rest_are_files = 1;
917       i++;
918     }
919
920     else if(strcmp(argv[i], "-h") == 0) {
921       show_help = 1;
922       i++;
923     }
924
925     else {
926       fprintf(stderr, "Selector: Unknown option %s.\n", argv[i]);
927       error = 1;
928     }
929   }
930
931   if(show_help || error) {
932     if(error) {
933       print_help(stderr);
934       exit(EXIT_FAILURE);
935     } else {
936       print_help(stdout);
937       exit(EXIT_SUCCESS);
938     }
939   }
940
941   lines = safe_malloc(nb_lines_max * sizeof(char *));
942
943   nb_lines = 0;
944
945   if(remove_duplicates) {
946     hash_table = new_hash_table(nb_lines_max * 10);
947   } else {
948     hash_table = 0;
949   }
950
951   if(input_filename[0]) {
952     read_file(hash_table,
953               input_filename,
954               nb_lines_max, &nb_lines, lines);
955   }
956
957   while(i < argc) {
958     read_file(hash_table,
959               argv[i],
960               nb_lines_max, &nb_lines, lines);
961     i++;
962   }
963
964   if(hash_table) {
965     free_hash_table(hash_table);
966   }
967
968   /* Now remove the null strings */
969
970   n = 0;
971   for(k = 0; k < nb_lines; k++) {
972     if(lines[k]) {
973       lines[n++] = lines[k];
974     }
975   }
976
977   nb_lines = n;
978
979   if(inverse_order) {
980     for(i = 0; i < nb_lines / 2; i++) {
981       char *s = lines[nb_lines - 1 - i];
982       lines[nb_lines - 1 - i] = lines[i];
983       lines[i] = s;
984     }
985   }
986
987   /* Build the labels from the strings, take only the part before the
988      label_separator and transform control characters to printable
989      ones */
990
991   labels = safe_malloc(nb_lines * sizeof(char *));
992
993   for(l = 0; l < nb_lines; l++) {
994     char *s, *t;
995     int e = 0;
996     const char *u;
997     t = lines[l];
998
999     while(*t && *t != label_separator) {
1000       u = unctrl(*t++);
1001       e += strlen(u);
1002     }
1003
1004     labels[l] = safe_malloc((e + 1) * sizeof(char));
1005     t = lines[l];
1006     s = labels[l];
1007     while(*t && *t != label_separator) {
1008       u = unctrl(*t++);
1009       while(*u) { *s++ = *u++; }
1010     }
1011     *s = '\0';
1012   }
1013
1014   pattern[0] = '\0';
1015
1016   cursor_position = 0;
1017
1018   /* Here we start to display with curse */
1019
1020   initscr();
1021   cbreak();
1022   noecho();
1023   intrflush(stdscr, FALSE);
1024
1025   /* So that the arrow keys work */
1026   keypad(stdscr, TRUE);
1027
1028   attr_error = A_STANDOUT;
1029   attr_modeline = A_REVERSE;
1030   attr_focus_line = A_STANDOUT;
1031
1032   if(with_colors && has_colors()) {
1033
1034     start_color();
1035
1036     if(color_fg_modeline < 0  || color_fg_modeline >= COLORS ||
1037        color_bg_modeline < 0  || color_bg_modeline >= COLORS ||
1038        color_fg_highlight < 0 || color_bg_highlight >= COLORS ||
1039        color_bg_highlight < 0 || color_bg_highlight >= COLORS) {
1040       echo();
1041       endwin();
1042       fprintf(stderr, "Selector: Color numbers have to be between 0 and %d.\n",
1043               COLORS - 1);
1044       exit(EXIT_FAILURE);
1045     }
1046
1047     init_pair(1, color_fg_modeline, color_bg_modeline);
1048     attr_modeline = COLOR_PAIR(1);
1049
1050     init_pair(2, color_fg_highlight, color_bg_highlight);
1051     attr_focus_line = COLOR_PAIR(2);
1052
1053     init_pair(3, COLOR_WHITE, COLOR_RED);
1054     attr_error = COLOR_PAIR(3);
1055
1056   }
1057
1058   current_focus_line = 0;
1059   displayed_focus_line = 0;
1060
1061   update_screen(&current_focus_line, &displayed_focus_line,
1062                 0,
1063                 nb_lines, labels, cursor_position, pattern);
1064
1065   do {
1066     int motion = 0;
1067
1068     key = getch();
1069
1070     if(key >= ' ' && key <= '~') { /* Insert character */
1071       insert_char(pattern, &cursor_position, key);
1072     }
1073
1074     else if(key == KEY_BACKSPACE ||
1075             key == '\010' || /* ^H */
1076             key == '\177') { /* ^? */
1077       backspace_char(pattern, &cursor_position);
1078     }
1079
1080     else if(key == KEY_DC ||
1081             key == '\004') { /* ^D */
1082       delete_char(pattern, &cursor_position);
1083     }
1084
1085     else if(key == KEY_HOME) {
1086       current_focus_line = 0;
1087     }
1088
1089     else if(key == KEY_END) {
1090       current_focus_line = nb_lines - 1;
1091     }
1092
1093     else if(key == KEY_NPAGE) {
1094       motion = 10;
1095     }
1096
1097     else if(key == KEY_PPAGE) {
1098       motion = -10;
1099     }
1100
1101     else if(key == KEY_DOWN ||
1102             key == '\016') { /* ^N */
1103       motion = 1;
1104     }
1105
1106     else if(key == KEY_UP ||
1107             key == '\020') { /* ^P */
1108       motion = -1;
1109     }
1110
1111     else if(key == KEY_LEFT ||
1112             key == '\002') { /* ^B */
1113       if(cursor_position > 0) cursor_position--;
1114       else error_feedback();
1115     }
1116
1117     else if(key == KEY_RIGHT ||
1118             key == '\006') { /* ^F */
1119       if(pattern[cursor_position]) cursor_position++;
1120       else error_feedback();
1121     }
1122
1123     else if(key == '\001') { /* ^A */
1124       cursor_position = 0;
1125     }
1126
1127     else if(key == '\005') { /* ^E */
1128       cursor_position = strlen(pattern);
1129     }
1130
1131     else if(key == '\022') { /* ^R */
1132       use_regexp = !use_regexp;
1133     }
1134
1135     else if(key == '\011') { /* ^I */
1136       case_sensitive = !case_sensitive;
1137     }
1138
1139     else if(key == '\025') { /* ^U */
1140       kill_before_cursor(pattern, &cursor_position);
1141     }
1142
1143     else if(key == '\013') { /* ^K */
1144       kill_after_cursor(pattern, &cursor_position);
1145     }
1146
1147     else if(key == '\014') { /* ^L */
1148       /* I suspect that we may sometime mess up the display, so ^L is
1149          here to force a full refresh */
1150       clear();
1151     }
1152
1153     update_screen(&current_focus_line, &displayed_focus_line,
1154                   motion,
1155                   nb_lines, labels, cursor_position, pattern);
1156
1157   } while(key != '\007' && /* ^G */
1158           key != '\033' && /* ^[ (escape) */
1159           key != '\n' &&
1160           key != KEY_ENTER);
1161
1162   echo();
1163   endwin();
1164
1165   /* Here we come back to standard display */
1166
1167   if((key == KEY_ENTER || key == '\n')) {
1168
1169     char *t;
1170
1171     if(displayed_focus_line >= 0 && displayed_focus_line < nb_lines) {
1172       t = lines[displayed_focus_line];
1173       if(label_separator) {
1174         while(*t && *t != label_separator) t++;
1175         if(*t) t++;
1176       }
1177     } else {
1178       t = 0;
1179     }
1180
1181     if(output_to_vt_buffer && t) {
1182       inject_into_tty_buffer(t, add_control_qs);
1183     }
1184
1185     if(output_filename[0]) {
1186       FILE *out = fopen(output_filename, "w");
1187       if(out) {
1188         if(t) {
1189           fprintf(out, t);
1190         }
1191         fprintf(out, "\n");
1192       } else {
1193         fprintf(stderr,
1194                 "Selector: Can not open %s for writing.\n",
1195                 output_filename);
1196         exit(EXIT_FAILURE);
1197       }
1198       fclose(out);
1199     }
1200
1201   } else {
1202     printf("Aborted.\n");
1203   }
1204
1205   for(l = 0; l < nb_lines; l++) {
1206     free(lines[l]);
1207     free(labels[l]);
1208   }
1209
1210   free(labels);
1211   free(lines);
1212   free(title);
1213
1214   exit(EXIT_SUCCESS);
1215 }