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.3"
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;
70 int attr_modeline, attr_focus_line, attr_error;
72 /********************************************************************/
74 /* malloc with error checking. */
76 void *safe_malloc(size_t n) {
79 printf("Can not allocate memory: %s\n", strerror(errno));
85 /*********************************************************************/
87 void inject_into_tty_buffer(char *string, int add_control_qs) {
88 struct termios oldtio, newtio;
90 const char control_q = '\021';
91 tcgetattr(STDIN_FILENO, &oldtio);
92 memset(&newtio, 0, sizeof(newtio));
93 /* Set input mode (non-canonical, *no echo*,...) */
94 tcsetattr(STDIN_FILENO, TCSANOW, &newtio);
95 /* Put the selected string in the tty input buffer */
96 for(k = string; *k; k++) {
97 if(add_control_qs && !(*k >= ' ' && *k <= '~')) {
98 /* Add ^Q to quote control characters */
99 ioctl(STDIN_FILENO, TIOCSTI, &control_q);
101 ioctl(STDIN_FILENO, TIOCSTI, k);
103 /* Restore the old settings */
104 tcsetattr(STDIN_FILENO, TCSANOW, &oldtio);
107 /*********************************************************************/
109 void str_to_positive_integers(char *string, int *values, int nb) {
110 int current_value, gotone;
120 if(*s >= '0' && *s <= '9') {
121 current_value = current_value * 10 + (int) (*s - '0');
123 } else if(*s == ',' || *s == '\0') {
126 values[n++] = current_value;
132 "selector: Missing value in `%s'.\n", string);
140 "selector: Too many values in `%s'.\n", string);
145 "selector: Empty value in `%s'.\n", string);
150 "selector: Syntax error in `%s'.\n", string);
157 void error_feedback() {
165 void usage(FILE *out) {
167 fprintf(out, "Selector version %s (%s)\n", VERSION, UNAME);
168 fprintf(out, "Written by Francois Fleuret <francois@fleuret.org>.\n");
170 fprintf(out, "Usage: selector [options] [<filename1> [<filename2> ...]]\n");
172 fprintf(out, " -h, --help\n");
173 fprintf(out, " show this help\n");
174 fprintf(out, " -v, --inject-in-tty\n");
175 fprintf(out, " inject the selected line in the tty\n");
176 fprintf(out, " -w, --add-control-qs\n");
177 fprintf(out, " quote control characters with ^Qs when using -v\n");
178 fprintf(out, " -d, --remove-duplicates\n");
179 fprintf(out, " remove duplicated lines\n");
180 fprintf(out, " -b, --remove-bash-prefix\n");
181 fprintf(out, " remove the bash history line prefix\n");
182 fprintf(out, " -z, --remove-zsh-prefix\n");
183 fprintf(out, " remove the zsh history line prefix\n");
184 fprintf(out, " -i, --revert-order\n");
185 fprintf(out, " invert the order of lines\n");
186 fprintf(out, " -e, --regexp\n");
187 fprintf(out, " start in regexp mode\n");
188 fprintf(out, " -a, --case-sensitive\n");
189 fprintf(out, " start in case sensitive mode\n");
190 fprintf(out, " -n, --exclamation-negates\n");
191 fprintf(out, " exclamation points in substring requires the string to be absent\n");
192 fprintf(out, " -m, --monochrome\n");
193 fprintf(out, " monochrome mode\n");
194 fprintf(out, " -q, --no-beep\n");
195 fprintf(out, " make a flash instead of a beep on an edition error\n");
196 fprintf(out, " --bash\n");
197 fprintf(out, " setting for bash history search, same as -b -i -d -v -w -l ${HISTSIZE}\n");
198 fprintf(out, " -- all following arguments are filenames\n");
199 fprintf(out, " -t <title>, --title <title>\n");
200 fprintf(out, " add a title in the modeline\n");
201 fprintf(out, " -c <colors>, --colors <colors>\n");
202 fprintf(out, " set the display colors with an argument of the form\n");
203 fprintf(out, " <fg_modeline>,<bg_modeline>,<fg_highlight>,<bg_highlight>\n");
204 fprintf(out, " -o <output filename>, --output-file <output filename>\n");
205 fprintf(out, " set a file to write the selected line to\n");
206 fprintf(out, " -s <pattern separator>, --pattern-separator <pattern separator>\n");
207 fprintf(out, " set the symbol to separate substrings in the pattern\n");
208 fprintf(out, " -x <label separator>, --label-separator <label separator>\n");
209 fprintf(out, " set the symbol to terminate the label\n");
210 fprintf(out, " -l <max number of lines>, --number-of-lines <max number of lines>\n");
211 fprintf(out, " set the maximum number of lines to take into account\n");
215 /*********************************************************************/
217 /* A quick and dirty hash table */
219 #define MAGIC_HASH_MULTIPLIER 387433
221 /* The table itself stores indexes of the strings taken in a char**
222 table. When a string is added, if it was already in the table, the
223 new index replaces the previous one. */
225 struct hash_table_t {
230 struct hash_table_t *new_hash_table(int size) {
232 struct hash_table_t *hash_table;
234 hash_table = safe_malloc(sizeof(struct hash_table_t));
236 hash_table->size = size;
237 hash_table->entries = safe_malloc(hash_table->size * sizeof(int));
239 for(k = 0; k < hash_table->size; k++) {
240 hash_table->entries[k] = -1;
246 void free_hash_table(struct hash_table_t *hash_table) {
247 free(hash_table->entries);
251 /* Adds new_string in the table, associated to new_index. If this
252 string was not already in the table, returns -1. Otherwise, returns
253 the previous index it had. */
255 int add_and_get_previous_index(struct hash_table_t *hash_table,
256 const char *new_string, int new_index,
259 unsigned int code = 0, start;
262 /* This is my recipe. I checked, it seems to work (as long as
263 hash_table->size is not a multiple of MAGIC_HASH_MULTIPLIER that
266 for(k = 0; new_string[k]; k++) {
267 code = code * MAGIC_HASH_MULTIPLIER + (unsigned int) (new_string[k]);
270 code = code % hash_table->size;
273 while(hash_table->entries[code] >= 0) {
274 /* There is a string with that code */
275 if(strcmp(new_string, strings[hash_table->entries[code]]) == 0) {
276 /* It is the same string, we keep a copy of the stored index */
277 int result = hash_table->entries[code];
278 /* Put the new one */
279 hash_table->entries[code] = new_index;
280 /* And return the previous one */
283 /* This collision was not the same string, let's move to the next
285 code = (code + 1) % hash_table->size;
286 /* We came back to our original code, which means that the table
289 printf("Full hash table (that should not happen)\n");
294 /* This string was not already in there, store the index in the
295 table and return -1 */
297 hash_table->entries[code] = new_index;
301 /*********************************************************************
302 A matcher matches either with a collection of substrings, or with a
310 char *splitted_patterns, **patterns;
313 int match(matcher_t *matcher, char *string) {
315 if(matcher->nb_patterns >= 0) {
316 if(matcher->case_sensitive) {
317 if(exclamation_negates) {
318 for(n = 0; n < matcher->nb_patterns; n++) {
319 if(matcher->patterns[n][0] == '!') {
320 if(strstr(string, matcher->patterns[n] + 1) != 0) return 0;
322 if(strstr(string, matcher->patterns[n]) == 0) return 0;
326 for(n = 0; n < matcher->nb_patterns; n++) {
327 if(strstr(string, matcher->patterns[n]) == 0) return 0;
331 if(exclamation_negates) {
332 for(n = 0; n < matcher->nb_patterns; n++) {
333 if(matcher->patterns[n][0] == '!') {
334 if(strcasestr(string, matcher->patterns[n] + 1) != 0) return 0;
336 if(strcasestr(string, matcher->patterns[n]) == 0) return 0;
340 for(n = 0; n < matcher->nb_patterns; n++) {
341 if(strcasestr(string, matcher->patterns[n]) == 0) return 0;
347 return regexec(&matcher->preg, string, 0, 0, 0) == 0;
351 void free_matcher(matcher_t *matcher) {
352 if(matcher->nb_patterns < 0) {
353 if(!matcher->regexp_error) regfree(&matcher->preg);
355 free(matcher->splitted_patterns);
356 free(matcher->patterns);
360 void initialize_matcher(matcher_t *matcher,
361 int use_regexp, int case_sensitive,
362 const char *pattern) {
364 char *t, *last_pattern_start;
368 matcher->nb_patterns = -1;
369 matcher->regexp_error = regcomp(&matcher->preg, pattern,
370 case_sensitive ? 0 : REG_ICASE);
372 matcher->regexp_error = 0;
373 matcher->nb_patterns = 1;
374 matcher->case_sensitive = case_sensitive;
376 for(s = pattern; *s; s++) {
377 if(*s == pattern_separator) {
378 matcher->nb_patterns++;
382 matcher->splitted_patterns =
383 safe_malloc((strlen(pattern) + 1) * sizeof(char));
386 safe_malloc(matcher->nb_patterns * sizeof(char *));
388 strcpy(matcher->splitted_patterns, pattern);
391 last_pattern_start = matcher->splitted_patterns;
392 for(t = matcher->splitted_patterns; n < matcher->nb_patterns; t++) {
393 if(*t == pattern_separator || *t == '\0') {
395 matcher->patterns[n++] = last_pattern_start;
396 last_pattern_start = t + 1;
402 /*********************************************************************
405 void delete_char(char *buffer, int *position) {
406 if(buffer[*position]) {
408 while(c < BUFFER_SIZE && buffer[c]) {
409 buffer[c] = buffer[c+1];
412 } else error_feedback();
415 void backspace_char(char *buffer, int *position) {
417 if(buffer[*position]) {
418 int c = *position - 1;
420 buffer[c] = buffer[c+1];
424 buffer[*position - 1] = '\0';
428 } else error_feedback();
431 void insert_char(char *buffer, int *position, char character) {
432 if(strlen(buffer) < BUFFER_SIZE - 1) {
434 char t = buffer[c], u;
443 buffer[(*position)++] = character;
444 } else error_feedback();
447 void kill_before_cursor(char *buffer, int *position) {
449 while(buffer[*position + s]) {
450 buffer[s] = buffer[*position + s];
457 void kill_after_cursor(char *buffer, int *position) {
458 buffer[*position] = '\0';
461 /*********************************************************************/
463 int previous_visible(int current_line, char **lines, matcher_t *matcher) {
464 int line = current_line - 1;
465 while(line >= 0 && !match(matcher, lines[line])) line--;
469 int next_visible(int current_line, int nb_lines, char **lines,
470 matcher_t *matcher) {
471 int line = current_line + 1;
472 while(line < nb_lines && !match(matcher, lines[line])) line++;
480 /*********************************************************************/
482 /* The line highlighted is the first one matching the matcher in that
483 order: (1) current_focus_line after motion, if it does not match,
484 then (2) the first with a greater index, if none matches, then (3)
485 the first with a lesser index.
487 The index of the line actually shown highlighted is written in
488 displayed_focus_line (it can be -1 if no line at all matches the
491 If there is a motion and a line is actually shown highlighted, its
492 value is written in current_focus_line. */
494 void update_screen(int *current_focus_line, int *displayed_focus_line,
496 int nb_lines, char **lines,
500 char buffer[BUFFER_SIZE];
503 int console_width, console_height;
504 int nb_printed_lines = 0;
507 initialize_matcher(&matcher, use_regexp, case_sensitive, pattern);
509 console_width = getmaxx(stdscr);
510 console_height = getmaxy(stdscr);
512 use_default_colors();
514 /* Add an empty line where we will print the modeline at the end */
518 /* If the regexp is erroneous, print a message saying so */
520 if(matcher.regexp_error) {
522 addnstr("Regexp syntax error", console_width);
526 /* Else, and we do have lines to select from, find a visible line. */
528 else if(nb_lines > 0) {
530 if(match(&matcher, lines[*current_focus_line])) {
531 new_focus_line = *current_focus_line;
533 new_focus_line = next_visible(*current_focus_line, nb_lines, lines,
535 if(new_focus_line < 0) {
536 new_focus_line = previous_visible(*current_focus_line, lines, &matcher);
540 /* If we found a visible line and we should move, let's move */
542 if(new_focus_line >= 0 && motion != 0) {
543 int l = new_focus_line;
545 /* We want to go down, let's find the first visible line below */
546 for(m = 0; l >= 0 && m < motion; m++) {
547 l = next_visible(l, nb_lines, lines, &matcher);
553 /* We want to go up, let's find the first visible line above */
554 for(m = 0; l >= 0 && m < -motion; m++) {
555 l = previous_visible(l, lines, &matcher);
563 /* Here new_focus_line is either a line number matching the
566 if(new_focus_line >= 0) {
568 int first_line = new_focus_line, last_line = new_focus_line;
571 /* We find the first and last lines to show, so that the total
572 of visible lines between them (them included) is
575 while(nb_match < console_height-1 &&
576 (first_line > 0 || last_line < nb_lines - 1)) {
580 while(first_line > 0 && !match(&matcher, lines[first_line])) {
583 if(match(&matcher, lines[first_line])) {
588 if(nb_match < console_height - 1 && last_line < nb_lines - 1) {
590 while(last_line < nb_lines - 1 && !match(&matcher, lines[last_line])) {
594 if(match(&matcher, lines[last_line])) {
600 /* Now we display them */
602 for(l = first_line; l <= last_line; l++) {
603 if(match(&matcher, lines[l])) {
606 while(lines[l][k] && k < BUFFER_SIZE - 2 && k < console_width - 2) {
607 buffer[k] = lines[l][k];
611 /* We fill the rest of the line with blanks if this is the
614 if(l == new_focus_line) {
615 while(k < console_width) {
625 /* Highlight the highlighted line ... */
627 if(l == new_focus_line) {
628 attron(attr_focus_line);
629 addnstr(buffer, console_width);
630 attroff(attr_focus_line);
632 addnstr(buffer, console_width);
639 /* If we are on a focused line and we moved, this become the new
643 *current_focus_line = new_focus_line;
647 *displayed_focus_line = new_focus_line;
649 if(nb_printed_lines == 0) {
651 addnstr("No selection", console_width);
656 /* Else, print a message saying that there are no lines to select from */
660 addnstr("Empty choice", console_width);
666 /* Draw the modeline */
670 attron(attr_modeline);
672 for(k = 0; k < console_width; k++) buffer[k] = ' ';
673 buffer[console_width] = '\0';
674 addnstr(buffer, console_width);
678 /* There must be a more elegant way of moving the cursor at a
679 location met during display */
686 cursor_x += strlen(title) + 1;
689 sprintf(buffer, "%d/%d ", nb_printed_lines, nb_lines);
691 cursor_x += strlen(buffer);
693 addnstr(pattern, cursor_position);
694 cursor_x += cursor_position;
696 if(pattern[cursor_position]) {
697 addstr(pattern + cursor_position);
702 /* Add a few info about the mode we are in (regexp and/or case
705 if(use_regexp || case_sensitive) {
722 attroff(attr_modeline);
727 free_matcher(&matcher);
730 /*********************************************************************/
732 void store_line(struct hash_table_t *hash_table,
733 const char *new_line,
734 int *nb_lines, char **lines) {
737 /* Remove the zsh history prefix */
739 if(zsh_history && *new_line == ':') {
740 while(*new_line && *new_line != ';') new_line++;
741 if(*new_line == ';') new_line++;
744 /* Remove the bash history prefix */
747 while(*new_line == ' ') new_line++;
748 while(*new_line >= '0' && *new_line <= '9') new_line++;
749 while(*new_line == ' ') new_line++;
752 /* Check for duplicates with the hash table and insert the line in
753 the list if necessary */
756 dup = add_and_get_previous_index(hash_table,
757 new_line, *nb_lines, lines);
763 lines[*nb_lines] = safe_malloc((strlen(new_line) + 1) * sizeof(char));
764 strcpy(lines[*nb_lines], new_line);
766 /* The string was already in there, so we do not allocate a new
767 string but use the pointer to the first occurence of it */
768 lines[*nb_lines] = lines[dup];
775 void read_file(struct hash_table_t *hash_table,
776 const char *input_filename,
777 int nb_lines_max, int *nb_lines, char **lines) {
779 char raw_line[BUFFER_SIZE];
780 int start, end, eol, k;
783 file = fopen(input_filename, "r");
786 fprintf(stderr, "selector: Can not open `%s'.\n", input_filename);
793 while(*nb_lines < nb_lines_max && (end > start || !feof(file))) {
796 /* Look for the end of a line in what is already in the buffer */
797 while(eol < end && raw_line[eol] != '\n') eol++;
799 /* if we did not find the of a line, move what has not been
800 processed and is in the buffer to the beginning of the buffer,
801 fill the buffer with new data from the file, and look for the
804 for(k = 0; k < end - start; k++) {
805 raw_line[k] = raw_line[k + start];
810 end += fread(raw_line + end, sizeof(char), BUFFER_SIZE - end, file);
811 while(eol < end && raw_line[eol] != '\n') eol++;
814 /* The end of the line is the buffer size, which means the line is
817 if(eol == BUFFER_SIZE) {
818 raw_line[BUFFER_SIZE - 1] = '\0';
819 fprintf(stderr, "selector: Line too long (max is %d characters):\n",
821 fprintf(stderr, "%s", raw_line);
822 fprintf(stderr, "\n");
826 /* If we got a line, we replace the carriage return by a \0 to
829 raw_line[eol] = '\0';
831 store_line(hash_table, raw_line + start,
840 /*********************************************************************/
842 /* For long options that have no equivalent short option, use a
843 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
846 OPT_BASH_MODE = CHAR_MAX + 1
849 static struct option long_options[] = {
850 { "output-file", 1, 0, 'o' },
851 { "pattern-separator", 1, 0, 's' },
852 { "label-separator", 1, 0, 'x' },
853 { "inject-in-tty", no_argument, 0, 'v' },
854 { "add-control-qs", no_argument, 0, 'w' },
855 { "monochrome", no_argument, 0, 'm' },
856 { "no-beep", no_argument, 0, 'q' },
857 { "revert-order", no_argument, 0, 'i' },
858 { "remove-bash-prefix", no_argument, 0, 'b' },
859 { "remove-zsh-prefix", no_argument, 0, 'z' },
860 { "remove-duplicates", no_argument, 0, 'd' },
861 { "regexp", no_argument, 0, 'e' },
862 { "case-sensitive", no_argument, 0, 'a' },
863 { "title", 1, 0, 't' },
864 { "number-of-lines", 1, 0, 'l' },
865 { "colors", 1, 0, 'c' },
866 { "bash", no_argument, 0, OPT_BASH_MODE },
867 { "help", no_argument, 0, 'h' },
871 int main(int argc, char **argv) {
873 char output_filename[BUFFER_SIZE];
874 char pattern[BUFFER_SIZE];
877 int error = 0, show_help = 0, done = 0;
879 int current_focus_line, displayed_focus_line;
882 int color_fg_modeline, color_bg_modeline;
883 int color_fg_highlight, color_bg_highlight;
885 char **lines, **labels;
887 struct hash_table_t *hash_table;
890 if(!isatty(STDIN_FILENO)) {
891 fprintf(stderr, "selector: The standard input is not a tty.\n");
895 color_fg_modeline = COLOR_WHITE;
896 color_bg_modeline = COLOR_BLACK;
897 color_fg_highlight = COLOR_BLACK;
898 color_bg_highlight = COLOR_YELLOW;
900 setlocale(LC_ALL, "");
902 strcpy(output_filename, "");
904 while ((c = getopt_long(argc, argv, "o:s:x:vwmqf:ibzdeant:l:c:-h",
905 long_options, NULL)) != -1) {
910 strncpy(output_filename, optarg, BUFFER_SIZE);
914 pattern_separator = optarg[0];
918 label_separator = optarg[0];
922 output_to_vt_buffer = 1;
950 remove_duplicates = 1;
962 exclamation_negates = 1;
967 title = safe_malloc((strlen(optarg) + 1) * sizeof(char));
968 strcpy(title, optarg);
972 str_to_positive_integers(optarg, &nb_lines_max, 1);
976 str_to_positive_integers(optarg, colors, 4);
977 color_fg_modeline = colors[0];
978 color_bg_modeline = colors[1];
979 color_fg_highlight = colors[2];
980 color_bg_highlight = colors[3];
988 /* Same as -c 7,4,0,3 -q */
989 /* color_fg_modeline = 7; */
990 /* color_bg_modeline = 4; */
991 /* color_fg_highlight = 0; */
992 /* color_bg_highlight = 3; */
993 /* error_flash = 1; */
994 /* Same as -b -i -d -v -w */
997 remove_duplicates = 1;
998 output_to_vt_buffer = 1;
1000 bash_histsize = getenv("HISTSIZE");
1002 str_to_positive_integers(bash_histsize, &nb_lines_max, 1);
1022 lines = safe_malloc(nb_lines_max * sizeof(char *));
1026 if(remove_duplicates) {
1027 hash_table = new_hash_table(nb_lines_max * 10);
1032 while(optind < argc) {
1033 read_file(hash_table,
1035 nb_lines_max, &nb_lines, lines);
1040 free_hash_table(hash_table);
1043 /* Now remove the null strings */
1046 for(k = 0; k < nb_lines; k++) {
1048 lines[n++] = lines[k];
1055 for(l = 0; l < nb_lines / 2; l++) {
1056 char *s = lines[nb_lines - 1 - l];
1057 lines[nb_lines - 1 - l] = lines[l];
1062 /* Build the labels from the strings, take only the part before the
1063 label_separator and transform control characters to printable
1066 labels = safe_malloc(nb_lines * sizeof(char *));
1068 for(l = 0; l < nb_lines; l++) {
1074 while(*t && *t != label_separator) {
1079 labels[l] = safe_malloc((e + 1) * sizeof(char));
1082 while(*t && *t != label_separator) {
1084 while(*u) { *s++ = *u++; }
1091 cursor_position = 0;
1093 /* Here we start to display with curse */
1098 intrflush(stdscr, FALSE);
1100 /* So that the arrow keys work */
1101 keypad(stdscr, TRUE);
1103 attr_error = A_STANDOUT;
1104 attr_modeline = A_REVERSE;
1105 attr_focus_line = A_STANDOUT;
1107 if(with_colors && has_colors()) {
1111 if(color_fg_modeline < 0 || color_fg_modeline >= COLORS ||
1112 color_bg_modeline < 0 || color_bg_modeline >= COLORS ||
1113 color_fg_highlight < 0 || color_bg_highlight >= COLORS ||
1114 color_bg_highlight < 0 || color_bg_highlight >= COLORS) {
1117 fprintf(stderr, "selector: Color numbers have to be between 0 and %d.\n",
1122 init_pair(1, color_fg_modeline, color_bg_modeline);
1123 attr_modeline = COLOR_PAIR(1);
1125 init_pair(2, color_fg_highlight, color_bg_highlight);
1126 attr_focus_line = COLOR_PAIR(2);
1128 init_pair(3, COLOR_WHITE, COLOR_RED);
1129 attr_error = COLOR_PAIR(3);
1133 current_focus_line = 0;
1134 displayed_focus_line = 0;
1136 update_screen(¤t_focus_line, &displayed_focus_line,
1138 nb_lines, labels, cursor_position, pattern);
1145 if(key >= ' ' && key <= '~') { /* Insert character */
1146 insert_char(pattern, &cursor_position, key);
1149 else if(key == KEY_BACKSPACE ||
1150 key == '\010' || /* ^H */
1151 key == '\177') { /* ^? */
1152 backspace_char(pattern, &cursor_position);
1155 else if(key == KEY_DC ||
1156 key == '\004') { /* ^D */
1157 delete_char(pattern, &cursor_position);
1160 else if(key == KEY_HOME) {
1161 current_focus_line = 0;
1164 else if(key == KEY_END) {
1165 current_focus_line = nb_lines - 1;
1168 else if(key == KEY_NPAGE) {
1172 else if(key == KEY_PPAGE) {
1176 else if(key == KEY_DOWN ||
1177 key == '\016') { /* ^N */
1181 else if(key == KEY_UP ||
1182 key == '\020') { /* ^P */
1186 else if(key == KEY_LEFT ||
1187 key == '\002') { /* ^B */
1188 if(cursor_position > 0) cursor_position--;
1189 else error_feedback();
1192 else if(key == KEY_RIGHT ||
1193 key == '\006') { /* ^F */
1194 if(pattern[cursor_position]) cursor_position++;
1195 else error_feedback();
1198 else if(key == '\001') { /* ^A */
1199 cursor_position = 0;
1202 else if(key == '\005') { /* ^E */
1203 cursor_position = strlen(pattern);
1206 else if(key == '\022') { /* ^R */
1207 use_regexp = !use_regexp;
1210 else if(key == '\011') { /* ^I */
1211 case_sensitive = !case_sensitive;
1214 else if(key == '\025') { /* ^U */
1215 kill_before_cursor(pattern, &cursor_position);
1218 else if(key == '\013') { /* ^K */
1219 kill_after_cursor(pattern, &cursor_position);
1222 else if(key == '\014') { /* ^L */
1223 /* I suspect that we may sometime mess up the display, so ^L is
1224 here to force a full refresh */
1228 else if(key == '\007' || /* ^G */
1229 key == '\033' || /* ^[ (escape) */
1235 else if(key == KEY_RESIZE || key == -1) {
1236 /* Do nothing when the tty is resized */
1244 update_screen(¤t_focus_line, &displayed_focus_line,
1246 nb_lines, labels, cursor_position, pattern);
1253 /* Here we come back to standard display */
1255 if((key == KEY_ENTER || key == '\n')) {
1259 if(displayed_focus_line >= 0 && displayed_focus_line < nb_lines) {
1260 t = lines[displayed_focus_line];
1261 if(label_separator) {
1262 while(*t && *t != label_separator) t++;
1269 if(output_to_vt_buffer && t) {
1270 inject_into_tty_buffer(t, add_control_qs);
1273 if(output_filename[0]) {
1274 FILE *out = fopen(output_filename, "w");
1277 fprintf(out, "%s", t);
1282 "selector: Can not open %s for writing.\n",
1290 printf("Aborted.\n");
1293 for(l = 0; l < nb_lines; l++) {