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