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