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