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