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.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, " --, --rest-are-files\n");
199 fprintf(out, " all following arguments are filenames\n");
200 fprintf(out, " -t <title>, --title <title>\n");
201 fprintf(out, " add a title in the modeline\n");
202 fprintf(out, " -c <colors>, --colors <colors>\n");
203 fprintf(out, " set the display colors with an argument of the form\n");
204 fprintf(out, " <fg_modeline>,<bg_modeline>,<fg_highlight>,<bg_highlight>\n");
205 fprintf(out, " -o <output filename>, --output-file <output filename>\n");
206 fprintf(out, " set a file to write the selected line to\n");
207 fprintf(out, " -s <pattern separator>, --pattern-separator <pattern separator>\n");
208 fprintf(out, " set the symbol to separate substrings in the pattern\n");
209 fprintf(out, " -x <label separator>, --label-separator <label separator>\n");
210 fprintf(out, " set the symbol to terminate the label\n");
211 fprintf(out, " -l <max number of lines>, --number-of-lines <max number of lines>\n");
212 fprintf(out, " set the maximum number of lines to take into account\n");
216 /*********************************************************************/
218 /* A quick and dirty hash table */
220 #define MAGIC_HASH_MULTIPLIER 387433
222 /* The table itself stores indexes of the strings taken in a char**
223 table. When a string is added, if it was already in the table, the
224 new index replaces the previous one. */
226 struct hash_table_t {
231 struct hash_table_t *new_hash_table(int size) {
233 struct hash_table_t *hash_table;
235 hash_table = safe_malloc(sizeof(struct hash_table_t));
237 hash_table->size = size;
238 hash_table->entries = safe_malloc(hash_table->size * sizeof(int));
240 for(k = 0; k < hash_table->size; k++) {
241 hash_table->entries[k] = -1;
247 void free_hash_table(struct hash_table_t *hash_table) {
248 free(hash_table->entries);
252 /* Adds new_string in the table, associated to new_index. If this
253 string was not already in the table, returns -1. Otherwise, returns
254 the previous index it had. */
256 int add_and_get_previous_index(struct hash_table_t *hash_table,
257 const char *new_string, int new_index,
260 unsigned int code = 0, start;
263 /* This is my recipe. I checked, it seems to work (as long as
264 hash_table->size is not a multiple of MAGIC_HASH_MULTIPLIER that
267 for(k = 0; new_string[k]; k++) {
268 code = code * MAGIC_HASH_MULTIPLIER + (unsigned int) (new_string[k]);
271 code = code % hash_table->size;
274 while(hash_table->entries[code] >= 0) {
275 /* There is a string with that code */
276 if(strcmp(new_string, strings[hash_table->entries[code]]) == 0) {
277 /* It is the same string, we keep a copy of the stored index */
278 int result = hash_table->entries[code];
279 /* Put the new one */
280 hash_table->entries[code] = new_index;
281 /* And return the previous one */
284 /* This collision was not the same string, let's move to the next
286 code = (code + 1) % hash_table->size;
287 /* We came back to our original code, which means that the table
290 printf("Full hash table (that should not happen)\n");
295 /* This string was not already in there, store the index in the
296 table and return -1 */
298 hash_table->entries[code] = new_index;
302 /*********************************************************************
303 A matcher matches either with a collection of substrings, or with a
311 char *splitted_patterns, **patterns;
314 int match(matcher_t *matcher, char *string) {
316 if(matcher->nb_patterns >= 0) {
317 if(matcher->case_sensitive) {
318 if(exclamation_negates) {
319 for(n = 0; n < matcher->nb_patterns; n++) {
320 if(matcher->patterns[n][0] == '!') {
321 if(strstr(string, matcher->patterns[n] + 1) != 0) return 0;
323 if(strstr(string, matcher->patterns[n]) == 0) return 0;
327 for(n = 0; n < matcher->nb_patterns; n++) {
328 if(strstr(string, matcher->patterns[n]) == 0) return 0;
332 if(exclamation_negates) {
333 for(n = 0; n < matcher->nb_patterns; n++) {
334 if(matcher->patterns[n][0] == '!') {
335 if(strcasestr(string, matcher->patterns[n] + 1) != 0) return 0;
337 if(strcasestr(string, matcher->patterns[n]) == 0) return 0;
341 for(n = 0; n < matcher->nb_patterns; n++) {
342 if(strcasestr(string, matcher->patterns[n]) == 0) return 0;
348 return regexec(&matcher->preg, string, 0, 0, 0) == 0;
352 void free_matcher(matcher_t *matcher) {
353 if(matcher->nb_patterns < 0) {
354 if(!matcher->regexp_error) regfree(&matcher->preg);
356 free(matcher->splitted_patterns);
357 free(matcher->patterns);
361 void initialize_matcher(matcher_t *matcher,
362 int use_regexp, int case_sensitive,
363 const char *pattern) {
365 char *t, *last_pattern_start;
369 matcher->nb_patterns = -1;
370 matcher->regexp_error = regcomp(&matcher->preg, pattern,
371 case_sensitive ? 0 : REG_ICASE);
373 matcher->regexp_error = 0;
374 matcher->nb_patterns = 1;
375 matcher->case_sensitive = case_sensitive;
377 for(s = pattern; *s; s++) {
378 if(*s == pattern_separator) {
379 matcher->nb_patterns++;
383 matcher->splitted_patterns =
384 safe_malloc((strlen(pattern) + 1) * sizeof(char));
387 safe_malloc(matcher->nb_patterns * sizeof(char *));
389 strcpy(matcher->splitted_patterns, pattern);
392 last_pattern_start = matcher->splitted_patterns;
393 for(t = matcher->splitted_patterns; n < matcher->nb_patterns; t++) {
394 if(*t == pattern_separator || *t == '\0') {
396 matcher->patterns[n++] = last_pattern_start;
397 last_pattern_start = t + 1;
403 /*********************************************************************
406 void delete_char(char *buffer, int *position) {
407 if(buffer[*position]) {
409 while(c < BUFFER_SIZE && buffer[c]) {
410 buffer[c] = buffer[c+1];
413 } else error_feedback();
416 void backspace_char(char *buffer, int *position) {
418 if(buffer[*position]) {
419 int c = *position - 1;
421 buffer[c] = buffer[c+1];
425 buffer[*position - 1] = '\0';
429 } else error_feedback();
432 void insert_char(char *buffer, int *position, char character) {
433 if(strlen(buffer) < BUFFER_SIZE - 1) {
435 char t = buffer[c], u;
444 buffer[(*position)++] = character;
445 } else error_feedback();
448 void kill_before_cursor(char *buffer, int *position) {
450 while(buffer[*position + s]) {
451 buffer[s] = buffer[*position + s];
458 void kill_after_cursor(char *buffer, int *position) {
459 buffer[*position] = '\0';
462 /*********************************************************************/
464 int previous_visible(int current_line, char **lines, matcher_t *matcher) {
465 int line = current_line - 1;
466 while(line >= 0 && !match(matcher, lines[line])) line--;
470 int next_visible(int current_line, int nb_lines, char **lines,
471 matcher_t *matcher) {
472 int line = current_line + 1;
473 while(line < nb_lines && !match(matcher, lines[line])) line++;
481 /*********************************************************************/
483 /* The line highlighted is the first one matching the matcher in that
484 order: (1) current_focus_line after motion, if it does not match,
485 then (2) the first with a greater index, if none matches, then (3)
486 the first with a lesser index.
488 The index of the line actually shown highlighted is written in
489 displayed_focus_line (it can be -1 if no line at all matches the
492 If there is a motion and a line is actually shown highlighted, its
493 value is written in current_focus_line. */
495 void update_screen(int *current_focus_line, int *displayed_focus_line,
497 int nb_lines, char **lines,
501 char buffer[BUFFER_SIZE];
504 int console_width, console_height;
505 int nb_printed_lines = 0;
508 initialize_matcher(&matcher, use_regexp, case_sensitive, pattern);
510 console_width = getmaxx(stdscr);
511 console_height = getmaxy(stdscr);
513 use_default_colors();
515 /* Add an empty line where we will print the modeline at the end */
519 /* If the regexp is erroneous, print a message saying so */
521 if(matcher.regexp_error) {
523 addnstr("Regexp syntax error", console_width);
527 /* Else, and we do have lines to select from, find a visible line. */
529 else if(nb_lines > 0) {
531 if(match(&matcher, lines[*current_focus_line])) {
532 new_focus_line = *current_focus_line;
534 new_focus_line = next_visible(*current_focus_line, nb_lines, lines,
536 if(new_focus_line < 0) {
537 new_focus_line = previous_visible(*current_focus_line, lines, &matcher);
541 /* If we found a visible line and we should move, let's move */
543 if(new_focus_line >= 0 && motion != 0) {
544 int l = new_focus_line;
546 /* We want to go down, let's find the first visible line below */
547 for(m = 0; l >= 0 && m < motion; m++) {
548 l = next_visible(l, nb_lines, lines, &matcher);
554 /* We want to go up, let's find the first visible line above */
555 for(m = 0; l >= 0 && m < -motion; m++) {
556 l = previous_visible(l, lines, &matcher);
564 /* Here new_focus_line is either a line number matching the
567 if(new_focus_line >= 0) {
569 int first_line = new_focus_line, last_line = new_focus_line;
572 /* We find the first and last lines to show, so that the total
573 of visible lines between them (them included) is
576 while(nb_match < console_height-1 &&
577 (first_line > 0 || last_line < nb_lines - 1)) {
581 while(first_line > 0 && !match(&matcher, lines[first_line])) {
584 if(match(&matcher, lines[first_line])) {
589 if(nb_match < console_height - 1 && last_line < nb_lines - 1) {
591 while(last_line < nb_lines - 1 && !match(&matcher, lines[last_line])) {
595 if(match(&matcher, lines[last_line])) {
601 /* Now we display them */
603 for(l = first_line; l <= last_line; l++) {
604 if(match(&matcher, lines[l])) {
607 while(lines[l][k] && k < BUFFER_SIZE - 2 && k < console_width - 2) {
608 buffer[k] = lines[l][k];
612 /* We fill the rest of the line with blanks if this is the
615 if(l == new_focus_line) {
616 while(k < console_width) {
626 /* Highlight the highlighted line ... */
628 if(l == new_focus_line) {
629 attron(attr_focus_line);
630 addnstr(buffer, console_width);
631 attroff(attr_focus_line);
633 addnstr(buffer, console_width);
640 /* If we are on a focused line and we moved, this become the new
644 *current_focus_line = new_focus_line;
648 *displayed_focus_line = new_focus_line;
650 if(nb_printed_lines == 0) {
652 addnstr("No selection", console_width);
657 /* Else, print a message saying that there are no lines to select from */
661 addnstr("Empty choice", console_width);
667 /* Draw the modeline */
671 attron(attr_modeline);
673 for(k = 0; k < console_width; k++) buffer[k] = ' ';
674 buffer[console_width] = '\0';
675 addnstr(buffer, console_width);
679 /* There must be a more elegant way of moving the cursor at a
680 location met during display */
687 cursor_x += strlen(title) + 1;
690 sprintf(buffer, "%d/%d ", nb_printed_lines, nb_lines);
692 cursor_x += strlen(buffer);
694 addnstr(pattern, cursor_position);
695 cursor_x += cursor_position;
697 if(pattern[cursor_position]) {
698 addstr(pattern + cursor_position);
703 /* Add a few info about the mode we are in (regexp and/or case
706 if(use_regexp || case_sensitive) {
723 attroff(attr_modeline);
728 free_matcher(&matcher);
731 /*********************************************************************/
733 void store_line(struct hash_table_t *hash_table,
734 const char *new_line,
735 int *nb_lines, char **lines) {
738 /* Remove the zsh history prefix */
740 if(zsh_history && *new_line == ':') {
741 while(*new_line && *new_line != ';') new_line++;
742 if(*new_line == ';') new_line++;
745 /* Remove the bash history prefix */
748 while(*new_line == ' ') new_line++;
749 while(*new_line >= '0' && *new_line <= '9') new_line++;
750 while(*new_line == ' ') new_line++;
753 /* Check for duplicates with the hash table and insert the line in
754 the list if necessary */
757 dup = add_and_get_previous_index(hash_table,
758 new_line, *nb_lines, lines);
764 lines[*nb_lines] = safe_malloc((strlen(new_line) + 1) * sizeof(char));
765 strcpy(lines[*nb_lines], new_line);
767 /* The string was already in there, so we do not allocate a new
768 string but use the pointer to the first occurence of it */
769 lines[*nb_lines] = lines[dup];
776 void read_file(struct hash_table_t *hash_table,
777 const char *input_filename,
778 int nb_lines_max, int *nb_lines, char **lines) {
780 char raw_line[BUFFER_SIZE];
781 int start, end, eol, k;
784 file = fopen(input_filename, "r");
787 fprintf(stderr, "selector: Can not open `%s'.\n", input_filename);
794 while(*nb_lines < nb_lines_max && (end > start || !feof(file))) {
797 /* Look for the end of a line in what is already in the buffer */
798 while(eol < end && raw_line[eol] != '\n') eol++;
800 /* if we did not find the of a line, move what has not been
801 processed and is in the buffer to the beginning of the buffer,
802 fill the buffer with new data from the file, and look for the
805 for(k = 0; k < end - start; k++) {
806 raw_line[k] = raw_line[k + start];
811 end += fread(raw_line + end, sizeof(char), BUFFER_SIZE - end, file);
812 while(eol < end && raw_line[eol] != '\n') eol++;
815 /* The end of the line is the buffer size, which means the line is
818 if(eol == BUFFER_SIZE) {
819 raw_line[BUFFER_SIZE - 1] = '\0';
820 fprintf(stderr, "selector: Line too long (max is %d characters):\n",
822 fprintf(stderr, "%s", raw_line);
823 fprintf(stderr, "\n");
827 /* If we got a line, we replace the carriage return by a \0 to
830 raw_line[eol] = '\0';
832 store_line(hash_table, raw_line + start,
841 /*********************************************************************/
843 /* For long options that have no equivalent short option, use a
844 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
847 OPT_BASH_MODE = CHAR_MAX + 1
850 static struct option long_options[] = {
851 { "output-file", 1, 0, 'o' },
852 { "pattern-separator", 1, 0, 's' },
853 { "label-separator", 1, 0, 'x' },
854 { "inject-in-tty", no_argument, 0, 'v' },
855 { "add-control-qs", no_argument, 0, 'w' },
856 { "monochrome", no_argument, 0, 'm' },
857 { "no-beep", no_argument, 0, 'q' },
858 { "revert-order", no_argument, 0, 'i' },
859 { "remove-bash-prefix", no_argument, 0, 'b' },
860 { "remove-zsh-prefix", no_argument, 0, 'z' },
861 { "remove-duplicates", no_argument, 0, 'd' },
862 { "regexp", no_argument, 0, 'e' },
863 { "case-sensitive", no_argument, 0, 'a' },
864 { "title", 1, 0, 't' },
865 { "number-of-lines", 1, 0, 'l' },
866 { "colors", 1, 0, 'c' },
867 { "rest-are-files", no_argument, 0, '-' },
868 { "bash", no_argument, 0, OPT_BASH_MODE },
869 { "help", no_argument, 0, 'h' },
873 int main(int argc, char **argv) {
875 char output_filename[BUFFER_SIZE];
876 char pattern[BUFFER_SIZE];
879 int error = 0, show_help = 0, done = 0;
880 int rest_are_files = 0;
882 int current_focus_line, displayed_focus_line;
885 int color_fg_modeline, color_bg_modeline;
886 int color_fg_highlight, color_bg_highlight;
888 char **lines, **labels;
890 struct hash_table_t *hash_table;
893 if(!isatty(STDIN_FILENO)) {
894 fprintf(stderr, "selector: The standard input is not a tty.\n");
898 color_fg_modeline = COLOR_WHITE;
899 color_bg_modeline = COLOR_BLACK;
900 color_fg_highlight = COLOR_BLACK;
901 color_bg_highlight = COLOR_YELLOW;
903 setlocale(LC_ALL, "");
905 strcpy(output_filename, "");
907 while (!rest_are_files &&
908 (c = getopt_long(argc, argv, "o:s:x:vwmqf:ibzdeant:l:c:-h",
909 long_options, NULL)) != -1) {
914 strncpy(output_filename, optarg, BUFFER_SIZE);
918 pattern_separator = optarg[0];
922 label_separator = optarg[0];
926 output_to_vt_buffer = 1;
954 remove_duplicates = 1;
966 exclamation_negates = 1;
971 title = safe_malloc((strlen(optarg) + 1) * sizeof(char));
972 strcpy(title, optarg);
976 str_to_positive_integers(optarg, &nb_lines_max, 1);
980 str_to_positive_integers(optarg, colors, 4);
981 color_fg_modeline = colors[0];
982 color_bg_modeline = colors[1];
983 color_fg_highlight = colors[2];
984 color_bg_highlight = colors[3];
996 /* Same as -c 7,4,0,3 -q */
997 /* color_fg_modeline = 7; */
998 /* color_bg_modeline = 4; */
999 /* color_fg_highlight = 0; */
1000 /* color_bg_highlight = 3; */
1001 /* error_flash = 1; */
1002 /* Same as -b -i -d -v -w */
1005 remove_duplicates = 1;
1006 output_to_vt_buffer = 1;
1008 bash_histsize = getenv("HISTSIZE");
1010 str_to_positive_integers(bash_histsize, &nb_lines_max, 1);
1030 lines = safe_malloc(nb_lines_max * sizeof(char *));
1034 if(remove_duplicates) {
1035 hash_table = new_hash_table(nb_lines_max * 10);
1040 while(optind < argc) {
1041 read_file(hash_table,
1043 nb_lines_max, &nb_lines, lines);
1048 free_hash_table(hash_table);
1051 /* Now remove the null strings */
1054 for(k = 0; k < nb_lines; k++) {
1056 lines[n++] = lines[k];
1063 for(l = 0; l < nb_lines / 2; l++) {
1064 char *s = lines[nb_lines - 1 - l];
1065 lines[nb_lines - 1 - l] = lines[l];
1070 /* Build the labels from the strings, take only the part before the
1071 label_separator and transform control characters to printable
1074 labels = safe_malloc(nb_lines * sizeof(char *));
1076 for(l = 0; l < nb_lines; l++) {
1082 while(*t && *t != label_separator) {
1087 labels[l] = safe_malloc((e + 1) * sizeof(char));
1090 while(*t && *t != label_separator) {
1092 while(*u) { *s++ = *u++; }
1099 cursor_position = 0;
1101 /* Here we start to display with curse */
1106 intrflush(stdscr, FALSE);
1108 /* So that the arrow keys work */
1109 keypad(stdscr, TRUE);
1111 attr_error = A_STANDOUT;
1112 attr_modeline = A_REVERSE;
1113 attr_focus_line = A_STANDOUT;
1115 if(with_colors && has_colors()) {
1119 if(color_fg_modeline < 0 || color_fg_modeline >= COLORS ||
1120 color_bg_modeline < 0 || color_bg_modeline >= COLORS ||
1121 color_fg_highlight < 0 || color_bg_highlight >= COLORS ||
1122 color_bg_highlight < 0 || color_bg_highlight >= COLORS) {
1125 fprintf(stderr, "selector: Color numbers have to be between 0 and %d.\n",
1130 init_pair(1, color_fg_modeline, color_bg_modeline);
1131 attr_modeline = COLOR_PAIR(1);
1133 init_pair(2, color_fg_highlight, color_bg_highlight);
1134 attr_focus_line = COLOR_PAIR(2);
1136 init_pair(3, COLOR_WHITE, COLOR_RED);
1137 attr_error = COLOR_PAIR(3);
1141 current_focus_line = 0;
1142 displayed_focus_line = 0;
1144 update_screen(¤t_focus_line, &displayed_focus_line,
1146 nb_lines, labels, cursor_position, pattern);
1153 if(key >= ' ' && key <= '~') { /* Insert character */
1154 insert_char(pattern, &cursor_position, key);
1157 else if(key == KEY_BACKSPACE ||
1158 key == '\010' || /* ^H */
1159 key == '\177') { /* ^? */
1160 backspace_char(pattern, &cursor_position);
1163 else if(key == KEY_DC ||
1164 key == '\004') { /* ^D */
1165 delete_char(pattern, &cursor_position);
1168 else if(key == KEY_HOME) {
1169 current_focus_line = 0;
1172 else if(key == KEY_END) {
1173 current_focus_line = nb_lines - 1;
1176 else if(key == KEY_NPAGE) {
1180 else if(key == KEY_PPAGE) {
1184 else if(key == KEY_DOWN ||
1185 key == '\016') { /* ^N */
1189 else if(key == KEY_UP ||
1190 key == '\020') { /* ^P */
1194 else if(key == KEY_LEFT ||
1195 key == '\002') { /* ^B */
1196 if(cursor_position > 0) cursor_position--;
1197 else error_feedback();
1200 else if(key == KEY_RIGHT ||
1201 key == '\006') { /* ^F */
1202 if(pattern[cursor_position]) cursor_position++;
1203 else error_feedback();
1206 else if(key == '\001') { /* ^A */
1207 cursor_position = 0;
1210 else if(key == '\005') { /* ^E */
1211 cursor_position = strlen(pattern);
1214 else if(key == '\022') { /* ^R */
1215 use_regexp = !use_regexp;
1218 else if(key == '\011') { /* ^I */
1219 case_sensitive = !case_sensitive;
1222 else if(key == '\025') { /* ^U */
1223 kill_before_cursor(pattern, &cursor_position);
1226 else if(key == '\013') { /* ^K */
1227 kill_after_cursor(pattern, &cursor_position);
1230 else if(key == '\014') { /* ^L */
1231 /* I suspect that we may sometime mess up the display, so ^L is
1232 here to force a full refresh */
1236 else if(key == '\007' || /* ^G */
1237 key == '\033' || /* ^[ (escape) */
1248 update_screen(¤t_focus_line, &displayed_focus_line,
1250 nb_lines, labels, cursor_position, pattern);
1257 /* Here we come back to standard display */
1259 if((key == KEY_ENTER || key == '\n')) {
1263 if(displayed_focus_line >= 0 && displayed_focus_line < nb_lines) {
1264 t = lines[displayed_focus_line];
1265 if(label_separator) {
1266 while(*t && *t != label_separator) t++;
1273 if(output_to_vt_buffer && t) {
1274 inject_into_tty_buffer(t, add_control_qs);
1277 if(output_filename[0]) {
1278 FILE *out = fopen(output_filename, "w");
1281 fprintf(out, "%s", t);
1286 "selector: Can not open %s for writing.\n",
1294 printf("Aborted.\n");
1297 for(l = 0; l < nb_lines; l++) {