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