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