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