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