3 * selector is a simple command line utility for selection of strings
4 * with a dynamic pattern-matching.
6 * Copyright (c) 2009, 2010, 2011 Francois Fleuret
7 * Written by Francois Fleuret <francois@fleuret.org>
9 * This file is part of selector.
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.
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.
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/>.
27 To use it as a super-history-search for bash:
28 selector --bash <(history)
41 #include <sys/ioctl.h>
48 #define VERSION "1.1.4"
50 #define BUFFER_SIZE 4096
52 /* Yeah, global variables! */
54 int nb_lines_max = 1000;
55 char pattern_separator = ';';
56 char label_separator = '\0';
57 int output_to_vt_buffer = 0;
58 int add_control_qs = 0;
62 int inverse_order = 0;
63 int remove_duplicates = 0;
65 int case_sensitive = 0;
68 int exclamation_negates = 0;
69 int upper_caps_makes_case_sensitive = 0;
71 int attr_modeline, attr_focus_line, attr_error;
73 /********************************************************************/
75 /* malloc with error checking. */
77 void *safe_malloc(size_t n) {
80 printf("Can not allocate memory: %s\n", strerror(errno));
86 /*********************************************************************/
88 void inject_into_tty_buffer(char *string, int add_control_qs) {
89 struct termios oldtio, newtio;
91 const char control_q = '\021';
92 tcgetattr(STDIN_FILENO, &oldtio);
93 memset(&newtio, 0, sizeof(newtio));
94 /* Set input mode (non-canonical, *no echo*,...) */
95 tcsetattr(STDIN_FILENO, TCSANOW, &newtio);
96 /* Put the selected string in the tty input buffer */
97 for(k = string; *k; k++) {
98 if(add_control_qs && !(*k >= ' ' && *k <= '~')) {
99 /* Add ^Q to quote control characters */
100 ioctl(STDIN_FILENO, TIOCSTI, &control_q);
102 ioctl(STDIN_FILENO, TIOCSTI, k);
104 /* Restore the old settings */
105 tcsetattr(STDIN_FILENO, TCSANOW, &oldtio);
108 /*********************************************************************/
110 void str_to_positive_integers(char *string, int *values, int nb) {
111 int current_value, gotone;
121 if(*s >= '0' && *s <= '9') {
122 current_value = current_value * 10 + (int) (*s - '0');
124 } else if(*s == ',' || *s == '\0') {
127 values[n++] = current_value;
133 "selector: Missing value in `%s'.\n", string);
141 "selector: Too many values in `%s'.\n", string);
146 "selector: Empty value in `%s'.\n", string);
151 "selector: Syntax error in `%s'.\n", string);
158 void error_feedback() {
166 void usage(FILE *out) {
168 fprintf(out, "Selector version %s (%s)\n", VERSION, UNAME);
169 fprintf(out, "Written by Francois Fleuret <francois@fleuret.org>.\n");
171 fprintf(out, "Usage: selector [options] [<filename1> [<filename2> ...]]\n");
173 fprintf(out, " -h, --help\n");
174 fprintf(out, " show this help\n");
175 fprintf(out, " -v, --inject-in-tty\n");
176 fprintf(out, " inject the selected line in the tty\n");
177 fprintf(out, " -w, --add-control-qs\n");
178 fprintf(out, " quote control characters with ^Qs when using -v\n");
179 fprintf(out, " -d, --remove-duplicates\n");
180 fprintf(out, " remove duplicated lines\n");
181 fprintf(out, " -b, --remove-bash-prefix\n");
182 fprintf(out, " remove the bash history line prefix\n");
183 fprintf(out, " -z, --remove-zsh-prefix\n");
184 fprintf(out, " remove the zsh history line prefix\n");
185 fprintf(out, " -i, --revert-order\n");
186 fprintf(out, " invert the order of lines\n");
187 fprintf(out, " -e, --regexp\n");
188 fprintf(out, " start in regexp mode\n");
189 fprintf(out, " -a, --case-sensitive\n");
190 fprintf(out, " start in case sensitive mode\n");
191 fprintf(out, " -u, --upper-case-makes-case-sensitive\n");
192 fprintf(out, " using an upper case character in the matching string makes\n");
193 fprintf(out, " the matching case-sensitive\n");
194 fprintf(out, " -n, --exclamation-negates\n");
195 fprintf(out, " substrings starting with an exclamation mark have to be absent\n");
196 fprintf(out, " -m, --monochrome\n");
197 fprintf(out, " monochrome mode\n");
198 fprintf(out, " -q, --no-beep\n");
199 fprintf(out, " make a flash instead of a beep on an edition error\n");
200 fprintf(out, " --bash\n");
201 fprintf(out, " setting for bash history search, same as -b -i -d -v -w -l ${HISTSIZE}\n");
202 fprintf(out, " -- all following arguments are filenames\n");
203 fprintf(out, " -t <title>, --title <title>\n");
204 fprintf(out, " add a title in the modeline\n");
205 fprintf(out, " -c <colors>, --colors <colors>\n");
206 fprintf(out, " set the display colors with an argument of the form\n");
207 fprintf(out, " <fg_modeline>,<bg_modeline>,<fg_highlight>,<bg_highlight>\n");
208 fprintf(out, " -o <output filename>, --output-file <output filename>\n");
209 fprintf(out, " set a file to write the selected line to\n");
210 fprintf(out, " -s <pattern separator>, --pattern-separator <pattern separator>\n");
211 fprintf(out, " set the symbol to separate substrings in the pattern\n");
212 fprintf(out, " -x <label separator>, --label-separator <label separator>\n");
213 fprintf(out, " set the symbol to terminate the label\n");
214 fprintf(out, " -l <max number of lines>, --number-of-lines <max number of lines>\n");
215 fprintf(out, " set the maximum number of lines to take into account\n");
219 /*********************************************************************/
221 /* A quick and dirty hash table */
223 #define MAGIC_HASH_MULTIPLIER 387433
225 /* The table itself stores indexes of the strings taken in a char**
226 table. When a string is added, if it was already in the table, the
227 new index replaces the previous one. */
229 struct hash_table_t {
234 struct hash_table_t *new_hash_table(int size) {
236 struct hash_table_t *hash_table;
238 hash_table = safe_malloc(sizeof(struct hash_table_t));
240 hash_table->size = size;
241 hash_table->entries = safe_malloc(hash_table->size * sizeof(int));
243 for(k = 0; k < hash_table->size; k++) {
244 hash_table->entries[k] = -1;
250 void free_hash_table(struct hash_table_t *hash_table) {
251 free(hash_table->entries);
255 /* Adds new_string in the table, associated to new_index. If this
256 string was not already in the table, returns -1. Otherwise, returns
257 the previous index it had. */
259 int add_and_get_previous_index(struct hash_table_t *hash_table,
260 const char *new_string, int new_index,
263 unsigned int code = 0, start;
266 /* This is my recipe. I checked, it seems to work (as long as
267 hash_table->size is not a multiple of MAGIC_HASH_MULTIPLIER that
270 for(k = 0; new_string[k]; k++) {
271 code = code * MAGIC_HASH_MULTIPLIER + (unsigned int) (new_string[k]);
274 code = code % hash_table->size;
277 while(hash_table->entries[code] >= 0) {
278 /* There is a string with that code */
279 if(strcmp(new_string, strings[hash_table->entries[code]]) == 0) {
280 /* It is the same string, we keep a copy of the stored index */
281 int result = hash_table->entries[code];
282 /* Put the new one */
283 hash_table->entries[code] = new_index;
284 /* And return the previous one */
287 /* This collision was not the same string, let's move to the next
289 code = (code + 1) % hash_table->size;
290 /* We came back to our original code, which means that the table
293 printf("Full hash table (that should not happen)\n");
298 /* This string was not already in there, store the index in the
299 table and return -1 */
301 hash_table->entries[code] = new_index;
305 /*********************************************************************
306 A matcher matches either with a collection of substrings, or with a
314 char *splitted_patterns, **patterns;
317 int match(struct matcher *matcher, char *string) {
319 if(matcher->nb_patterns >= 0) {
320 if(matcher->case_sensitive) {
321 if(exclamation_negates) {
322 for(n = 0; n < matcher->nb_patterns; n++) {
323 if(matcher->patterns[n][0] == '!') {
324 if(strstr(string, matcher->patterns[n] + 1) != 0) return 0;
326 if(strstr(string, matcher->patterns[n]) == 0) return 0;
330 for(n = 0; n < matcher->nb_patterns; n++) {
331 if(strstr(string, matcher->patterns[n]) == 0) return 0;
335 if(exclamation_negates) {
336 for(n = 0; n < matcher->nb_patterns; n++) {
337 if(matcher->patterns[n][0] == '!') {
338 if(strcasestr(string, matcher->patterns[n] + 1) != 0) return 0;
340 if(strcasestr(string, matcher->patterns[n]) == 0) return 0;
344 for(n = 0; n < matcher->nb_patterns; n++) {
345 if(strcasestr(string, matcher->patterns[n]) == 0) return 0;
351 return regexec(&matcher->preg, string, 0, 0, 0) == 0;
355 void free_matcher(struct matcher *matcher) {
356 if(matcher->nb_patterns < 0) {
357 if(!matcher->regexp_error) regfree(&matcher->preg);
359 free(matcher->splitted_patterns);
360 free(matcher->patterns);
364 void initialize_matcher(struct matcher *matcher,
365 int use_regexp, int case_sensitive,
366 const char *pattern) {
368 char *t, *last_pattern_start;
372 matcher->case_sensitive = case_sensitive;
373 matcher->nb_patterns = -1;
374 matcher->regexp_error = regcomp(&matcher->preg, pattern,
375 case_sensitive ? 0 : REG_ICASE);
377 matcher->regexp_error = 0;
378 matcher->nb_patterns = 1;
380 if(upper_caps_makes_case_sensitive) {
381 for(s = pattern; *s && !case_sensitive; s++) {
382 case_sensitive = (*s >= 'A' && *s <= 'Z');
386 matcher->case_sensitive = case_sensitive;
388 for(s = pattern; *s; s++) {
389 if(*s == pattern_separator) {
390 matcher->nb_patterns++;
394 matcher->splitted_patterns =
395 safe_malloc((strlen(pattern) + 1) * sizeof(char));
398 safe_malloc(matcher->nb_patterns * sizeof(char *));
400 strcpy(matcher->splitted_patterns, pattern);
403 last_pattern_start = matcher->splitted_patterns;
404 for(t = matcher->splitted_patterns; n < matcher->nb_patterns; t++) {
405 if(*t == pattern_separator || *t == '\0') {
407 matcher->patterns[n++] = last_pattern_start;
408 last_pattern_start = t + 1;
414 /*********************************************************************
417 void delete_char(char *buffer, int *position) {
418 if(buffer[*position]) {
420 while(c < BUFFER_SIZE && buffer[c]) {
421 buffer[c] = buffer[c+1];
424 } else error_feedback();
427 void backspace_char(char *buffer, int *position) {
429 if(buffer[*position]) {
430 int c = *position - 1;
432 buffer[c] = buffer[c+1];
436 buffer[*position - 1] = '\0';
440 } else error_feedback();
443 void insert_char(char *buffer, int *position, char character) {
444 if(strlen(buffer) < BUFFER_SIZE - 1) {
446 char t = buffer[c], u;
455 buffer[(*position)++] = character;
456 } else error_feedback();
459 void kill_before_cursor(char *buffer, int *position) {
461 while(buffer[*position + s]) {
462 buffer[s] = buffer[*position + s];
469 void kill_after_cursor(char *buffer, int *position) {
470 buffer[*position] = '\0';
473 /*********************************************************************/
475 int previous_visible(int current_line, char **lines, struct matcher *matcher) {
476 int line = current_line - 1;
477 while(line >= 0 && !match(matcher, lines[line])) line--;
481 int next_visible(int current_line, int nb_lines, char **lines,
482 struct matcher *matcher) {
483 int line = current_line + 1;
484 while(line < nb_lines && !match(matcher, lines[line])) line++;
492 /*********************************************************************/
494 /* The line highlighted is the first one matching the matcher in that
495 order: (1) current_focus_line after motion, if it does not match,
496 then (2) the first with a greater index, if none matches, then (3)
497 the first with a lesser index.
499 The index of the line actually shown highlighted is written in
500 displayed_focus_line (it can be -1 if no line at all matches the
503 If there is a motion and a line is actually shown highlighted, its
504 value is written in current_focus_line. */
506 void update_screen(int *current_focus_line, int *displayed_focus_line,
508 int nb_lines, char **lines,
512 char buffer[BUFFER_SIZE];
513 struct matcher matcher;
515 int console_width, console_height;
516 int nb_printed_lines = 0;
519 initialize_matcher(&matcher, use_regexp, case_sensitive, pattern);
521 console_width = getmaxx(stdscr);
522 console_height = getmaxy(stdscr);
524 use_default_colors();
526 /* Add an empty line where we will print the modeline at the end */
530 /* If the regexp is erroneous, print a message saying so */
532 if(matcher.regexp_error) {
534 addnstr("Regexp syntax error", console_width);
538 /* Else, and we do have lines to select from, find a visible line. */
540 else if(nb_lines > 0) {
542 if(match(&matcher, lines[*current_focus_line])) {
543 new_focus_line = *current_focus_line;
545 new_focus_line = next_visible(*current_focus_line, nb_lines, lines,
547 if(new_focus_line < 0) {
548 new_focus_line = previous_visible(*current_focus_line, lines, &matcher);
552 /* If we found a visible line and we should move, let's move */
554 if(new_focus_line >= 0 && motion != 0) {
555 int l = new_focus_line;
557 /* We want to go down, let's find the first visible line below */
558 for(m = 0; l >= 0 && m < motion; m++) {
559 l = next_visible(l, nb_lines, lines, &matcher);
565 /* We want to go up, let's find the first visible line above */
566 for(m = 0; l >= 0 && m < -motion; m++) {
567 l = previous_visible(l, lines, &matcher);
575 /* Here new_focus_line is either a line number matching the
578 if(new_focus_line >= 0) {
580 int first_line = new_focus_line, last_line = new_focus_line;
583 /* We find the first and last lines to show, so that the total
584 of visible lines between them (them included) is
587 while(nb_match < console_height-1 &&
588 (first_line > 0 || last_line < nb_lines - 1)) {
592 while(first_line > 0 && !match(&matcher, lines[first_line])) {
595 if(match(&matcher, lines[first_line])) {
600 if(nb_match < console_height - 1 && last_line < nb_lines - 1) {
602 while(last_line < nb_lines - 1 && !match(&matcher, lines[last_line])) {
606 if(match(&matcher, lines[last_line])) {
612 /* Now we display them */
614 for(l = first_line; l <= last_line; l++) {
615 if(match(&matcher, lines[l])) {
618 while(lines[l][k] && k < BUFFER_SIZE - 2 && k < console_width) {
619 buffer[k] = lines[l][k];
623 /* Highlight the highlighted line ... */
625 if(l == new_focus_line) {
626 while(k < console_width) {
629 attron(attr_focus_line);
630 addnstr(buffer, console_width);
631 attroff(attr_focus_line);
636 addnstr(buffer, console_width);
643 /* If we are on a focused line and we moved, this become the new
647 *current_focus_line = new_focus_line;
651 *displayed_focus_line = new_focus_line;
653 if(nb_printed_lines == 0) {
655 addnstr("No selection", console_width);
660 /* Else, print a message saying that there are no lines to select from */
664 addnstr("Empty choice", console_width);
670 /* Draw the modeline */
674 attron(attr_modeline);
676 for(k = 0; k < console_width; k++) buffer[k] = ' ';
677 buffer[console_width] = '\0';
678 addnstr(buffer, console_width);
682 /* There must be a more elegant way of moving the cursor at a
683 location met during display */
690 cursor_x += strlen(title) + 1;
693 sprintf(buffer, "%d/%d ", nb_printed_lines, nb_lines);
695 cursor_x += strlen(buffer);
697 addnstr(pattern, cursor_position);
698 cursor_x += cursor_position;
700 if(pattern[cursor_position]) {
701 addstr(pattern + cursor_position);
706 /* Add a few info about the mode we are in (regexp and/or case
709 if(use_regexp || matcher.case_sensitive) {
715 if(matcher.case_sensitive) {
726 attroff(attr_modeline);
731 free_matcher(&matcher);
734 /*********************************************************************/
736 void store_line(struct hash_table_t *hash_table,
737 const char *new_line,
738 int *nb_lines, char **lines) {
741 /* Remove the zsh history prefix */
743 if(zsh_history && *new_line == ':') {
744 while(*new_line && *new_line != ';') new_line++;
745 if(*new_line == ';') new_line++;
748 /* Remove the bash history prefix */
751 while(*new_line == ' ') new_line++;
752 while(*new_line >= '0' && *new_line <= '9') new_line++;
753 while(*new_line == ' ') new_line++;
756 /* Check for duplicates with the hash table and insert the line in
757 the list if necessary */
760 dup = add_and_get_previous_index(hash_table,
761 new_line, *nb_lines, lines);
767 lines[*nb_lines] = safe_malloc((strlen(new_line) + 1) * sizeof(char));
768 strcpy(lines[*nb_lines], new_line);
770 /* The string was already in there, so we do not allocate a new
771 string but use the pointer to the first occurence of it */
772 lines[*nb_lines] = lines[dup];
779 void read_file(struct hash_table_t *hash_table,
780 const char *input_filename,
781 int nb_lines_max, int *nb_lines, char **lines) {
783 char raw_line[BUFFER_SIZE];
784 int start, end, eol, k;
787 file = fopen(input_filename, "r");
790 fprintf(stderr, "selector: Can not open `%s'.\n", input_filename);
797 while(*nb_lines < nb_lines_max && (end > start || !feof(file))) {
800 /* Look for the end of a line in what is already in the buffer */
801 while(eol < end && raw_line[eol] != '\n') eol++;
803 /* if we did not find the of a line, move what has not been
804 processed and is in the buffer to the beginning of the buffer,
805 fill the buffer with new data from the file, and look for the
808 for(k = 0; k < end - start; k++) {
809 raw_line[k] = raw_line[k + start];
814 end += fread(raw_line + end, sizeof(char), BUFFER_SIZE - end, file);
815 while(eol < end && raw_line[eol] != '\n') eol++;
818 /* The end of the line is the buffer size, which means the line is
821 if(eol == BUFFER_SIZE) {
822 raw_line[BUFFER_SIZE - 1] = '\0';
823 fprintf(stderr, "selector: Line too long (max is %d characters):\n",
825 fprintf(stderr, "%s", raw_line);
826 fprintf(stderr, "\n");
830 /* If we got a line, we replace the carriage return by a \0 to
833 raw_line[eol] = '\0';
835 store_line(hash_table, raw_line + start,
844 /*********************************************************************/
846 /* For long options that have no equivalent short option, use a
847 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
850 OPT_BASH_MODE = CHAR_MAX + 1
853 static struct option long_options[] = {
854 { "output-file", 1, 0, 'o' },
855 { "pattern-separator", 1, 0, 's' },
856 { "label-separator", 1, 0, 'x' },
857 { "inject-in-tty", no_argument, 0, 'v' },
858 { "add-control-qs", no_argument, 0, 'w' },
859 { "monochrome", no_argument, 0, 'm' },
860 { "no-beep", no_argument, 0, 'q' },
861 { "revert-order", no_argument, 0, 'i' },
862 { "remove-bash-prefix", no_argument, 0, 'b' },
863 { "remove-zsh-prefix", no_argument, 0, 'z' },
864 { "remove-duplicates", no_argument, 0, 'd' },
865 { "regexp", no_argument, 0, 'e' },
866 { "case-sensitive", no_argument, 0, 'a' },
867 { "upper-case-makes-case-sensitive", no_argument, 0, 'u' },
868 { "title", 1, 0, 't' },
869 { "number-of-lines", 1, 0, 'l' },
870 { "colors", 1, 0, 'c' },
871 { "bash", no_argument, 0, OPT_BASH_MODE },
872 { "help", no_argument, 0, 'h' },
876 int main(int argc, char **argv) {
878 char output_filename[BUFFER_SIZE];
879 char pattern[BUFFER_SIZE];
882 int error = 0, show_help = 0, done = 0;
884 int current_focus_line, displayed_focus_line;
887 int color_fg_modeline, color_bg_modeline;
888 int color_fg_highlight, color_bg_highlight;
890 char **lines, **labels;
892 struct hash_table_t *hash_table;
895 if(!isatty(STDIN_FILENO)) {
896 fprintf(stderr, "selector: The standard input is not a tty.\n");
900 color_fg_modeline = COLOR_WHITE;
901 color_bg_modeline = COLOR_BLACK;
902 color_fg_highlight = COLOR_BLACK;
903 color_bg_highlight = COLOR_YELLOW;
905 setlocale(LC_ALL, "");
907 strcpy(output_filename, "");
909 while ((c = getopt_long(argc, argv, "o:s:x:vwmqf:ibzdeaunt:l:c:-h",
910 long_options, NULL)) != -1) {
915 strncpy(output_filename, optarg, BUFFER_SIZE);
919 pattern_separator = optarg[0];
923 label_separator = optarg[0];
927 output_to_vt_buffer = 1;
955 remove_duplicates = 1;
967 upper_caps_makes_case_sensitive = 1;
971 exclamation_negates = 1;
976 title = safe_malloc((strlen(optarg) + 1) * sizeof(char));
977 strcpy(title, optarg);
981 str_to_positive_integers(optarg, &nb_lines_max, 1);
985 str_to_positive_integers(optarg, colors, 4);
986 color_fg_modeline = colors[0];
987 color_bg_modeline = colors[1];
988 color_fg_highlight = colors[2];
989 color_bg_highlight = colors[3];
997 /* Same as -c 7,4,0,3 -q */
998 /* color_fg_modeline = 7; */
999 /* color_bg_modeline = 4; */
1000 /* color_fg_highlight = 0; */
1001 /* color_bg_highlight = 3; */
1002 /* error_flash = 1; */
1003 /* Same as -b -i -d -v -w */
1006 remove_duplicates = 1;
1007 output_to_vt_buffer = 1;
1009 bash_histsize = getenv("HISTSIZE");
1011 str_to_positive_integers(bash_histsize, &nb_lines_max, 1);
1031 lines = safe_malloc(nb_lines_max * sizeof(char *));
1035 if(remove_duplicates) {
1036 hash_table = new_hash_table(nb_lines_max * 10);
1041 while(optind < argc) {
1042 read_file(hash_table,
1044 nb_lines_max, &nb_lines, lines);
1049 free_hash_table(hash_table);
1052 /* Now remove the null strings */
1055 for(k = 0; k < nb_lines; k++) {
1057 lines[n++] = lines[k];
1064 for(l = 0; l < nb_lines / 2; l++) {
1065 char *s = lines[nb_lines - 1 - l];
1066 lines[nb_lines - 1 - l] = lines[l];
1071 /* Build the labels from the strings, take only the part before the
1072 label_separator and transform control characters to printable
1075 labels = safe_malloc(nb_lines * sizeof(char *));
1077 for(l = 0; l < nb_lines; l++) {
1083 while(*t && *t != label_separator) {
1088 labels[l] = safe_malloc((e + 1) * sizeof(char));
1091 while(*t && *t != label_separator) {
1093 while(*u) { *s++ = *u++; }
1100 cursor_position = 0;
1102 /* Here we start to display with curse */
1107 intrflush(stdscr, FALSE);
1109 /* So that the arrow keys work */
1110 keypad(stdscr, TRUE);
1112 attr_error = A_STANDOUT;
1113 attr_modeline = A_REVERSE;
1114 attr_focus_line = A_STANDOUT;
1116 if(with_colors && has_colors()) {
1120 if(color_fg_modeline < 0 || color_fg_modeline >= COLORS ||
1121 color_bg_modeline < 0 || color_bg_modeline >= COLORS ||
1122 color_fg_highlight < 0 || color_bg_highlight >= COLORS ||
1123 color_bg_highlight < 0 || color_bg_highlight >= COLORS) {
1126 fprintf(stderr, "selector: Color numbers have to be between 0 and %d.\n",
1131 init_pair(1, color_fg_modeline, color_bg_modeline);
1132 attr_modeline = COLOR_PAIR(1);
1134 init_pair(2, color_fg_highlight, color_bg_highlight);
1135 attr_focus_line = COLOR_PAIR(2);
1137 init_pair(3, COLOR_WHITE, COLOR_RED);
1138 attr_error = COLOR_PAIR(3);
1142 current_focus_line = 0;
1143 displayed_focus_line = 0;
1145 update_screen(¤t_focus_line, &displayed_focus_line,
1147 nb_lines, labels, cursor_position, pattern);
1154 if(key >= ' ' && key <= '~') { /* Insert character */
1155 insert_char(pattern, &cursor_position, key);
1158 else if(key == KEY_BACKSPACE ||
1159 key == '\010' || /* ^H */
1160 key == '\177') { /* ^? */
1161 backspace_char(pattern, &cursor_position);
1164 else if(key == KEY_DC ||
1165 key == '\004') { /* ^D */
1166 delete_char(pattern, &cursor_position);
1169 else if(key == KEY_HOME) {
1170 current_focus_line = 0;
1173 else if(key == KEY_END) {
1174 current_focus_line = nb_lines - 1;
1177 else if(key == KEY_NPAGE) {
1181 else if(key == KEY_PPAGE) {
1185 else if(key == KEY_DOWN ||
1186 key == '\016') { /* ^N */
1190 else if(key == KEY_UP ||
1191 key == '\020') { /* ^P */
1195 else if(key == KEY_LEFT ||
1196 key == '\002') { /* ^B */
1197 if(cursor_position > 0) cursor_position--;
1198 else error_feedback();
1201 else if(key == KEY_RIGHT ||
1202 key == '\006') { /* ^F */
1203 if(pattern[cursor_position]) cursor_position++;
1204 else error_feedback();
1207 else if(key == '\001') { /* ^A */
1208 cursor_position = 0;
1211 else if(key == '\005') { /* ^E */
1212 cursor_position = strlen(pattern);
1215 else if(key == '\022') { /* ^R */
1216 use_regexp = !use_regexp;
1219 else if(key == '\011') { /* ^I */
1220 case_sensitive = !case_sensitive;
1223 else if(key == '\025') { /* ^U */
1224 kill_before_cursor(pattern, &cursor_position);
1227 else if(key == '\013') { /* ^K */
1228 kill_after_cursor(pattern, &cursor_position);
1231 else if(key == '\014') { /* ^L */
1232 /* I suspect that we may sometime mess up the display, so ^L is
1233 here to force a full refresh */
1237 else if(key == '\007' || /* ^G */
1238 key == '\033' || /* ^[ (escape) */
1244 else if(key == KEY_RESIZE || key == -1) {
1245 /* Do nothing when the tty is resized */
1253 update_screen(¤t_focus_line, &displayed_focus_line,
1255 nb_lines, labels, cursor_position, pattern);
1262 /* Here we come back to standard display */
1264 if((key == KEY_ENTER || key == '\n')) {
1268 if(displayed_focus_line >= 0 && displayed_focus_line < nb_lines) {
1269 t = lines[displayed_focus_line];
1270 if(label_separator) {
1271 while(*t && *t != label_separator) t++;
1278 if(output_to_vt_buffer && t) {
1279 inject_into_tty_buffer(t, add_control_qs);
1282 if(output_filename[0]) {
1283 FILE *out = fopen(output_filename, "w");
1286 fprintf(out, "%s", t);
1291 "selector: Can not open %s for writing.\n",
1299 printf("Aborted.\n");
1302 for(l = 0; l < nb_lines; l++) {