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