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