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