3 * selector is a simple command line utility for selection of strings
4 * with a dynamic pattern-matching.
6 * Copyright (c) 2009 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 -q -b -i -d -v -w -l ${HISTSIZE} <(history)
41 #include <sys/ioctl.h>
48 #define BUFFER_SIZE 4096
50 /* Yeah, global variables! */
52 int nb_lines_max = 1000;
53 char pattern_separator = ';';
54 char label_separator = '\0';
55 int output_to_vt_buffer = 0;
56 int add_control_qs = 0;
60 int inverse_order = 0;
61 int remove_duplicates = 0;
63 int case_sensitive = 0;
67 int attr_modeline, attr_focus_line, attr_error;
69 /********************************************************************/
71 /* malloc with error checking. */
73 void *safe_malloc(size_t n) {
76 printf("Can not allocate memory: %s\n", strerror(errno));
82 /*********************************************************************/
84 void inject_into_tty_buffer(char *string, int add_control_qs) {
85 struct termios oldtio, newtio;
87 const char control_q = '\021';
88 tcgetattr(STDIN_FILENO, &oldtio);
89 memset(&newtio, 0, sizeof(newtio));
90 /* Set input mode (non-canonical, *no echo*,...) */
91 tcsetattr(STDIN_FILENO, TCSANOW, &newtio);
92 /* Put the selected string in the tty input buffer */
93 for(k = string; *k; k++) {
94 if(add_control_qs && !(*k >= ' ' && *k <= '~')) {
95 /* Add ^Q to quote control characters */
96 ioctl(STDIN_FILENO, TIOCSTI, &control_q);
98 ioctl(STDIN_FILENO, TIOCSTI, k);
100 /* Restore the old settings */
101 tcsetattr(STDIN_FILENO, TCSANOW, &oldtio);
104 /*********************************************************************/
106 void check_opt(int argc, char **argv, int n_opt, int n, const char *help) {
107 if(n_opt + n >= argc) {
108 fprintf(stderr, "Selector: Missing argument for %s, expecting %s.\n",
114 int string_to_positive_integer(char *string) {
120 for(s = string; *s; s++) {
121 if(*s >= '0' && *s <= '9') {
122 result = result * 10 + (int) (*s - '0');
129 "Selector: Value `%s' is not a positive integer.\n",
137 void error_feedback() {
145 /*********************************************************************/
147 /* A quick and dirty hash table */
149 /* The table itself stores indexes of the strings taken in a char**
150 table. When a string is added, if it was already in the table, the
151 new index replaces the previous one. */
153 struct hash_table_t {
158 struct hash_table_t *new_hash_table(int size) {
160 struct hash_table_t *hash_table;
162 hash_table = safe_malloc(sizeof(struct hash_table_t));
164 hash_table->size = size;
165 hash_table->entries = safe_malloc(hash_table->size * sizeof(int));
167 for(k = 0; k < hash_table->size; k++) {
168 hash_table->entries[k] = -1;
174 void free_hash_table(struct hash_table_t *hash_table) {
175 free(hash_table->entries);
179 /* Adds new_string in the table, associated to new_index. If this
180 string was not already in the table, returns -1. Otherwise, returns
181 the previous index it had. */
183 int add_and_get_previous_index(struct hash_table_t *hash_table,
184 const char *new_string, int new_index,
187 unsigned int code = 0;
190 /* This is my recipe. I checked, it seems to work (as long as
191 hash_table->size is not a multiple of 387433 that should be
194 for(k = 0; new_string[k]; k++) {
195 code = code * 387433 + (unsigned int) (new_string[k]);
198 code = code % hash_table->size;
200 while(hash_table->entries[code] >= 0) {
201 /* There is a string with that code */
202 if(strcmp(new_string, strings[hash_table->entries[code]]) == 0) {
203 /* It is the same string, we keep a copy of the stored index */
204 int result = hash_table->entries[code];
205 /* Put the new one */
206 hash_table->entries[code] = new_index;
207 /* And return the previous one */
210 /* This collision was not the same string, let's move to the next
212 code = (code + 1) % hash_table->size;
215 /* This string was not already in there, store the index in the
216 table and return -1 */
218 hash_table->entries[code] = new_index;
222 /*********************************************************************
223 A matcher matches either with a collection of substrings, or with a
231 char *splitted_patterns, **patterns;
234 int match(char *string, matcher_t *matcher) {
236 if(matcher->nb_patterns >= 0) {
237 if(matcher->case_sensitive) {
238 for(n = 0; n < matcher->nb_patterns; n++) {
239 if(strstr(string, matcher->patterns[n]) == 0) return 0;
242 for(n = 0; n < matcher->nb_patterns; n++) {
243 if(strcasestr(string, matcher->patterns[n]) == 0) return 0;
248 return regexec(&matcher->preg, string, 0, 0, 0) == 0;
252 void free_matcher(matcher_t *matcher) {
253 if(matcher->nb_patterns < 0) {
254 if(!matcher->regexp_error) regfree(&matcher->preg);
256 free(matcher->splitted_patterns);
257 free(matcher->patterns);
261 void initialize_matcher(int use_regexp, int case_sensitive,
262 matcher_t *matcher, const char *pattern) {
264 char *t, *last_pattern_start;
268 matcher->nb_patterns = -1;
269 matcher->regexp_error = regcomp(&matcher->preg, pattern,
270 case_sensitive ? 0 : REG_ICASE);
272 matcher->regexp_error = 0;
273 matcher->nb_patterns = 1;
274 matcher->case_sensitive = case_sensitive;
276 for(s = pattern; *s; s++) {
277 if(*s == pattern_separator) {
278 matcher->nb_patterns++;
282 matcher->splitted_patterns =
283 safe_malloc((strlen(pattern) + 1) * sizeof(char));
286 safe_malloc(matcher->nb_patterns * sizeof(char *));
288 strcpy(matcher->splitted_patterns, pattern);
291 last_pattern_start = matcher->splitted_patterns;
292 for(t = matcher->splitted_patterns; n < matcher->nb_patterns; t++) {
293 if(*t == pattern_separator || *t == '\0') {
295 matcher->patterns[n++] = last_pattern_start;
296 last_pattern_start = t + 1;
302 /*********************************************************************
305 void delete_char(char *buffer, int *position) {
306 if(buffer[*position]) {
308 while(c < BUFFER_SIZE && buffer[c]) {
309 buffer[c] = buffer[c+1];
312 } else error_feedback();
315 void backspace_char(char *buffer, int *position) {
317 if(buffer[*position]) {
318 int c = *position - 1;
320 buffer[c] = buffer[c+1];
324 buffer[*position - 1] = '\0';
328 } else error_feedback();
331 void insert_char(char *buffer, int *position, char character) {
332 if(strlen(buffer) < BUFFER_SIZE - 1) {
334 char t = buffer[c], u;
343 buffer[(*position)++] = character;
344 } else error_feedback();
347 void kill_before_cursor(char *buffer, int *position) {
349 while(buffer[*position + s]) {
350 buffer[s] = buffer[*position + s];
357 void kill_after_cursor(char *buffer, int *position) {
358 buffer[*position] = '\0';
361 /*********************************************************************/
363 int previous_visible(int current_line, int nb_lines, char **lines,
364 matcher_t *matcher) {
365 int line = current_line - 1;
366 while(line >= 0 && !match(lines[line], matcher)) line--;
370 int next_visible(int current_line, int nb_lines, char **lines,
371 matcher_t *matcher) {
372 int line = current_line + 1;
373 while(line < nb_lines && !match(lines[line], matcher)) line++;
381 /*********************************************************************/
383 /* The line highlighted is the first one matching the matcher in that
384 order: (1) current_focus_line after motion, if it does not match,
385 then (2) the first with a greater index, if none matches, then (3)
386 the first with a lesser index.
388 The index of the line actually shown highlighted is written in
389 displayed_focus_line (it can be -1 if no line at all matches the
392 If there is a motion and a line is actually shown highlighted, its
393 value is written in current_focus_line. */
395 void update_screen(int *current_focus_line, int *displayed_focus_line,
397 int nb_lines, char **lines,
401 char buffer[BUFFER_SIZE];
404 int console_width, console_height;
405 int nb_printed_lines = 0;
408 initialize_matcher(use_regexp, case_sensitive, &matcher, pattern);
410 console_width = getmaxx(stdscr);
411 console_height = getmaxy(stdscr);
413 use_default_colors();
415 /* Add an empty line where we will print the modeline at the end */
419 /* If the regexp is erroneous, print a message saying so */
421 if(matcher.regexp_error) {
423 addnstr("Regexp syntax error", console_width);
427 /* Else, and we do have lines to select from, find a visible line. */
429 else if(nb_lines > 0) {
431 if(match(lines[*current_focus_line], &matcher)) {
432 new_focus_line = *current_focus_line;
434 new_focus_line = next_visible(*current_focus_line, nb_lines, lines,
436 if(new_focus_line < 0) {
437 new_focus_line = previous_visible(*current_focus_line, nb_lines, lines,
442 /* If we found a visible line and we should move, let's move */
444 if(new_focus_line >= 0 && motion != 0) {
445 int l = new_focus_line;
447 /* We want to go down, let's find the first visible line below */
448 for(m = 0; l >= 0 && m < motion; m++) {
449 l = next_visible(l, nb_lines, lines, &matcher);
455 /* We want to go up, let's find the first visible line above */
456 for(m = 0; l >= 0 && m < -motion; m++) {
457 l = previous_visible(l, nb_lines, lines, &matcher);
465 /* Here new_focus_line is either a line number matching the
468 if(new_focus_line >= 0) {
470 int first_line = new_focus_line, last_line = new_focus_line;
473 /* We find the first and last line to show, so that the total of
474 visible lines between them (them included) is
477 while(nb_match < console_height-1 &&
478 (first_line > 0 || last_line < nb_lines - 1)) {
482 while(first_line > 0 && !match(lines[first_line], &matcher)) {
485 if(match(lines[first_line], &matcher)) {
490 if(nb_match < console_height - 1 && last_line < nb_lines - 1) {
492 while(last_line < nb_lines - 1 && !match(lines[last_line], &matcher)) {
496 if(match(lines[last_line], &matcher)) {
502 /* Now we display them */
504 for(l = first_line; l <= last_line; l++) {
505 if(match(lines[l], &matcher)) {
508 while(lines[l][k] && k < BUFFER_SIZE - 2 && k < console_width - 2) {
509 buffer[k] = lines[l][k];
513 /* We fill the rest of the line with blanks if this is the
516 if(l == new_focus_line) {
517 while(k < console_width) {
527 /* Highlight the highlighted line ... */
529 if(l == new_focus_line) {
530 attron(attr_focus_line);
531 addnstr(buffer, console_width);
532 attroff(attr_focus_line);
534 addnstr(buffer, console_width);
541 /* If we are on a focused line and we moved, this become the new
545 *current_focus_line = new_focus_line;
549 *displayed_focus_line = new_focus_line;
551 if(nb_printed_lines == 0) {
553 addnstr("No selection", console_width);
558 /* Else, print a message saying that there are no lines to select from */
562 addnstr("Empty choice", console_width);
568 /* Draw the modeline */
572 attron(attr_modeline);
574 for(k = 0; k < console_width; k++) buffer[k] = ' ';
575 buffer[console_width] = '\0';
576 addnstr(buffer, console_width);
580 /* There must be a more elegant way of moving the cursor at a
581 location met during display */
588 cursor_x += strlen(title) + 1;
591 sprintf(buffer, "%d/%d ", nb_printed_lines, nb_lines);
593 cursor_x += strlen(buffer);
595 addnstr(pattern, cursor_position);
596 cursor_x += cursor_position;
598 if(pattern[cursor_position]) {
599 addstr(pattern + cursor_position);
604 /* Add a few info about the mode we are in (regexp and/or case
607 if(use_regexp || case_sensitive) {
624 attroff(attr_modeline);
629 free_matcher(&matcher);
632 /*********************************************************************/
634 void store_line(struct hash_table_t *hash_table,
635 const char *new_line,
636 int *nb_lines, char **lines) {
639 /* Remove the zsh history prefix */
641 if(zsh_history && *new_line == ':') {
642 while(*new_line && *new_line != ';') new_line++;
643 if(*new_line == ';') new_line++;
646 /* Remove the bash history prefix */
649 while(*new_line == ' ') new_line++;
650 while(*new_line >= '0' && *new_line <= '9') new_line++;
651 while(*new_line == ' ') new_line++;
654 /* Check for duplicates with the hash table and insert the line in
655 the list if necessary */
658 dup = add_and_get_previous_index(hash_table,
659 new_line, *nb_lines, lines);
665 lines[*nb_lines] = safe_malloc((strlen(new_line) + 1) * sizeof(char));
666 strcpy(lines[*nb_lines], new_line);
668 /* The string was already in there, so we do not allocate a new
669 string but use the pointer to the first occurence of it */
670 lines[*nb_lines] = lines[dup];
677 void read_file(struct hash_table_t *hash_table,
678 const char *input_filename,
679 int nb_lines_max, int *nb_lines, char **lines) {
681 char raw_line[BUFFER_SIZE];
682 int start, end, eol, k;
685 file = fopen(input_filename, "r");
688 fprintf(stderr, "Selector: Can not open `%s'.\n", input_filename);
695 while(*nb_lines < nb_lines_max && (end > start || !feof(file))) {
698 /* Look for the end of a line in what is already in the buffer */
699 while(eol < end && raw_line[eol] != '\n') eol++;
701 /* if we did not find the of a line, move what has not been
702 processed and is in the buffer to the beginning of the buffer,
703 fill the buffer with new data from the file, and look for the
706 for(k = 0; k < end - start; k++) {
707 raw_line[k] = raw_line[k + start];
712 end += fread(raw_line + end, sizeof(char), BUFFER_SIZE - end, file);
713 while(eol < end && raw_line[eol] != '\n') eol++;
716 /* The end of the line is the buffer size, which means the line is
719 if(eol == BUFFER_SIZE) {
720 raw_line[BUFFER_SIZE - 1] = '\0';
721 fprintf(stderr, "Selector: Line too long (max is %d characters):\n",
723 fprintf(stderr, raw_line);
724 fprintf(stderr, "\n");
728 /* If we got a line, we replace the carriage return by a \0 to
731 raw_line[eol] = '\0';
733 store_line(hash_table, raw_line + start,
742 /*********************************************************************/
744 int main(int argc, char **argv) {
746 char input_filename[BUFFER_SIZE], output_filename[BUFFER_SIZE];
747 char pattern[BUFFER_SIZE];
750 int error = 0, show_help = 0;
751 int rest_are_files = 0;
753 int current_focus_line, displayed_focus_line;
755 int color_fg_modeline, color_bg_modeline;
756 int color_fg_highlight, color_bg_highlight;
758 char **lines, **labels;
760 struct hash_table_t *hash_table;
762 if(!isatty(STDIN_FILENO)) {
763 fprintf(stderr, "Selector: The standard input is not a tty.\n");
767 color_fg_modeline = COLOR_WHITE;
768 color_bg_modeline = COLOR_BLACK;
769 color_fg_highlight = COLOR_BLACK;
770 color_bg_highlight = COLOR_YELLOW;
772 setlocale(LC_ALL, "");
774 strcpy(input_filename, "");
775 strcpy(output_filename, "");
779 while(!error && !show_help &&
781 argv[i][0] == '-' && !rest_are_files) {
783 if(strcmp(argv[i], "-o") == 0) {
784 check_opt(argc, argv, i, 1, "<output filename>");
785 strncpy(output_filename, argv[i+1], BUFFER_SIZE);
789 else if(strcmp(argv[i], "-s") == 0) {
790 check_opt(argc, argv, i, 1, "<pattern separator>");
791 pattern_separator = argv[i+1][0];
795 else if(strcmp(argv[i], "-x") == 0) {
796 check_opt(argc, argv, i, 1, "<label separator>");
797 label_separator = argv[i+1][0];
801 else if(strcmp(argv[i], "-v") == 0) {
802 output_to_vt_buffer = 1;
806 else if(strcmp(argv[i], "-w") == 0) {
811 else if(strcmp(argv[i], "-m") == 0) {
816 else if(strcmp(argv[i], "-q") == 0) {
821 else if(strcmp(argv[i], "-f") == 0) {
822 check_opt(argc, argv, i, 1, "<input filename>");
823 strncpy(input_filename, argv[i+1], BUFFER_SIZE);
827 else if(strcmp(argv[i], "-i") == 0) {
832 else if(strcmp(argv[i], "-b") == 0) {
837 else if(strcmp(argv[i], "-z") == 0) {
842 else if(strcmp(argv[i], "-d") == 0) {
843 remove_duplicates = 1;
847 else if(strcmp(argv[i], "-e") == 0) {
852 else if(strcmp(argv[i], "-a") == 0) {
857 else if(strcmp(argv[i], "-t") == 0) {
858 check_opt(argc, argv, i, 1, "<title>");
860 title = safe_malloc((strlen(argv[i+1]) + 1) * sizeof(char));
861 strcpy(title, argv[i+1]);
865 else if(strcmp(argv[i], "-l") == 0) {
866 check_opt(argc, argv, i, 1, "<maximum number of lines>");
867 nb_lines_max = string_to_positive_integer(argv[i+1]);
871 else if(strcmp(argv[i], "-c") == 0) {
872 check_opt(argc, argv, i, 4,
873 "<fg modeline> <bg modeline> <fg highlight> <bg highlight>");
874 color_fg_modeline = string_to_positive_integer(argv[i + 1]);
875 color_bg_modeline = string_to_positive_integer(argv[i + 2]);
876 color_fg_highlight = string_to_positive_integer(argv[i + 3]);
877 color_bg_highlight = string_to_positive_integer(argv[i + 4]);
881 else if(strcmp(argv[i], "--") == 0) {
886 else if(strcmp(argv[i], "-h") == 0) {
892 fprintf(stderr, "Selector: Unknown option %s.\n", argv[i]);
897 if(show_help || error) {
905 fprintf(out, "Selector version %s-R%s (%s)\n", VERSION, REVISION_NUMBER, UNAME);
906 fprintf(out, "Written by Francois Fleuret <francois@fleuret.org>.\n");
908 fprintf(out, "Usage: %s [options] [<filename1> [<filename2> ...]]\n",
911 fprintf(out, " -h show this help\n");
912 fprintf(out, " -v inject the selected line in the tty\n");
913 fprintf(out, " -w quote control characters with ^Qs when using -v\n");
914 fprintf(out, " -d remove duplicated lines\n");
915 fprintf(out, " -b remove the bash history line prefix\n");
916 fprintf(out, " -z remove the zsh history line prefix\n");
917 fprintf(out, " -i invert the order of lines\n");
918 fprintf(out, " -e start in regexp mode\n");
919 fprintf(out, " -a start in case sensitive mode\n");
920 fprintf(out, " -m monochrome mode\n");
921 fprintf(out, " -q make a flash instead of a beep on an edition error\n");
922 fprintf(out, " -- all following arguments are filenames\n");
923 fprintf(out, " -t <title>\n");
924 fprintf(out, " add a title in the modeline\n");
925 fprintf(out, " -c <fg modeline> <bg modeline> <fg highlight> <bg highlight>\n");
926 fprintf(out, " set the display colors\n");
927 fprintf(out, " -o <output filename>\n");
928 fprintf(out, " set a file to write the selected line to\n");
929 fprintf(out, " -s <pattern separator>\n");
930 fprintf(out, " set the symbol to separate substrings in the pattern\n");
931 fprintf(out, " -x <label separator>\n");
932 fprintf(out, " set the symbol to terminate the label\n");
933 fprintf(out, " -l <max number of lines>\n");
934 fprintf(out, " set the maximum number of lines to take into account\n");
939 lines = safe_malloc(nb_lines_max * sizeof(char *));
943 if(remove_duplicates) {
944 hash_table = new_hash_table(nb_lines_max * 10);
949 if(input_filename[0]) {
950 read_file(hash_table,
952 nb_lines_max, &nb_lines, lines);
956 read_file(hash_table,
958 nb_lines_max, &nb_lines, lines);
963 free_hash_table(hash_table);
966 /* Now remove the null strings */
969 for(k = 0; k < nb_lines; k++) {
971 lines[n++] = lines[k];
978 for(i = 0; i < nb_lines / 2; i++) {
979 char *s = lines[nb_lines - 1 - i];
980 lines[nb_lines - 1 - i] = lines[i];
985 /* Build the labels from the strings, take only the part before the
986 label_separator and transform control characters to printable
989 labels = safe_malloc(nb_lines * sizeof(char *));
991 for(l = 0; l < nb_lines; l++) {
997 while(*t && *t != label_separator) {
1002 labels[l] = safe_malloc((e + 1) * sizeof(char));
1005 while(*t && *t != label_separator) {
1007 while(*u) { *s++ = *u++; }
1014 cursor_position = 0;
1016 /* Here we start to display with curse */
1021 intrflush(stdscr, FALSE);
1023 /* So that the arrow keys work */
1024 keypad(stdscr, TRUE);
1026 attr_error = A_STANDOUT;
1027 attr_modeline = A_REVERSE;
1028 attr_focus_line = A_STANDOUT;
1030 if(with_colors && has_colors()) {
1034 if(color_fg_modeline < 0 || color_fg_modeline >= COLORS ||
1035 color_bg_modeline < 0 || color_bg_modeline >= COLORS ||
1036 color_fg_highlight < 0 || color_bg_highlight >= COLORS ||
1037 color_bg_highlight < 0 || color_bg_highlight >= COLORS) {
1040 fprintf(stderr, "Selector: Color numbers have to be between 0 and %d.\n",
1045 init_pair(1, color_fg_modeline, color_bg_modeline);
1046 attr_modeline = COLOR_PAIR(1);
1048 init_pair(2, color_fg_highlight, color_bg_highlight);
1049 attr_focus_line = COLOR_PAIR(2);
1051 init_pair(3, COLOR_WHITE, COLOR_RED);
1052 attr_error = COLOR_PAIR(3);
1056 current_focus_line = 0;
1057 displayed_focus_line = 0;
1059 update_screen(¤t_focus_line, &displayed_focus_line,
1061 nb_lines, labels, cursor_position, pattern);
1068 if(key >= ' ' && key <= '~') { /* Insert character */
1069 insert_char(pattern, &cursor_position, key);
1072 else if(key == KEY_BACKSPACE ||
1073 key == '\010' || /* ^H */
1074 key == '\177') { /* ^? */
1075 backspace_char(pattern, &cursor_position);
1078 else if(key == KEY_DC ||
1079 key == '\004') { /* ^D */
1080 delete_char(pattern, &cursor_position);
1083 else if(key == KEY_HOME) {
1084 current_focus_line = 0;
1087 else if(key == KEY_END) {
1088 current_focus_line = nb_lines - 1;
1091 else if(key == KEY_NPAGE) {
1095 else if(key == KEY_PPAGE) {
1099 else if(key == KEY_DOWN ||
1100 key == '\016') { /* ^N */
1104 else if(key == KEY_UP ||
1105 key == '\020') { /* ^P */
1109 else if(key == KEY_LEFT ||
1110 key == '\002') { /* ^B */
1111 if(cursor_position > 0) cursor_position--;
1112 else error_feedback();
1115 else if(key == KEY_RIGHT ||
1116 key == '\006') { /* ^F */
1117 if(pattern[cursor_position]) cursor_position++;
1118 else error_feedback();
1121 else if(key == '\001') { /* ^A */
1122 cursor_position = 0;
1125 else if(key == '\005') { /* ^E */
1126 cursor_position = strlen(pattern);
1129 else if(key == '\022') { /* ^R */
1130 use_regexp = !use_regexp;
1133 else if(key == '\011') { /* ^I */
1134 case_sensitive = !case_sensitive;
1137 else if(key == '\025') { /* ^U */
1138 kill_before_cursor(pattern, &cursor_position);
1141 else if(key == '\013') { /* ^K */
1142 kill_after_cursor(pattern, &cursor_position);
1145 else if(key == '\014') { /* ^L */
1146 /* I suspect that we may sometime mess up the display, so ^L is
1147 here to force a full refresh */
1151 update_screen(¤t_focus_line, &displayed_focus_line,
1153 nb_lines, labels, cursor_position, pattern);
1155 } while(key != '\007' && /* ^G */
1156 key != '\033' && /* ^[ (escape) */
1163 /* Here we come back to standard display */
1165 if((key == KEY_ENTER || key == '\n')) {
1169 if(displayed_focus_line >= 0 && displayed_focus_line < nb_lines) {
1170 t = lines[displayed_focus_line];
1171 if(label_separator) {
1172 while(*t && *t != label_separator) t++;
1179 if(output_to_vt_buffer && t) {
1180 inject_into_tty_buffer(t, add_control_qs);
1183 if(output_filename[0]) {
1184 FILE *out = fopen(output_filename, "w");
1192 "Selector: Can not open %s for writing.\n",
1200 printf("Aborted.\n");
1203 for(l = 0; l < nb_lines; l++) {