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