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