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