3 * selector is a simple command line utility for selection of strings
4 * with a dynamic pattern-matching.
6 * Copyright (c) 2009, 2010 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.2"
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;
69 int attr_modeline, attr_focus_line, attr_error;
71 /********************************************************************/
73 /* malloc with error checking. */
75 void *safe_malloc(size_t n) {
78 printf("Can not allocate memory: %s\n", strerror(errno));
84 /*********************************************************************/
86 void inject_into_tty_buffer(char *string, int add_control_qs) {
87 struct termios oldtio, newtio;
89 const char control_q = '\021';
90 tcgetattr(STDIN_FILENO, &oldtio);
91 memset(&newtio, 0, sizeof(newtio));
92 /* Set input mode (non-canonical, *no echo*,...) */
93 tcsetattr(STDIN_FILENO, TCSANOW, &newtio);
94 /* Put the selected string in the tty input buffer */
95 for(k = string; *k; k++) {
96 if(add_control_qs && !(*k >= ' ' && *k <= '~')) {
97 /* Add ^Q to quote control characters */
98 ioctl(STDIN_FILENO, TIOCSTI, &control_q);
100 ioctl(STDIN_FILENO, TIOCSTI, k);
102 /* Restore the old settings */
103 tcsetattr(STDIN_FILENO, TCSANOW, &oldtio);
106 /*********************************************************************/
108 void str_to_positive_integers(char *string, int *values, int nb) {
109 int current_value, gotone;
119 if(*s >= '0' && *s <= '9') {
120 current_value = current_value * 10 + (int) (*s - '0');
122 } else if(*s == ',' || *s == '\0') {
125 values[n++] = current_value;
131 "selector: Missing value in `%s'.\n", string);
139 "selector: Too many values in `%s'.\n", string);
144 "selector: Empty value in `%s'.\n", string);
149 "selector: Syntax error in `%s'.\n", string);
156 void error_feedback() {
164 void usage(FILE *out) {
166 fprintf(out, "Selector version %s (%s)\n", VERSION, UNAME);
167 fprintf(out, "Written by Francois Fleuret <francois@fleuret.org>.\n");
169 fprintf(out, "Usage: selector [options] [<filename1> [<filename2> ...]]\n");
171 fprintf(out, " -h, --help\n");
172 fprintf(out, " show this help\n");
173 fprintf(out, " -v, --inject-in-tty\n");
174 fprintf(out, " inject the selected line in the tty\n");
175 fprintf(out, " -w, --add-control-qs\n");
176 fprintf(out, " quote control characters with ^Qs when using -v\n");
177 fprintf(out, " -d, --remove-duplicates\n");
178 fprintf(out, " remove duplicated lines\n");
179 fprintf(out, " -b, --remove-bash-prefix\n");
180 fprintf(out, " remove the bash history line prefix\n");
181 fprintf(out, " -z, --remove-zsh-prefix\n");
182 fprintf(out, " remove the zsh history line prefix\n");
183 fprintf(out, " -i, --revert-order\n");
184 fprintf(out, " invert the order of lines\n");
185 fprintf(out, " -e, --regexp\n");
186 fprintf(out, " start in regexp mode\n");
187 fprintf(out, " -a, --case-sensitive\n");
188 fprintf(out, " start in case sensitive mode\n");
189 fprintf(out, " -m, --monochrome\n");
190 fprintf(out, " monochrome mode\n");
191 fprintf(out, " -q, --no-beep\n");
192 fprintf(out, " make a flash instead of a beep on an edition error\n");
193 fprintf(out, " --bash\n");
194 fprintf(out, " setting for bash history search, same as -b -i -d -v -w -l ${HISTSIZE}\n");
195 fprintf(out, " --, --rest-are-files\n");
196 fprintf(out, " all following arguments are filenames\n");
197 fprintf(out, " -t <title>, --title <title>\n");
198 fprintf(out, " add a title in the modeline\n");
199 fprintf(out, " -c <colors>, --colors <colors>\n");
200 fprintf(out, " set the display colors with an argument of the form\n");
201 fprintf(out, " <fg_modeline>,<bg_modeline>,<fg_highlight>,<bg_highlight>\n");
202 fprintf(out, " -o <output filename>, --output-file <output filename>\n");
203 fprintf(out, " set a file to write the selected line to\n");
204 fprintf(out, " -s <pattern separator>, --pattern-separator <pattern separator>\n");
205 fprintf(out, " set the symbol to separate substrings in the pattern\n");
206 fprintf(out, " -x <label separator>, --label-separator <label separator>\n");
207 fprintf(out, " set the symbol to terminate the label\n");
208 fprintf(out, " -l <max number of lines>, --number-of-lines <max number of lines>\n");
209 fprintf(out, " set the maximum number of lines to take into account\n");
213 /*********************************************************************/
215 /* A quick and dirty hash table */
217 #define MAGIC_HASH_MULTIPLIER 387433
219 /* The table itself stores indexes of the strings taken in a char**
220 table. When a string is added, if it was already in the table, the
221 new index replaces the previous one. */
223 struct hash_table_t {
228 struct hash_table_t *new_hash_table(int size) {
230 struct hash_table_t *hash_table;
232 hash_table = safe_malloc(sizeof(struct hash_table_t));
234 hash_table->size = size;
235 hash_table->entries = safe_malloc(hash_table->size * sizeof(int));
237 for(k = 0; k < hash_table->size; k++) {
238 hash_table->entries[k] = -1;
244 void free_hash_table(struct hash_table_t *hash_table) {
245 free(hash_table->entries);
249 /* Adds new_string in the table, associated to new_index. If this
250 string was not already in the table, returns -1. Otherwise, returns
251 the previous index it had. */
253 int add_and_get_previous_index(struct hash_table_t *hash_table,
254 const char *new_string, int new_index,
257 unsigned int code = 0, start;
260 /* This is my recipe. I checked, it seems to work (as long as
261 hash_table->size is not a multiple of MAGIC_HASH_MULTIPLIER that
264 for(k = 0; new_string[k]; k++) {
265 code = code * MAGIC_HASH_MULTIPLIER + (unsigned int) (new_string[k]);
268 code = code % hash_table->size;
271 while(hash_table->entries[code] >= 0) {
272 /* There is a string with that code */
273 if(strcmp(new_string, strings[hash_table->entries[code]]) == 0) {
274 /* It is the same string, we keep a copy of the stored index */
275 int result = hash_table->entries[code];
276 /* Put the new one */
277 hash_table->entries[code] = new_index;
278 /* And return the previous one */
281 /* This collision was not the same string, let's move to the next
283 code = (code + 1) % hash_table->size;
284 /* We came back to our original code, which means that the table
287 printf("Full hash table (that should not happen)\n");
292 /* This string was not already in there, store the index in the
293 table and return -1 */
295 hash_table->entries[code] = new_index;
299 /*********************************************************************
300 A matcher matches either with a collection of substrings, or with a
308 char *splitted_patterns, **patterns;
311 int match(matcher_t *matcher, char *string) {
313 if(matcher->nb_patterns >= 0) {
314 if(matcher->case_sensitive) {
315 for(n = 0; n < matcher->nb_patterns; n++) {
316 if(strstr(string, matcher->patterns[n]) == 0) return 0;
319 for(n = 0; n < matcher->nb_patterns; n++) {
320 if(strcasestr(string, matcher->patterns[n]) == 0) return 0;
325 return regexec(&matcher->preg, string, 0, 0, 0) == 0;
329 void free_matcher(matcher_t *matcher) {
330 if(matcher->nb_patterns < 0) {
331 if(!matcher->regexp_error) regfree(&matcher->preg);
333 free(matcher->splitted_patterns);
334 free(matcher->patterns);
338 void initialize_matcher(matcher_t *matcher,
339 int use_regexp, int case_sensitive,
340 const char *pattern) {
342 char *t, *last_pattern_start;
346 matcher->nb_patterns = -1;
347 matcher->regexp_error = regcomp(&matcher->preg, pattern,
348 case_sensitive ? 0 : REG_ICASE);
350 matcher->regexp_error = 0;
351 matcher->nb_patterns = 1;
352 matcher->case_sensitive = case_sensitive;
354 for(s = pattern; *s; s++) {
355 if(*s == pattern_separator) {
356 matcher->nb_patterns++;
360 matcher->splitted_patterns =
361 safe_malloc((strlen(pattern) + 1) * sizeof(char));
364 safe_malloc(matcher->nb_patterns * sizeof(char *));
366 strcpy(matcher->splitted_patterns, pattern);
369 last_pattern_start = matcher->splitted_patterns;
370 for(t = matcher->splitted_patterns; n < matcher->nb_patterns; t++) {
371 if(*t == pattern_separator || *t == '\0') {
373 matcher->patterns[n++] = last_pattern_start;
374 last_pattern_start = t + 1;
380 /*********************************************************************
383 void delete_char(char *buffer, int *position) {
384 if(buffer[*position]) {
386 while(c < BUFFER_SIZE && buffer[c]) {
387 buffer[c] = buffer[c+1];
390 } else error_feedback();
393 void backspace_char(char *buffer, int *position) {
395 if(buffer[*position]) {
396 int c = *position - 1;
398 buffer[c] = buffer[c+1];
402 buffer[*position - 1] = '\0';
406 } else error_feedback();
409 void insert_char(char *buffer, int *position, char character) {
410 if(strlen(buffer) < BUFFER_SIZE - 1) {
412 char t = buffer[c], u;
421 buffer[(*position)++] = character;
422 } else error_feedback();
425 void kill_before_cursor(char *buffer, int *position) {
427 while(buffer[*position + s]) {
428 buffer[s] = buffer[*position + s];
435 void kill_after_cursor(char *buffer, int *position) {
436 buffer[*position] = '\0';
439 /*********************************************************************/
441 int previous_visible(int current_line, char **lines, matcher_t *matcher) {
442 int line = current_line - 1;
443 while(line >= 0 && !match(matcher, lines[line])) line--;
447 int next_visible(int current_line, int nb_lines, char **lines,
448 matcher_t *matcher) {
449 int line = current_line + 1;
450 while(line < nb_lines && !match(matcher, lines[line])) line++;
458 /*********************************************************************/
460 /* The line highlighted is the first one matching the matcher in that
461 order: (1) current_focus_line after motion, if it does not match,
462 then (2) the first with a greater index, if none matches, then (3)
463 the first with a lesser index.
465 The index of the line actually shown highlighted is written in
466 displayed_focus_line (it can be -1 if no line at all matches the
469 If there is a motion and a line is actually shown highlighted, its
470 value is written in current_focus_line. */
472 void update_screen(int *current_focus_line, int *displayed_focus_line,
474 int nb_lines, char **lines,
478 char buffer[BUFFER_SIZE];
481 int console_width, console_height;
482 int nb_printed_lines = 0;
485 initialize_matcher(&matcher, use_regexp, case_sensitive, pattern);
487 console_width = getmaxx(stdscr);
488 console_height = getmaxy(stdscr);
490 use_default_colors();
492 /* Add an empty line where we will print the modeline at the end */
496 /* If the regexp is erroneous, print a message saying so */
498 if(matcher.regexp_error) {
500 addnstr("Regexp syntax error", console_width);
504 /* Else, and we do have lines to select from, find a visible line. */
506 else if(nb_lines > 0) {
508 if(match(&matcher, lines[*current_focus_line])) {
509 new_focus_line = *current_focus_line;
511 new_focus_line = next_visible(*current_focus_line, nb_lines, lines,
513 if(new_focus_line < 0) {
514 new_focus_line = previous_visible(*current_focus_line, lines, &matcher);
518 /* If we found a visible line and we should move, let's move */
520 if(new_focus_line >= 0 && motion != 0) {
521 int l = new_focus_line;
523 /* We want to go down, let's find the first visible line below */
524 for(m = 0; l >= 0 && m < motion; m++) {
525 l = next_visible(l, nb_lines, lines, &matcher);
531 /* We want to go up, let's find the first visible line above */
532 for(m = 0; l >= 0 && m < -motion; m++) {
533 l = previous_visible(l, lines, &matcher);
541 /* Here new_focus_line is either a line number matching the
544 if(new_focus_line >= 0) {
546 int first_line = new_focus_line, last_line = new_focus_line;
549 /* We find the first and last lines to show, so that the total
550 of visible lines between them (them included) is
553 while(nb_match < console_height-1 &&
554 (first_line > 0 || last_line < nb_lines - 1)) {
558 while(first_line > 0 && !match(&matcher, lines[first_line])) {
561 if(match(&matcher, lines[first_line])) {
566 if(nb_match < console_height - 1 && last_line < nb_lines - 1) {
568 while(last_line < nb_lines - 1 && !match(&matcher, lines[last_line])) {
572 if(match(&matcher, lines[last_line])) {
578 /* Now we display them */
580 for(l = first_line; l <= last_line; l++) {
581 if(match(&matcher, lines[l])) {
584 while(lines[l][k] && k < BUFFER_SIZE - 2 && k < console_width - 2) {
585 buffer[k] = lines[l][k];
589 /* We fill the rest of the line with blanks if this is the
592 if(l == new_focus_line) {
593 while(k < console_width) {
603 /* Highlight the highlighted line ... */
605 if(l == new_focus_line) {
606 attron(attr_focus_line);
607 addnstr(buffer, console_width);
608 attroff(attr_focus_line);
610 addnstr(buffer, console_width);
617 /* If we are on a focused line and we moved, this become the new
621 *current_focus_line = new_focus_line;
625 *displayed_focus_line = new_focus_line;
627 if(nb_printed_lines == 0) {
629 addnstr("No selection", console_width);
634 /* Else, print a message saying that there are no lines to select from */
638 addnstr("Empty choice", console_width);
644 /* Draw the modeline */
648 attron(attr_modeline);
650 for(k = 0; k < console_width; k++) buffer[k] = ' ';
651 buffer[console_width] = '\0';
652 addnstr(buffer, console_width);
656 /* There must be a more elegant way of moving the cursor at a
657 location met during display */
664 cursor_x += strlen(title) + 1;
667 sprintf(buffer, "%d/%d ", nb_printed_lines, nb_lines);
669 cursor_x += strlen(buffer);
671 addnstr(pattern, cursor_position);
672 cursor_x += cursor_position;
674 if(pattern[cursor_position]) {
675 addstr(pattern + cursor_position);
680 /* Add a few info about the mode we are in (regexp and/or case
683 if(use_regexp || case_sensitive) {
700 attroff(attr_modeline);
705 free_matcher(&matcher);
708 /*********************************************************************/
710 void store_line(struct hash_table_t *hash_table,
711 const char *new_line,
712 int *nb_lines, char **lines) {
715 /* Remove the zsh history prefix */
717 if(zsh_history && *new_line == ':') {
718 while(*new_line && *new_line != ';') new_line++;
719 if(*new_line == ';') new_line++;
722 /* Remove the bash history prefix */
725 while(*new_line == ' ') new_line++;
726 while(*new_line >= '0' && *new_line <= '9') new_line++;
727 while(*new_line == ' ') new_line++;
730 /* Check for duplicates with the hash table and insert the line in
731 the list if necessary */
734 dup = add_and_get_previous_index(hash_table,
735 new_line, *nb_lines, lines);
741 lines[*nb_lines] = safe_malloc((strlen(new_line) + 1) * sizeof(char));
742 strcpy(lines[*nb_lines], new_line);
744 /* The string was already in there, so we do not allocate a new
745 string but use the pointer to the first occurence of it */
746 lines[*nb_lines] = lines[dup];
753 void read_file(struct hash_table_t *hash_table,
754 const char *input_filename,
755 int nb_lines_max, int *nb_lines, char **lines) {
757 char raw_line[BUFFER_SIZE];
758 int start, end, eol, k;
761 file = fopen(input_filename, "r");
764 fprintf(stderr, "selector: Can not open `%s'.\n", input_filename);
771 while(*nb_lines < nb_lines_max && (end > start || !feof(file))) {
774 /* Look for the end of a line in what is already in the buffer */
775 while(eol < end && raw_line[eol] != '\n') eol++;
777 /* if we did not find the of a line, move what has not been
778 processed and is in the buffer to the beginning of the buffer,
779 fill the buffer with new data from the file, and look for the
782 for(k = 0; k < end - start; k++) {
783 raw_line[k] = raw_line[k + start];
788 end += fread(raw_line + end, sizeof(char), BUFFER_SIZE - end, file);
789 while(eol < end && raw_line[eol] != '\n') eol++;
792 /* The end of the line is the buffer size, which means the line is
795 if(eol == BUFFER_SIZE) {
796 raw_line[BUFFER_SIZE - 1] = '\0';
797 fprintf(stderr, "selector: Line too long (max is %d characters):\n",
799 fprintf(stderr, raw_line);
800 fprintf(stderr, "\n");
804 /* If we got a line, we replace the carriage return by a \0 to
807 raw_line[eol] = '\0';
809 store_line(hash_table, raw_line + start,
818 /*********************************************************************/
820 /* For long options that have no equivalent short option, use a
821 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
824 OPT_BASH_MODE = CHAR_MAX + 1
827 static struct option long_options[] = {
828 { "output-file", 1, 0, 'o' },
829 { "pattern-separator", 1, 0, 's' },
830 { "label-separator", 1, 0, 'x' },
831 { "inject-in-tty", no_argument, 0, 'v' },
832 { "add-control-qs", no_argument, 0, 'w' },
833 { "monochrome", no_argument, 0, 'm' },
834 { "no-beep", no_argument, 0, 'q' },
835 { "revert-order", no_argument, 0, 'i' },
836 { "remove-bash-prefix", no_argument, 0, 'b' },
837 { "remove-zsh-prefix", no_argument, 0, 'z' },
838 { "remove-duplicates", no_argument, 0, 'd' },
839 { "regexp", no_argument, 0, 'e' },
840 { "case-sensitive", no_argument, 0, 'a' },
841 { "title", 1, 0, 't' },
842 { "number-of-lines", 1, 0, 'l' },
843 { "colors", 1, 0, 'c' },
844 { "rest-are-files", no_argument, 0, '-' },
845 { "bash", no_argument, 0, OPT_BASH_MODE },
846 { "help", no_argument, 0, 'h' },
850 int main(int argc, char **argv) {
852 char output_filename[BUFFER_SIZE];
853 char pattern[BUFFER_SIZE];
856 int error = 0, show_help = 0, done = 0;
857 int rest_are_files = 0;
859 int current_focus_line, displayed_focus_line;
862 int color_fg_modeline, color_bg_modeline;
863 int color_fg_highlight, color_bg_highlight;
865 char **lines, **labels;
867 struct hash_table_t *hash_table;
870 if(!isatty(STDIN_FILENO)) {
871 fprintf(stderr, "selector: The standard input is not a tty.\n");
875 color_fg_modeline = COLOR_WHITE;
876 color_bg_modeline = COLOR_BLACK;
877 color_fg_highlight = COLOR_BLACK;
878 color_bg_highlight = COLOR_YELLOW;
880 setlocale(LC_ALL, "");
882 strcpy(output_filename, "");
884 while (!rest_are_files &&
885 (c = getopt_long(argc, argv, "o:s:x:vwmqf:ibzdeat:l:c:-h",
886 long_options, NULL)) != -1) {
891 strncpy(output_filename, optarg, BUFFER_SIZE);
895 pattern_separator = optarg[0];
899 label_separator = optarg[0];
903 output_to_vt_buffer = 1;
931 remove_duplicates = 1;
944 title = safe_malloc((strlen(optarg) + 1) * sizeof(char));
945 strcpy(title, optarg);
949 str_to_positive_integers(optarg, &nb_lines_max, 1);
953 str_to_positive_integers(optarg, colors, 4);
954 color_fg_modeline = colors[0];
955 color_bg_modeline = colors[1];
956 color_fg_highlight = colors[2];
957 color_bg_highlight = colors[3];
969 /* Same as -c 7,4,0,3 -q */
970 /* color_fg_modeline = 7; */
971 /* color_bg_modeline = 4; */
972 /* color_fg_highlight = 0; */
973 /* color_bg_highlight = 3; */
974 /* error_flash = 1; */
975 /* Same as -b -i -d -v -w */
978 remove_duplicates = 1;
979 output_to_vt_buffer = 1;
981 bash_histsize = getenv("HISTSIZE");
983 str_to_positive_integers(bash_histsize, &nb_lines_max, 1);
1003 lines = safe_malloc(nb_lines_max * sizeof(char *));
1007 if(remove_duplicates) {
1008 hash_table = new_hash_table(nb_lines_max * 10);
1013 while(optind < argc) {
1014 read_file(hash_table,
1016 nb_lines_max, &nb_lines, lines);
1021 free_hash_table(hash_table);
1024 /* Now remove the null strings */
1027 for(k = 0; k < nb_lines; k++) {
1029 lines[n++] = lines[k];
1036 for(l = 0; l < nb_lines / 2; l++) {
1037 char *s = lines[nb_lines - 1 - l];
1038 lines[nb_lines - 1 - l] = lines[l];
1043 /* Build the labels from the strings, take only the part before the
1044 label_separator and transform control characters to printable
1047 labels = safe_malloc(nb_lines * sizeof(char *));
1049 for(l = 0; l < nb_lines; l++) {
1055 while(*t && *t != label_separator) {
1060 labels[l] = safe_malloc((e + 1) * sizeof(char));
1063 while(*t && *t != label_separator) {
1065 while(*u) { *s++ = *u++; }
1072 cursor_position = 0;
1074 /* Here we start to display with curse */
1079 intrflush(stdscr, FALSE);
1081 /* So that the arrow keys work */
1082 keypad(stdscr, TRUE);
1084 attr_error = A_STANDOUT;
1085 attr_modeline = A_REVERSE;
1086 attr_focus_line = A_STANDOUT;
1088 if(with_colors && has_colors()) {
1092 if(color_fg_modeline < 0 || color_fg_modeline >= COLORS ||
1093 color_bg_modeline < 0 || color_bg_modeline >= COLORS ||
1094 color_fg_highlight < 0 || color_bg_highlight >= COLORS ||
1095 color_bg_highlight < 0 || color_bg_highlight >= COLORS) {
1098 fprintf(stderr, "selector: Color numbers have to be between 0 and %d.\n",
1103 init_pair(1, color_fg_modeline, color_bg_modeline);
1104 attr_modeline = COLOR_PAIR(1);
1106 init_pair(2, color_fg_highlight, color_bg_highlight);
1107 attr_focus_line = COLOR_PAIR(2);
1109 init_pair(3, COLOR_WHITE, COLOR_RED);
1110 attr_error = COLOR_PAIR(3);
1114 current_focus_line = 0;
1115 displayed_focus_line = 0;
1117 update_screen(¤t_focus_line, &displayed_focus_line,
1119 nb_lines, labels, cursor_position, pattern);
1126 if(key >= ' ' && key <= '~') { /* Insert character */
1127 insert_char(pattern, &cursor_position, key);
1130 else if(key == KEY_BACKSPACE ||
1131 key == '\010' || /* ^H */
1132 key == '\177') { /* ^? */
1133 backspace_char(pattern, &cursor_position);
1136 else if(key == KEY_DC ||
1137 key == '\004') { /* ^D */
1138 delete_char(pattern, &cursor_position);
1141 else if(key == KEY_HOME) {
1142 current_focus_line = 0;
1145 else if(key == KEY_END) {
1146 current_focus_line = nb_lines - 1;
1149 else if(key == KEY_NPAGE) {
1153 else if(key == KEY_PPAGE) {
1157 else if(key == KEY_DOWN ||
1158 key == '\016') { /* ^N */
1162 else if(key == KEY_UP ||
1163 key == '\020') { /* ^P */
1167 else if(key == KEY_LEFT ||
1168 key == '\002') { /* ^B */
1169 if(cursor_position > 0) cursor_position--;
1170 else error_feedback();
1173 else if(key == KEY_RIGHT ||
1174 key == '\006') { /* ^F */
1175 if(pattern[cursor_position]) cursor_position++;
1176 else error_feedback();
1179 else if(key == '\001') { /* ^A */
1180 cursor_position = 0;
1183 else if(key == '\005') { /* ^E */
1184 cursor_position = strlen(pattern);
1187 else if(key == '\022') { /* ^R */
1188 use_regexp = !use_regexp;
1191 else if(key == '\011') { /* ^I */
1192 case_sensitive = !case_sensitive;
1195 else if(key == '\025') { /* ^U */
1196 kill_before_cursor(pattern, &cursor_position);
1199 else if(key == '\013') { /* ^K */
1200 kill_after_cursor(pattern, &cursor_position);
1203 else if(key == '\014') { /* ^L */
1204 /* I suspect that we may sometime mess up the display, so ^L is
1205 here to force a full refresh */
1209 else if(key == '\007' || /* ^G */
1210 key == '\033' || /* ^[ (escape) */
1221 update_screen(¤t_focus_line, &displayed_focus_line,
1223 nb_lines, labels, cursor_position, pattern);
1230 /* Here we come back to standard display */
1232 if((key == KEY_ENTER || key == '\n')) {
1236 if(displayed_focus_line >= 0 && displayed_focus_line < nb_lines) {
1237 t = lines[displayed_focus_line];
1238 if(label_separator) {
1239 while(*t && *t != label_separator) t++;
1246 if(output_to_vt_buffer && t) {
1247 inject_into_tty_buffer(t, add_control_qs);
1250 if(output_filename[0]) {
1251 FILE *out = fopen(output_filename, "w");
1259 "selector: Can not open %s for writing.\n",
1267 printf("Aborted.\n");
1270 for(l = 0; l < nb_lines; l++) {