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