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