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