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(struct matcher *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(struct matcher *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(struct matcher *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, struct matcher *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 struct matcher *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];
501 struct matcher matcher;
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) {
607 buffer[k] = lines[l][k];
611 /* Highlight the highlighted line ... */
613 if(l == new_focus_line) {
614 while(k < console_width) {
617 attron(attr_focus_line);
618 addnstr(buffer, console_width);
619 attroff(attr_focus_line);
624 addnstr(buffer, console_width);
631 /* If we are on a focused line and we moved, this become the new
635 *current_focus_line = new_focus_line;
639 *displayed_focus_line = new_focus_line;
641 if(nb_printed_lines == 0) {
643 addnstr("No selection", console_width);
648 /* Else, print a message saying that there are no lines to select from */
652 addnstr("Empty choice", console_width);
658 /* Draw the modeline */
662 attron(attr_modeline);
664 for(k = 0; k < console_width; k++) buffer[k] = ' ';
665 buffer[console_width] = '\0';
666 addnstr(buffer, console_width);
670 /* There must be a more elegant way of moving the cursor at a
671 location met during display */
678 cursor_x += strlen(title) + 1;
681 sprintf(buffer, "%d/%d ", nb_printed_lines, nb_lines);
683 cursor_x += strlen(buffer);
685 addnstr(pattern, cursor_position);
686 cursor_x += cursor_position;
688 if(pattern[cursor_position]) {
689 addstr(pattern + cursor_position);
694 /* Add a few info about the mode we are in (regexp and/or case
697 if(use_regexp || case_sensitive) {
714 attroff(attr_modeline);
719 free_matcher(&matcher);
722 /*********************************************************************/
724 void store_line(struct hash_table_t *hash_table,
725 const char *new_line,
726 int *nb_lines, char **lines) {
729 /* Remove the zsh history prefix */
731 if(zsh_history && *new_line == ':') {
732 while(*new_line && *new_line != ';') new_line++;
733 if(*new_line == ';') new_line++;
736 /* Remove the bash history prefix */
739 while(*new_line == ' ') new_line++;
740 while(*new_line >= '0' && *new_line <= '9') new_line++;
741 while(*new_line == ' ') new_line++;
744 /* Check for duplicates with the hash table and insert the line in
745 the list if necessary */
748 dup = add_and_get_previous_index(hash_table,
749 new_line, *nb_lines, lines);
755 lines[*nb_lines] = safe_malloc((strlen(new_line) + 1) * sizeof(char));
756 strcpy(lines[*nb_lines], new_line);
758 /* The string was already in there, so we do not allocate a new
759 string but use the pointer to the first occurence of it */
760 lines[*nb_lines] = lines[dup];
767 void read_file(struct hash_table_t *hash_table,
768 const char *input_filename,
769 int nb_lines_max, int *nb_lines, char **lines) {
771 char raw_line[BUFFER_SIZE];
772 int start, end, eol, k;
775 file = fopen(input_filename, "r");
778 fprintf(stderr, "selector: Can not open `%s'.\n", input_filename);
785 while(*nb_lines < nb_lines_max && (end > start || !feof(file))) {
788 /* Look for the end of a line in what is already in the buffer */
789 while(eol < end && raw_line[eol] != '\n') eol++;
791 /* if we did not find the of a line, move what has not been
792 processed and is in the buffer to the beginning of the buffer,
793 fill the buffer with new data from the file, and look for the
796 for(k = 0; k < end - start; k++) {
797 raw_line[k] = raw_line[k + start];
802 end += fread(raw_line + end, sizeof(char), BUFFER_SIZE - end, file);
803 while(eol < end && raw_line[eol] != '\n') eol++;
806 /* The end of the line is the buffer size, which means the line is
809 if(eol == BUFFER_SIZE) {
810 raw_line[BUFFER_SIZE - 1] = '\0';
811 fprintf(stderr, "selector: Line too long (max is %d characters):\n",
813 fprintf(stderr, "%s", raw_line);
814 fprintf(stderr, "\n");
818 /* If we got a line, we replace the carriage return by a \0 to
821 raw_line[eol] = '\0';
823 store_line(hash_table, raw_line + start,
832 /*********************************************************************/
834 /* For long options that have no equivalent short option, use a
835 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
838 OPT_BASH_MODE = CHAR_MAX + 1
841 static struct option long_options[] = {
842 { "output-file", 1, 0, 'o' },
843 { "pattern-separator", 1, 0, 's' },
844 { "label-separator", 1, 0, 'x' },
845 { "inject-in-tty", no_argument, 0, 'v' },
846 { "add-control-qs", no_argument, 0, 'w' },
847 { "monochrome", no_argument, 0, 'm' },
848 { "no-beep", no_argument, 0, 'q' },
849 { "revert-order", no_argument, 0, 'i' },
850 { "remove-bash-prefix", no_argument, 0, 'b' },
851 { "remove-zsh-prefix", no_argument, 0, 'z' },
852 { "remove-duplicates", no_argument, 0, 'd' },
853 { "regexp", no_argument, 0, 'e' },
854 { "case-sensitive", no_argument, 0, 'a' },
855 { "title", 1, 0, 't' },
856 { "number-of-lines", 1, 0, 'l' },
857 { "colors", 1, 0, 'c' },
858 { "bash", no_argument, 0, OPT_BASH_MODE },
859 { "help", no_argument, 0, 'h' },
863 int main(int argc, char **argv) {
865 char output_filename[BUFFER_SIZE];
866 char pattern[BUFFER_SIZE];
869 int error = 0, show_help = 0, done = 0;
871 int current_focus_line, displayed_focus_line;
874 int color_fg_modeline, color_bg_modeline;
875 int color_fg_highlight, color_bg_highlight;
877 char **lines, **labels;
879 struct hash_table_t *hash_table;
882 if(!isatty(STDIN_FILENO)) {
883 fprintf(stderr, "selector: The standard input is not a tty.\n");
887 color_fg_modeline = COLOR_WHITE;
888 color_bg_modeline = COLOR_BLACK;
889 color_fg_highlight = COLOR_BLACK;
890 color_bg_highlight = COLOR_YELLOW;
892 setlocale(LC_ALL, "");
894 strcpy(output_filename, "");
896 while ((c = getopt_long(argc, argv, "o:s:x:vwmqf:ibzdeant:l:c:-h",
897 long_options, NULL)) != -1) {
902 strncpy(output_filename, optarg, BUFFER_SIZE);
906 pattern_separator = optarg[0];
910 label_separator = optarg[0];
914 output_to_vt_buffer = 1;
942 remove_duplicates = 1;
954 exclamation_negates = 1;
959 title = safe_malloc((strlen(optarg) + 1) * sizeof(char));
960 strcpy(title, optarg);
964 str_to_positive_integers(optarg, &nb_lines_max, 1);
968 str_to_positive_integers(optarg, colors, 4);
969 color_fg_modeline = colors[0];
970 color_bg_modeline = colors[1];
971 color_fg_highlight = colors[2];
972 color_bg_highlight = colors[3];
980 /* Same as -c 7,4,0,3 -q */
981 /* color_fg_modeline = 7; */
982 /* color_bg_modeline = 4; */
983 /* color_fg_highlight = 0; */
984 /* color_bg_highlight = 3; */
985 /* error_flash = 1; */
986 /* Same as -b -i -d -v -w */
989 remove_duplicates = 1;
990 output_to_vt_buffer = 1;
992 bash_histsize = getenv("HISTSIZE");
994 str_to_positive_integers(bash_histsize, &nb_lines_max, 1);
1014 lines = safe_malloc(nb_lines_max * sizeof(char *));
1018 if(remove_duplicates) {
1019 hash_table = new_hash_table(nb_lines_max * 10);
1024 while(optind < argc) {
1025 read_file(hash_table,
1027 nb_lines_max, &nb_lines, lines);
1032 free_hash_table(hash_table);
1035 /* Now remove the null strings */
1038 for(k = 0; k < nb_lines; k++) {
1040 lines[n++] = lines[k];
1047 for(l = 0; l < nb_lines / 2; l++) {
1048 char *s = lines[nb_lines - 1 - l];
1049 lines[nb_lines - 1 - l] = lines[l];
1054 /* Build the labels from the strings, take only the part before the
1055 label_separator and transform control characters to printable
1058 labels = safe_malloc(nb_lines * sizeof(char *));
1060 for(l = 0; l < nb_lines; l++) {
1066 while(*t && *t != label_separator) {
1071 labels[l] = safe_malloc((e + 1) * sizeof(char));
1074 while(*t && *t != label_separator) {
1076 while(*u) { *s++ = *u++; }
1083 cursor_position = 0;
1085 /* Here we start to display with curse */
1090 intrflush(stdscr, FALSE);
1092 /* So that the arrow keys work */
1093 keypad(stdscr, TRUE);
1095 attr_error = A_STANDOUT;
1096 attr_modeline = A_REVERSE;
1097 attr_focus_line = A_STANDOUT;
1099 if(with_colors && has_colors()) {
1103 if(color_fg_modeline < 0 || color_fg_modeline >= COLORS ||
1104 color_bg_modeline < 0 || color_bg_modeline >= COLORS ||
1105 color_fg_highlight < 0 || color_bg_highlight >= COLORS ||
1106 color_bg_highlight < 0 || color_bg_highlight >= COLORS) {
1109 fprintf(stderr, "selector: Color numbers have to be between 0 and %d.\n",
1114 init_pair(1, color_fg_modeline, color_bg_modeline);
1115 attr_modeline = COLOR_PAIR(1);
1117 init_pair(2, color_fg_highlight, color_bg_highlight);
1118 attr_focus_line = COLOR_PAIR(2);
1120 init_pair(3, COLOR_WHITE, COLOR_RED);
1121 attr_error = COLOR_PAIR(3);
1125 current_focus_line = 0;
1126 displayed_focus_line = 0;
1128 update_screen(¤t_focus_line, &displayed_focus_line,
1130 nb_lines, labels, cursor_position, pattern);
1137 if(key >= ' ' && key <= '~') { /* Insert character */
1138 insert_char(pattern, &cursor_position, key);
1141 else if(key == KEY_BACKSPACE ||
1142 key == '\010' || /* ^H */
1143 key == '\177') { /* ^? */
1144 backspace_char(pattern, &cursor_position);
1147 else if(key == KEY_DC ||
1148 key == '\004') { /* ^D */
1149 delete_char(pattern, &cursor_position);
1152 else if(key == KEY_HOME) {
1153 current_focus_line = 0;
1156 else if(key == KEY_END) {
1157 current_focus_line = nb_lines - 1;
1160 else if(key == KEY_NPAGE) {
1164 else if(key == KEY_PPAGE) {
1168 else if(key == KEY_DOWN ||
1169 key == '\016') { /* ^N */
1173 else if(key == KEY_UP ||
1174 key == '\020') { /* ^P */
1178 else if(key == KEY_LEFT ||
1179 key == '\002') { /* ^B */
1180 if(cursor_position > 0) cursor_position--;
1181 else error_feedback();
1184 else if(key == KEY_RIGHT ||
1185 key == '\006') { /* ^F */
1186 if(pattern[cursor_position]) cursor_position++;
1187 else error_feedback();
1190 else if(key == '\001') { /* ^A */
1191 cursor_position = 0;
1194 else if(key == '\005') { /* ^E */
1195 cursor_position = strlen(pattern);
1198 else if(key == '\022') { /* ^R */
1199 use_regexp = !use_regexp;
1202 else if(key == '\011') { /* ^I */
1203 case_sensitive = !case_sensitive;
1206 else if(key == '\025') { /* ^U */
1207 kill_before_cursor(pattern, &cursor_position);
1210 else if(key == '\013') { /* ^K */
1211 kill_after_cursor(pattern, &cursor_position);
1214 else if(key == '\014') { /* ^L */
1215 /* I suspect that we may sometime mess up the display, so ^L is
1216 here to force a full refresh */
1220 else if(key == '\007' || /* ^G */
1221 key == '\033' || /* ^[ (escape) */
1227 else if(key == KEY_RESIZE || key == -1) {
1228 /* Do nothing when the tty is resized */
1236 update_screen(¤t_focus_line, &displayed_focus_line,
1238 nb_lines, labels, cursor_position, pattern);
1245 /* Here we come back to standard display */
1247 if((key == KEY_ENTER || key == '\n')) {
1251 if(displayed_focus_line >= 0 && displayed_focus_line < nb_lines) {
1252 t = lines[displayed_focus_line];
1253 if(label_separator) {
1254 while(*t && *t != label_separator) t++;
1261 if(output_to_vt_buffer && t) {
1262 inject_into_tty_buffer(t, add_control_qs);
1265 if(output_filename[0]) {
1266 FILE *out = fopen(output_filename, "w");
1269 fprintf(out, "%s", t);
1274 "selector: Can not open %s for writing.\n",
1282 printf("Aborted.\n");
1285 for(l = 0; l < nb_lines; l++) {