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