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