Removed a debug line.
[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   { "input-file", 1, 0, 'f' },
788   { "revert-order", no_argument, 0, 'i' },
789   { "remove-bash-prefix", no_argument, 0, 'b' },
790   { "remove-zsh-prefix", no_argument, 0, 'z' },
791   { "remove-duplicates", no_argument, 0, 'd' },
792   { "regexp", no_argument, 0, 'e' },
793   { "case-sensitive", no_argument, 0, 'a' },
794   { "title", 1, 0, 't' },
795   { "number-of-lines", 1, 0, 'l' },
796   { "colors", 1, 0, 'c' },
797   { "rest-are-files", no_argument, 0, '-' },
798   { "help", no_argument, 0, 'h' },
799   { 0, 0, 0, 0 }
800 };
801
802 int main(int argc, char **argv) {
803
804   char input_filename[BUFFER_SIZE], output_filename[BUFFER_SIZE];
805   char *s;
806   char pattern[BUFFER_SIZE];
807   int c, k, l, n;
808   int cursor_position;
809   int error = 0, show_help = 0;
810   int rest_are_files = 0;
811   int key;
812   int current_focus_line, displayed_focus_line;
813
814   int color_fg_modeline, color_bg_modeline;
815   int color_fg_highlight, color_bg_highlight;
816
817   char **lines, **labels;
818   int nb_lines;
819   struct hash_table_t *hash_table;
820
821   if(!isatty(STDIN_FILENO)) {
822     fprintf(stderr, "Selector: The standard input is not a tty.\n");
823     exit(EXIT_FAILURE);
824   }
825
826   color_fg_modeline  = COLOR_WHITE;
827   color_bg_modeline  = COLOR_BLACK;
828   color_fg_highlight = COLOR_BLACK;
829   color_bg_highlight = COLOR_YELLOW;
830
831   setlocale(LC_ALL, "");
832
833   strcpy(input_filename, "");
834   strcpy(output_filename, "");
835
836   while (!rest_are_files &&
837          (c = getopt_long(argc, argv, "o:s:x:vwmqf:ibzdeat:l:c:-h",
838                           long_options, NULL)) != -1) {
839
840     switch(c) {
841
842     case 'o':
843       strncpy(output_filename, optarg, BUFFER_SIZE);
844       break;
845
846     case 's':
847       pattern_separator = optarg[0];
848       break;
849
850     case 'x':
851       label_separator = optarg[0];
852       break;
853
854     case 'v':
855       output_to_vt_buffer = 1;
856       break;
857
858     case 'w':
859       add_control_qs = 1;
860       break;
861
862     case 'm':
863       with_colors = 0;
864       break;
865
866     case 'q':
867       error_flash = 1;
868       break;
869
870     case 'f':
871       strncpy(input_filename, optarg, BUFFER_SIZE);
872       break;
873
874     case 'i':
875       inverse_order = 1;
876       break;
877
878     case 'b':
879       bash_history = 1;
880       break;
881
882     case 'z':
883       zsh_history = 1;
884       break;
885
886     case 'd':
887       remove_duplicates = 1;
888       break;
889
890     case 'e':
891       use_regexp = 1;
892       break;
893
894     case 'a':
895       case_sensitive = 1;
896       break;
897
898     case 't':
899       free(title);
900       title = safe_malloc((strlen(optarg) + 1) * sizeof(char));
901       strcpy(title, optarg);
902       break;
903
904     case 'l':
905       nb_lines_max = string_to_positive_integer(optarg);
906       break;
907
908     case 'c':
909       s = optarg;
910       color_fg_modeline = string_to_positive_integer(s);
911       while(*s && *s != ',') s++; if(*s == ',') { s++; }
912       color_bg_modeline = string_to_positive_integer(s);
913       while(*s && *s != ',') s++; if(*s == ',') { s++; }
914       color_fg_highlight = string_to_positive_integer(s);
915       while(*s && *s != ',') s++; if(*s == ',') { s++; }
916       color_bg_highlight = string_to_positive_integer(s);
917       break;
918
919     case '-':
920       rest_are_files = 1;
921       break;
922
923     case 'h':
924       show_help = 1;
925       break;
926
927     default:
928       error = 1;
929       break;
930     }
931   }
932
933   if(show_help || error) {
934     if(error) {
935       print_help(stderr);
936       exit(EXIT_FAILURE);
937     } else {
938       print_help(stdout);
939       exit(EXIT_SUCCESS);
940     }
941   }
942
943   lines = safe_malloc(nb_lines_max * sizeof(char *));
944
945   nb_lines = 0;
946
947   if(remove_duplicates) {
948     hash_table = new_hash_table(nb_lines_max * 10);
949   } else {
950     hash_table = 0;
951   }
952
953   if(input_filename[0]) {
954     read_file(hash_table,
955               input_filename,
956               nb_lines_max, &nb_lines, lines);
957   }
958
959   while(optind < argc) {
960     read_file(hash_table,
961               argv[optind],
962               nb_lines_max, &nb_lines, lines);
963     optind++;
964   }
965
966   if(hash_table) {
967     free_hash_table(hash_table);
968   }
969
970   /* Now remove the null strings */
971
972   n = 0;
973   for(k = 0; k < nb_lines; k++) {
974     if(lines[k]) {
975       lines[n++] = lines[k];
976     }
977   }
978
979   nb_lines = n;
980
981   if(inverse_order) {
982     for(l = 0; l < nb_lines / 2; l++) {
983       char *s = lines[nb_lines - 1 - l];
984       lines[nb_lines - 1 - l] = lines[l];
985       lines[l] = s;
986     }
987   }
988
989   /* Build the labels from the strings, take only the part before the
990      label_separator and transform control characters to printable
991      ones */
992
993   labels = safe_malloc(nb_lines * sizeof(char *));
994
995   for(l = 0; l < nb_lines; l++) {
996     char *s, *t;
997     int e = 0;
998     const char *u;
999     t = lines[l];
1000
1001     while(*t && *t != label_separator) {
1002       u = unctrl(*t++);
1003       e += strlen(u);
1004     }
1005
1006     labels[l] = safe_malloc((e + 1) * sizeof(char));
1007     t = lines[l];
1008     s = labels[l];
1009     while(*t && *t != label_separator) {
1010       u = unctrl(*t++);
1011       while(*u) { *s++ = *u++; }
1012     }
1013     *s = '\0';
1014   }
1015
1016   pattern[0] = '\0';
1017
1018   cursor_position = 0;
1019
1020   /* Here we start to display with curse */
1021
1022   initscr();
1023   cbreak();
1024   noecho();
1025   intrflush(stdscr, FALSE);
1026
1027   /* So that the arrow keys work */
1028   keypad(stdscr, TRUE);
1029
1030   attr_error = A_STANDOUT;
1031   attr_modeline = A_REVERSE;
1032   attr_focus_line = A_STANDOUT;
1033
1034   if(with_colors && has_colors()) {
1035
1036     start_color();
1037
1038     if(color_fg_modeline < 0  || color_fg_modeline >= COLORS ||
1039        color_bg_modeline < 0  || color_bg_modeline >= COLORS ||
1040        color_fg_highlight < 0 || color_bg_highlight >= COLORS ||
1041        color_bg_highlight < 0 || color_bg_highlight >= COLORS) {
1042       echo();
1043       endwin();
1044       fprintf(stderr, "Selector: Color numbers have to be between 0 and %d.\n",
1045               COLORS - 1);
1046       exit(EXIT_FAILURE);
1047     }
1048
1049     init_pair(1, color_fg_modeline, color_bg_modeline);
1050     attr_modeline = COLOR_PAIR(1);
1051
1052     init_pair(2, color_fg_highlight, color_bg_highlight);
1053     attr_focus_line = COLOR_PAIR(2);
1054
1055     init_pair(3, COLOR_WHITE, COLOR_RED);
1056     attr_error = COLOR_PAIR(3);
1057
1058   }
1059
1060   current_focus_line = 0;
1061   displayed_focus_line = 0;
1062
1063   update_screen(&current_focus_line, &displayed_focus_line,
1064                 0,
1065                 nb_lines, labels, cursor_position, pattern);
1066
1067   do {
1068     int motion = 0;
1069
1070     key = getch();
1071
1072     if(key >= ' ' && key <= '~') { /* Insert character */
1073       insert_char(pattern, &cursor_position, key);
1074     }
1075
1076     else if(key == KEY_BACKSPACE ||
1077             key == '\010' || /* ^H */
1078             key == '\177') { /* ^? */
1079       backspace_char(pattern, &cursor_position);
1080     }
1081
1082     else if(key == KEY_DC ||
1083             key == '\004') { /* ^D */
1084       delete_char(pattern, &cursor_position);
1085     }
1086
1087     else if(key == KEY_HOME) {
1088       current_focus_line = 0;
1089     }
1090
1091     else if(key == KEY_END) {
1092       current_focus_line = nb_lines - 1;
1093     }
1094
1095     else if(key == KEY_NPAGE) {
1096       motion = 10;
1097     }
1098
1099     else if(key == KEY_PPAGE) {
1100       motion = -10;
1101     }
1102
1103     else if(key == KEY_DOWN ||
1104             key == '\016') { /* ^N */
1105       motion = 1;
1106     }
1107
1108     else if(key == KEY_UP ||
1109             key == '\020') { /* ^P */
1110       motion = -1;
1111     }
1112
1113     else if(key == KEY_LEFT ||
1114             key == '\002') { /* ^B */
1115       if(cursor_position > 0) cursor_position--;
1116       else error_feedback();
1117     }
1118
1119     else if(key == KEY_RIGHT ||
1120             key == '\006') { /* ^F */
1121       if(pattern[cursor_position]) cursor_position++;
1122       else error_feedback();
1123     }
1124
1125     else if(key == '\001') { /* ^A */
1126       cursor_position = 0;
1127     }
1128
1129     else if(key == '\005') { /* ^E */
1130       cursor_position = strlen(pattern);
1131     }
1132
1133     else if(key == '\022') { /* ^R */
1134       use_regexp = !use_regexp;
1135     }
1136
1137     else if(key == '\011') { /* ^I */
1138       case_sensitive = !case_sensitive;
1139     }
1140
1141     else if(key == '\025') { /* ^U */
1142       kill_before_cursor(pattern, &cursor_position);
1143     }
1144
1145     else if(key == '\013') { /* ^K */
1146       kill_after_cursor(pattern, &cursor_position);
1147     }
1148
1149     else if(key == '\014') { /* ^L */
1150       /* I suspect that we may sometime mess up the display, so ^L is
1151          here to force a full refresh */
1152       clear();
1153     }
1154
1155     update_screen(&current_focus_line, &displayed_focus_line,
1156                   motion,
1157                   nb_lines, labels, cursor_position, pattern);
1158
1159   } while(key != '\007' && /* ^G */
1160           key != '\033' && /* ^[ (escape) */
1161           key != '\n' &&
1162           key != KEY_ENTER);
1163
1164   echo();
1165   endwin();
1166
1167   /* Here we come back to standard display */
1168
1169   if((key == KEY_ENTER || key == '\n')) {
1170
1171     char *t;
1172
1173     if(displayed_focus_line >= 0 && displayed_focus_line < nb_lines) {
1174       t = lines[displayed_focus_line];
1175       if(label_separator) {
1176         while(*t && *t != label_separator) t++;
1177         if(*t) t++;
1178       }
1179     } else {
1180       t = 0;
1181     }
1182
1183     if(output_to_vt_buffer && t) {
1184       inject_into_tty_buffer(t, add_control_qs);
1185     }
1186
1187     if(output_filename[0]) {
1188       FILE *out = fopen(output_filename, "w");
1189       if(out) {
1190         if(t) {
1191           fprintf(out, t);
1192         }
1193         fprintf(out, "\n");
1194       } else {
1195         fprintf(stderr,
1196                 "Selector: Can not open %s for writing.\n",
1197                 output_filename);
1198         exit(EXIT_FAILURE);
1199       }
1200       fclose(out);
1201     }
1202
1203   } else {
1204     printf("Aborted.\n");
1205   }
1206
1207   for(l = 0; l < nb_lines; l++) {
1208     free(lines[l]);
1209     free(labels[l]);
1210   }
1211
1212   free(labels);
1213   free(lines);
1214   free(title);
1215
1216   exit(EXIT_SUCCESS);
1217 }