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