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