Cosmetics.
[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 add_and_get_previous_index(const 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 store_line(const char *t,
576                 int nb_lines_max, int *nb_lines, char **lines,
577                 int hash_table_size, int *hash_table) {
578
579   /* Remove the zsh history prefix */
580
581   if(zsh_history && *t == ':') {
582     while(*t && *t != ';') t++;
583     if(*t == ';') t++;
584   }
585
586   /* Remove the bash history prefix */
587
588   if(bash_history) {
589     while(*t == ' ') t++;
590     while(*t >= '0' && *t <= '9') t++;
591     while(*t == ' ') t++;
592   }
593
594   /* Check for duplicates with the hash table and insert the line in
595      the list if necessary */
596
597   int dup;
598
599   if(hash_table) {
600     dup = add_and_get_previous_index(t, *nb_lines, lines, hash_table, hash_table_size);
601   } else {
602     dup = -1;
603   }
604
605   if(dup < 0) {
606     lines[*nb_lines] = (char *) malloc((strlen(t) + 1) * sizeof(char));
607     strcpy(lines[*nb_lines], t);
608   } else {
609     /* The string was already in there, so we do not allocate a new
610        string but use the pointer to the first occurence of it */
611     lines[*nb_lines] = lines[dup];
612     lines[dup] = 0;
613   }
614
615   (*nb_lines)++;
616 }
617
618 void read_file(const char *input_filename,
619                int nb_lines_max, int *nb_lines, char **lines,
620                int hash_table_size, int *hash_table) {
621
622   char raw_line[buffer_size];
623
624   FILE *file = fopen(input_filename, "r");
625
626   if(!file) {
627     fprintf(stderr, "Can not open `%s'.\n", input_filename);
628     exit(1);
629   }
630
631   int start = 0, end = 0, k;
632
633   while(*nb_lines < nb_lines_max && (end > start || !feof(file))) {
634     int eol = start;
635     while(eol < end && raw_line[eol] != '\n') eol++;
636
637     if(eol == end) {
638       for(k = 0; k < end - start; k++) {
639         raw_line[k] = raw_line[k + start];
640       }
641       end -= start;
642       eol -= start;
643       start = 0;
644       end += fread(raw_line + end, sizeof(char), buffer_size - end, file);
645       while(eol < end && raw_line[eol] != '\n') eol++;
646     }
647
648     if(eol == buffer_size) {
649       raw_line[buffer_size - 1] = '\0';
650       fprintf(stderr, "Line too long (max is %d characters):\n", buffer_size);
651       fprintf(stderr, raw_line);
652       fprintf(stderr, "\n");
653       exit(1);
654     }
655
656     raw_line[eol] = '\0';
657
658     store_line(raw_line + start,
659                nb_lines_max, nb_lines, lines,
660                hash_table_size, hash_table);
661
662     start = eol + 1;
663   }
664
665   fclose(file);
666 }
667
668 /*********************************************************************/
669
670 int main(int argc, char **argv) {
671
672   if(!ttyname(STDIN_FILENO)) {
673     fprintf(stderr, "The standard input is not a tty.\n");
674     exit(1);
675   }
676
677   char input_filename[buffer_size], output_filename[buffer_size];
678   int i, k, l, n;
679   int error = 0, show_help = 0;
680   int rest_are_files = 0;
681
682   int color_fg_modeline, color_bg_modeline;
683   int color_fg_highlight, color_bg_highlight;
684
685   color_fg_modeline  = COLOR_WHITE;
686   color_bg_modeline  = COLOR_BLACK;
687   color_fg_highlight = COLOR_BLACK;
688   color_bg_highlight = COLOR_YELLOW;
689
690   setlocale(LC_ALL, "");
691
692   strcpy(input_filename, "");
693   strcpy(output_filename, "");
694
695   i = 1;
696   while(!error && !show_help && i < argc && argv[i][0] == '-' && !rest_are_files) {
697
698     if(strcmp(argv[i], "-o") == 0) {
699       check_opt(argc, argv, i, 1, "<output filename>");
700       strncpy(output_filename, argv[i+1], buffer_size);
701       i += 2;
702     }
703
704     else if(strcmp(argv[i], "-s") == 0) {
705       check_opt(argc, argv, i, 1, "<pattern separator>");
706       pattern_separator = argv[i+1][0];
707       i += 2;
708     }
709
710     else if(strcmp(argv[i], "-x") == 0) {
711       check_opt(argc, argv, i, 1, "<label separator>");
712       label_separator = argv[i+1][0];
713       i += 2;
714     }
715
716     else if(strcmp(argv[i], "-v") == 0) {
717       output_to_vt_buffer = 1;
718       i++;
719     }
720
721     else if(strcmp(argv[i], "-w") == 0) {
722       add_control_qs = 1;
723       i++;
724     }
725
726     else if(strcmp(argv[i], "-m") == 0) {
727       with_colors = 0;
728       i++;
729     }
730
731     else if(strcmp(argv[i], "-q") == 0) {
732       error_flash = 1;
733       i++;
734     }
735
736     else if(strcmp(argv[i], "-f") == 0) {
737       check_opt(argc, argv, i, 1, "<input filename>");
738       strncpy(input_filename, argv[i+1], buffer_size);
739       i += 2;
740     }
741
742     else if(strcmp(argv[i], "-i") == 0) {
743       inverse_order = 1;
744       i++;
745     }
746
747     else if(strcmp(argv[i], "-b") == 0) {
748       bash_history = 1;
749       i++;
750     }
751
752     else if(strcmp(argv[i], "-z") == 0) {
753       zsh_history = 1;
754       i++;
755     }
756
757     else if(strcmp(argv[i], "-d") == 0) {
758       remove_duplicates = 1;
759       i++;
760     }
761
762     else if(strcmp(argv[i], "-e") == 0) {
763       use_regexp = 1;
764       i++;
765     }
766
767     else if(strcmp(argv[i], "-a") == 0) {
768       case_sensitive = 1;
769       i++;
770     }
771
772     else if(strcmp(argv[i], "-t") == 0) {
773       check_opt(argc, argv, i, 1, "<title>");
774       free(title);
775       title = (char *) malloc((strlen(argv[i+1]) + 1) * sizeof(char));
776       strcpy(title, argv[i+1]);
777       i += 2;
778     }
779
780     else if(strcmp(argv[i], "-l") == 0) {
781       check_opt(argc, argv, i, 1, "<maximum number of lines>");
782       nb_lines_max = string_to_positive_integer(argv[i+1]);
783       i += 2;
784     }
785
786     else if(strcmp(argv[i], "-c") == 0) {
787       check_opt(argc, argv, i, 4, "<fg modeline> <bg modeline> <fg highlight> <bg highlight>");
788       color_fg_modeline = string_to_positive_integer(argv[i + 1]);
789       color_bg_modeline = string_to_positive_integer(argv[i + 2]);
790       color_fg_highlight = string_to_positive_integer(argv[i + 3]);
791       color_bg_highlight = string_to_positive_integer(argv[i + 4]);
792       i += 5;
793     }
794
795     else if(strcmp(argv[i], "--") == 0) {
796       rest_are_files = 1;
797       i++;
798     }
799
800     else if(strcmp(argv[i], "-h") == 0) {
801       show_help = 1;
802       i++;
803     }
804
805     else {
806       fprintf(stderr, "Unknown option %s.\n", argv[i]);
807       error = 1;
808     }
809   }
810
811   if(show_help || error) {
812     fprintf(stderr, "Selector version %s-R%s\n", VERSION, REVISION_NUMBER);
813     fprintf(stderr, "Written by Francois Fleuret <francois@fleuret.org>.\n");
814     fprintf(stderr, "Usage: %s [options] [<filename1> [<filename2> ...]]\n", argv[0]);
815     fprintf(stderr, "\n");
816     fprintf(stderr, " -h      show this help\n");
817     fprintf(stderr, " -v      inject the selected line in the tty\n");
818     fprintf(stderr, " -w      quote control characters with ^Qs when using -v\n");
819     fprintf(stderr, " -d      remove duplicated lines\n");
820     fprintf(stderr, " -b      remove the bash history line prefix\n");
821     fprintf(stderr, " -z      remove the zsh history line prefix\n");
822     fprintf(stderr, " -i      invert the order of lines\n");
823     fprintf(stderr, " -e      start in regexp mode\n");
824     fprintf(stderr, " -a      start in case sensitive mode\n");
825     fprintf(stderr, " -m      monochrome mode\n");
826     fprintf(stderr, " -q      make a flash instead of a beep on an edition error\n");
827     fprintf(stderr, " --      all following arguments are filenames\n");
828     fprintf(stderr, " -t <title>\n");
829     fprintf(stderr, "         add a title in the modeline\n");
830     fprintf(stderr, " -c <fg modeline> <bg modeline> <fg highlight> <bg highlight>\n");
831     fprintf(stderr, "         set the display colors\n");
832     fprintf(stderr, " -o <output filename>\n");
833     fprintf(stderr, "         set a file to write the selected line to\n");
834     fprintf(stderr, " -s <pattern separator>\n");
835     fprintf(stderr, "         set the symbol to separate substrings in the pattern\n");
836     fprintf(stderr, " -x <label separator>\n");
837     fprintf(stderr, "         set the symbol to terminate the label\n");
838     fprintf(stderr, " -l <max number of lines>\n");
839     fprintf(stderr, "         set the maximum number of lines to take into account\n");
840     fprintf(stderr, "\n");
841     exit(error);
842   }
843
844   char **lines = (char **) malloc(nb_lines_max * sizeof(char *));
845
846   int nb_lines = 0;
847   int hash_table_size = nb_lines_max * 10;
848   int *hash_table = 0;
849
850   if(remove_duplicates) {
851     hash_table = new_hash_table(hash_table_size);
852   }
853
854   if(input_filename[0]) {
855     read_file(input_filename,
856               nb_lines_max, &nb_lines, lines,
857               hash_table_size, hash_table);
858   }
859
860   while(i < argc) {
861     read_file(argv[i],
862               nb_lines_max, &nb_lines, lines,
863               hash_table_size, hash_table);
864     i++;
865   }
866
867   free(hash_table);
868
869   /* Now remove the null strings */
870
871   n = 0;
872   for(k = 0; k < nb_lines; k++) {
873     if(lines[k]) {
874       lines[n++] = lines[k];
875     }
876   }
877
878   nb_lines = n;
879
880   if(inverse_order) {
881     for(i = 0; i < nb_lines / 2; i++) {
882       char *s = lines[nb_lines - 1 - i];
883       lines[nb_lines - 1 - i] = lines[i];
884       lines[i] = s;
885     }
886   }
887
888   /* Build the labels from the strings, take only the part before the
889      label_separator and transform control characters to printable
890      ones */
891
892   char **labels = (char **) malloc(nb_lines * sizeof(char *));
893   for(l = 0; l < nb_lines; l++) {
894     char *s, *t;
895     const char *u;
896     t = lines[l];
897     int e = 0;
898     while(*t && *t != label_separator) {
899       u = unctrl(*t++);
900       e += strlen(u);
901     }
902     labels[l] = (char *) malloc((e + 1) * sizeof(char));
903     t = lines[l];
904     s = labels[l];
905     while(*t && *t != label_separator) {
906       u = unctrl(*t++);
907       while(*u) { *s++ = *u++; }
908     }
909     *s = '\0';
910   }
911
912   char pattern[buffer_size];
913   pattern[0] = '\0';
914
915   int cursor_position;
916   cursor_position = 0;
917
918   /* Here we start to display with curse */
919
920   initscr();
921   cbreak();
922   noecho();
923   /* nonl(); */
924   intrflush(stdscr, FALSE);
925
926   /* So that the arrow keys work */
927   keypad(stdscr, TRUE);
928
929   attr_error = A_STANDOUT;
930   attr_modeline = A_REVERSE;
931   attr_focus_line = A_STANDOUT;
932
933   if(with_colors && has_colors()) {
934
935     start_color();
936
937     if(color_fg_modeline < 0  || color_fg_modeline >= COLORS ||
938        color_bg_modeline < 0  || color_bg_modeline >= COLORS ||
939        color_fg_highlight < 0 || color_bg_highlight >= COLORS ||
940        color_bg_highlight < 0 || color_bg_highlight >= COLORS) {
941       echo();
942       endwin();
943       fprintf(stderr, "Color numbers have to be between 0 and %d.\n", COLORS - 1);
944       exit(1);
945     }
946
947     init_pair(1, color_fg_modeline, color_bg_modeline);
948     attr_modeline = COLOR_PAIR(1);
949
950     init_pair(2, color_fg_highlight, color_bg_highlight);
951     attr_focus_line = COLOR_PAIR(2);
952
953     init_pair(3, COLOR_WHITE, COLOR_RED);
954     attr_error = COLOR_PAIR(3);
955
956   }
957
958   int key;
959   int current_focus_line = 0, displayed_focus_line = 0;
960
961   update_screen(&current_focus_line, &displayed_focus_line,
962                 0,
963                 nb_lines, labels, cursor_position, pattern);
964
965   do {
966
967     key = getch();
968
969     int motion = 0;
970
971     if(key >= ' ' && key <= '~') { /* Insert character */
972       insert_char(pattern, &cursor_position, key);
973     }
974
975     else if(key == KEY_BACKSPACE ||
976             key == '\010' || /* ^H */
977             key == '\177') { /* ^? */
978       backspace_char(pattern, &cursor_position);
979     }
980
981     else if(key == KEY_DC ||
982             key == '\004') { /* ^D */
983       delete_char(pattern, &cursor_position);
984     }
985
986     else if(key == KEY_HOME) {
987       current_focus_line = 0;
988     }
989
990     else if(key == KEY_END) {
991       current_focus_line = nb_lines - 1;
992     }
993
994     else if(key == KEY_NPAGE) {
995       motion = 10;
996     }
997
998     else if(key == KEY_PPAGE) {
999       motion = -10;
1000     }
1001
1002     else if(key == KEY_DOWN ||
1003             key == '\016') { /* ^N */
1004       motion = 1;
1005     }
1006
1007     else if(key == KEY_UP ||
1008             key == '\020') { /* ^P */
1009       motion = -1;
1010     }
1011
1012     else if(key == KEY_LEFT ||
1013             key == '\002') { /* ^B */
1014       if(cursor_position > 0) cursor_position--;
1015       else error_feedback();
1016     }
1017
1018     else if(key == KEY_RIGHT ||
1019             key == '\006') { /* ^F */
1020       if(pattern[cursor_position]) cursor_position++;
1021       else error_feedback();
1022     }
1023
1024     else if(key == '\001') { /* ^A */
1025       cursor_position = 0;
1026     }
1027
1028     else if(key == '\005') { /* ^E */
1029       cursor_position = strlen(pattern);
1030     }
1031
1032     else if(key == '\022') { /* ^R */
1033       use_regexp = !use_regexp;
1034     }
1035
1036     else if(key == '\011') { /* ^I */
1037       case_sensitive = !case_sensitive;
1038     }
1039
1040     else if(key == '\025') { /* ^U */
1041       kill_before_cursor(pattern, &cursor_position);
1042     }
1043
1044     else if(key == '\013') { /* ^K */
1045       kill_after_cursor(pattern, &cursor_position);
1046     }
1047
1048     else if(key == '\014') { /* ^L */
1049       /* I suspect that we may sometime mess up the display */
1050       clear();
1051     }
1052
1053     update_screen(&current_focus_line, &displayed_focus_line,
1054                   motion,
1055                   nb_lines, labels, cursor_position, pattern);
1056
1057   } while(key != '\007' && /* ^G */
1058           key != '\033' && /* ^[ (escape) */
1059           key != '\n' &&
1060           key != KEY_ENTER);
1061
1062   echo();
1063   endwin();
1064
1065   /* Here we come back to standard display */
1066
1067   if((key == KEY_ENTER || key == '\n')) {
1068
1069     char *t;
1070
1071     if(displayed_focus_line >= 0 && displayed_focus_line < nb_lines) {
1072       t = lines[displayed_focus_line];
1073       if(label_separator) {
1074         while(*t && *t != label_separator) t++;
1075         if(*t) t++;
1076       }
1077     } else {
1078       t = 0;
1079     }
1080
1081     if(output_to_vt_buffer && t) {
1082       inject_into_tty_buffer(t);
1083     }
1084
1085     if(output_filename[0]) {
1086       FILE *out = fopen(output_filename, "w");
1087       if(out) {
1088         if(t) {
1089           fprintf(out, t);
1090         }
1091         fprintf(out, "\n");
1092       } else {
1093         fprintf(stderr, "Can not open %s for writing.\n", output_filename);
1094         exit(1);
1095       }
1096       fclose(out);
1097     }
1098
1099   } else {
1100     printf("Aborted.\n");
1101   }
1102
1103   for(l = 0; l < nb_lines; l++) {
1104     free(lines[l]);
1105     free(labels[l]);
1106   }
1107
1108   free(labels);
1109   free(lines);
1110   free(title);
1111
1112   exit(0);
1113 }