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>
46 #define VERSION "1.0.1"
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 void print_help(FILE *out) {
147 fprintf(out, "Selector version %s (%s)\n", VERSION, UNAME);
148 fprintf(out, "Written by Francois Fleuret <francois@fleuret.org>.\n");
150 fprintf(out, "Usage: selector [options] [<filename1> [<filename2> ...]]\n");
152 fprintf(out, " -h show this help\n");
153 fprintf(out, " -v inject the selected line in the tty\n");
154 fprintf(out, " -w quote control characters with ^Qs when using -v\n");
155 fprintf(out, " -d remove duplicated lines\n");
156 fprintf(out, " -b remove the bash history line prefix\n");
157 fprintf(out, " -z remove the zsh history line prefix\n");
158 fprintf(out, " -i invert the order of lines\n");
159 fprintf(out, " -e start in regexp mode\n");
160 fprintf(out, " -a start in case sensitive mode\n");
161 fprintf(out, " -m monochrome mode\n");
162 fprintf(out, " -q make a flash instead of a beep on an edition error\n");
163 fprintf(out, " -- all following arguments are filenames\n");
164 fprintf(out, " -t <title>\n");
165 fprintf(out, " add a title in the modeline\n");
166 fprintf(out, " -c <fg modeline> <bg modeline> <fg highlight> <bg highlight>\n");
167 fprintf(out, " set the display colors\n");
168 fprintf(out, " -o <output filename>\n");
169 fprintf(out, " set a file to write the selected line to\n");
170 fprintf(out, " -s <pattern separator>\n");
171 fprintf(out, " set the symbol to separate substrings in the pattern\n");
172 fprintf(out, " -x <label separator>\n");
173 fprintf(out, " set the symbol to terminate the label\n");
174 fprintf(out, " -l <max number of lines>\n");
175 fprintf(out, " set the maximum number of lines to take into account\n");
179 /*********************************************************************/
181 /* A quick and dirty hash table */
183 /* The table itself stores indexes of the strings taken in a char**
184 table. When a string is added, if it was already in the table, the
185 new index replaces the previous one. */
187 struct hash_table_t {
192 struct hash_table_t *new_hash_table(int size) {
194 struct hash_table_t *hash_table;
196 hash_table = safe_malloc(sizeof(struct hash_table_t));
198 hash_table->size = size;
199 hash_table->entries = safe_malloc(hash_table->size * sizeof(int));
201 for(k = 0; k < hash_table->size; k++) {
202 hash_table->entries[k] = -1;
208 void free_hash_table(struct hash_table_t *hash_table) {
209 free(hash_table->entries);
213 /* Adds new_string in the table, associated to new_index. If this
214 string was not already in the table, returns -1. Otherwise, returns
215 the previous index it had. */
217 int add_and_get_previous_index(struct hash_table_t *hash_table,
218 const char *new_string, int new_index,
221 unsigned int code = 0;
224 /* This is my recipe. I checked, it seems to work (as long as
225 hash_table->size is not a multiple of 387433 that should be
228 for(k = 0; new_string[k]; k++) {
229 code = code * 387433 + (unsigned int) (new_string[k]);
232 code = code % hash_table->size;
234 while(hash_table->entries[code] >= 0) {
235 /* There is a string with that code */
236 if(strcmp(new_string, strings[hash_table->entries[code]]) == 0) {
237 /* It is the same string, we keep a copy of the stored index */
238 int result = hash_table->entries[code];
239 /* Put the new one */
240 hash_table->entries[code] = new_index;
241 /* And return the previous one */
244 /* This collision was not the same string, let's move to the next
246 code = (code + 1) % hash_table->size;
249 /* This string was not already in there, store the index in the
250 table and return -1 */
252 hash_table->entries[code] = new_index;
256 /*********************************************************************
257 A matcher matches either with a collection of substrings, or with a
265 char *splitted_patterns, **patterns;
268 int match(char *string, matcher_t *matcher) {
270 if(matcher->nb_patterns >= 0) {
271 if(matcher->case_sensitive) {
272 for(n = 0; n < matcher->nb_patterns; n++) {
273 if(strstr(string, matcher->patterns[n]) == 0) return 0;
276 for(n = 0; n < matcher->nb_patterns; n++) {
277 if(strcasestr(string, matcher->patterns[n]) == 0) return 0;
282 return regexec(&matcher->preg, string, 0, 0, 0) == 0;
286 void free_matcher(matcher_t *matcher) {
287 if(matcher->nb_patterns < 0) {
288 if(!matcher->regexp_error) regfree(&matcher->preg);
290 free(matcher->splitted_patterns);
291 free(matcher->patterns);
295 void initialize_matcher(int use_regexp, int case_sensitive,
296 matcher_t *matcher, const char *pattern) {
298 char *t, *last_pattern_start;
302 matcher->nb_patterns = -1;
303 matcher->regexp_error = regcomp(&matcher->preg, pattern,
304 case_sensitive ? 0 : REG_ICASE);
306 matcher->regexp_error = 0;
307 matcher->nb_patterns = 1;
308 matcher->case_sensitive = case_sensitive;
310 for(s = pattern; *s; s++) {
311 if(*s == pattern_separator) {
312 matcher->nb_patterns++;
316 matcher->splitted_patterns =
317 safe_malloc((strlen(pattern) + 1) * sizeof(char));
320 safe_malloc(matcher->nb_patterns * sizeof(char *));
322 strcpy(matcher->splitted_patterns, pattern);
325 last_pattern_start = matcher->splitted_patterns;
326 for(t = matcher->splitted_patterns; n < matcher->nb_patterns; t++) {
327 if(*t == pattern_separator || *t == '\0') {
329 matcher->patterns[n++] = last_pattern_start;
330 last_pattern_start = t + 1;
336 /*********************************************************************
339 void delete_char(char *buffer, int *position) {
340 if(buffer[*position]) {
342 while(c < BUFFER_SIZE && buffer[c]) {
343 buffer[c] = buffer[c+1];
346 } else error_feedback();
349 void backspace_char(char *buffer, int *position) {
351 if(buffer[*position]) {
352 int c = *position - 1;
354 buffer[c] = buffer[c+1];
358 buffer[*position - 1] = '\0';
362 } else error_feedback();
365 void insert_char(char *buffer, int *position, char character) {
366 if(strlen(buffer) < BUFFER_SIZE - 1) {
368 char t = buffer[c], u;
377 buffer[(*position)++] = character;
378 } else error_feedback();
381 void kill_before_cursor(char *buffer, int *position) {
383 while(buffer[*position + s]) {
384 buffer[s] = buffer[*position + s];
391 void kill_after_cursor(char *buffer, int *position) {
392 buffer[*position] = '\0';
395 /*********************************************************************/
397 int previous_visible(int current_line, int nb_lines, char **lines,
398 matcher_t *matcher) {
399 int line = current_line - 1;
400 while(line >= 0 && !match(lines[line], matcher)) line--;
404 int next_visible(int current_line, int nb_lines, char **lines,
405 matcher_t *matcher) {
406 int line = current_line + 1;
407 while(line < nb_lines && !match(lines[line], matcher)) line++;
415 /*********************************************************************/
417 /* The line highlighted is the first one matching the matcher in that
418 order: (1) current_focus_line after motion, if it does not match,
419 then (2) the first with a greater index, if none matches, then (3)
420 the first with a lesser index.
422 The index of the line actually shown highlighted is written in
423 displayed_focus_line (it can be -1 if no line at all matches the
426 If there is a motion and a line is actually shown highlighted, its
427 value is written in current_focus_line. */
429 void update_screen(int *current_focus_line, int *displayed_focus_line,
431 int nb_lines, char **lines,
435 char buffer[BUFFER_SIZE];
438 int console_width, console_height;
439 int nb_printed_lines = 0;
442 initialize_matcher(use_regexp, case_sensitive, &matcher, pattern);
444 console_width = getmaxx(stdscr);
445 console_height = getmaxy(stdscr);
447 use_default_colors();
449 /* Add an empty line where we will print the modeline at the end */
453 /* If the regexp is erroneous, print a message saying so */
455 if(matcher.regexp_error) {
457 addnstr("Regexp syntax error", console_width);
461 /* Else, and we do have lines to select from, find a visible line. */
463 else if(nb_lines > 0) {
465 if(match(lines[*current_focus_line], &matcher)) {
466 new_focus_line = *current_focus_line;
468 new_focus_line = next_visible(*current_focus_line, nb_lines, lines,
470 if(new_focus_line < 0) {
471 new_focus_line = previous_visible(*current_focus_line, nb_lines, lines,
476 /* If we found a visible line and we should move, let's move */
478 if(new_focus_line >= 0 && motion != 0) {
479 int l = new_focus_line;
481 /* We want to go down, let's find the first visible line below */
482 for(m = 0; l >= 0 && m < motion; m++) {
483 l = next_visible(l, nb_lines, lines, &matcher);
489 /* We want to go up, let's find the first visible line above */
490 for(m = 0; l >= 0 && m < -motion; m++) {
491 l = previous_visible(l, nb_lines, lines, &matcher);
499 /* Here new_focus_line is either a line number matching the
502 if(new_focus_line >= 0) {
504 int first_line = new_focus_line, last_line = new_focus_line;
507 /* We find the first and last line to show, so that the total of
508 visible lines between them (them included) is
511 while(nb_match < console_height-1 &&
512 (first_line > 0 || last_line < nb_lines - 1)) {
516 while(first_line > 0 && !match(lines[first_line], &matcher)) {
519 if(match(lines[first_line], &matcher)) {
524 if(nb_match < console_height - 1 && last_line < nb_lines - 1) {
526 while(last_line < nb_lines - 1 && !match(lines[last_line], &matcher)) {
530 if(match(lines[last_line], &matcher)) {
536 /* Now we display them */
538 for(l = first_line; l <= last_line; l++) {
539 if(match(lines[l], &matcher)) {
542 while(lines[l][k] && k < BUFFER_SIZE - 2 && k < console_width - 2) {
543 buffer[k] = lines[l][k];
547 /* We fill the rest of the line with blanks if this is the
550 if(l == new_focus_line) {
551 while(k < console_width) {
561 /* Highlight the highlighted line ... */
563 if(l == new_focus_line) {
564 attron(attr_focus_line);
565 addnstr(buffer, console_width);
566 attroff(attr_focus_line);
568 addnstr(buffer, console_width);
575 /* If we are on a focused line and we moved, this become the new
579 *current_focus_line = new_focus_line;
583 *displayed_focus_line = new_focus_line;
585 if(nb_printed_lines == 0) {
587 addnstr("No selection", console_width);
592 /* Else, print a message saying that there are no lines to select from */
596 addnstr("Empty choice", console_width);
602 /* Draw the modeline */
606 attron(attr_modeline);
608 for(k = 0; k < console_width; k++) buffer[k] = ' ';
609 buffer[console_width] = '\0';
610 addnstr(buffer, console_width);
614 /* There must be a more elegant way of moving the cursor at a
615 location met during display */
622 cursor_x += strlen(title) + 1;
625 sprintf(buffer, "%d/%d ", nb_printed_lines, nb_lines);
627 cursor_x += strlen(buffer);
629 addnstr(pattern, cursor_position);
630 cursor_x += cursor_position;
632 if(pattern[cursor_position]) {
633 addstr(pattern + cursor_position);
638 /* Add a few info about the mode we are in (regexp and/or case
641 if(use_regexp || case_sensitive) {
658 attroff(attr_modeline);
663 free_matcher(&matcher);
666 /*********************************************************************/
668 void store_line(struct hash_table_t *hash_table,
669 const char *new_line,
670 int *nb_lines, char **lines) {
673 /* Remove the zsh history prefix */
675 if(zsh_history && *new_line == ':') {
676 while(*new_line && *new_line != ';') new_line++;
677 if(*new_line == ';') new_line++;
680 /* Remove the bash history prefix */
683 while(*new_line == ' ') new_line++;
684 while(*new_line >= '0' && *new_line <= '9') new_line++;
685 while(*new_line == ' ') new_line++;
688 /* Check for duplicates with the hash table and insert the line in
689 the list if necessary */
692 dup = add_and_get_previous_index(hash_table,
693 new_line, *nb_lines, lines);
699 lines[*nb_lines] = safe_malloc((strlen(new_line) + 1) * sizeof(char));
700 strcpy(lines[*nb_lines], new_line);
702 /* The string was already in there, so we do not allocate a new
703 string but use the pointer to the first occurence of it */
704 lines[*nb_lines] = lines[dup];
711 void read_file(struct hash_table_t *hash_table,
712 const char *input_filename,
713 int nb_lines_max, int *nb_lines, char **lines) {
715 char raw_line[BUFFER_SIZE];
716 int start, end, eol, k;
719 file = fopen(input_filename, "r");
722 fprintf(stderr, "Selector: Can not open `%s'.\n", input_filename);
729 while(*nb_lines < nb_lines_max && (end > start || !feof(file))) {
732 /* Look for the end of a line in what is already in the buffer */
733 while(eol < end && raw_line[eol] != '\n') eol++;
735 /* if we did not find the of a line, move what has not been
736 processed and is in the buffer to the beginning of the buffer,
737 fill the buffer with new data from the file, and look for the
740 for(k = 0; k < end - start; k++) {
741 raw_line[k] = raw_line[k + start];
746 end += fread(raw_line + end, sizeof(char), BUFFER_SIZE - end, file);
747 while(eol < end && raw_line[eol] != '\n') eol++;
750 /* The end of the line is the buffer size, which means the line is
753 if(eol == BUFFER_SIZE) {
754 raw_line[BUFFER_SIZE - 1] = '\0';
755 fprintf(stderr, "Selector: Line too long (max is %d characters):\n",
757 fprintf(stderr, raw_line);
758 fprintf(stderr, "\n");
762 /* If we got a line, we replace the carriage return by a \0 to
765 raw_line[eol] = '\0';
767 store_line(hash_table, raw_line + start,
776 /*********************************************************************/
778 int main(int argc, char **argv) {
780 char input_filename[BUFFER_SIZE], output_filename[BUFFER_SIZE];
781 char pattern[BUFFER_SIZE];
784 int error = 0, show_help = 0;
785 int rest_are_files = 0;
787 int current_focus_line, displayed_focus_line;
789 int color_fg_modeline, color_bg_modeline;
790 int color_fg_highlight, color_bg_highlight;
792 char **lines, **labels;
794 struct hash_table_t *hash_table;
796 if(!isatty(STDIN_FILENO)) {
797 fprintf(stderr, "Selector: The standard input is not a tty.\n");
801 color_fg_modeline = COLOR_WHITE;
802 color_bg_modeline = COLOR_BLACK;
803 color_fg_highlight = COLOR_BLACK;
804 color_bg_highlight = COLOR_YELLOW;
806 setlocale(LC_ALL, "");
808 strcpy(input_filename, "");
809 strcpy(output_filename, "");
813 while(!error && !show_help &&
815 argv[i][0] == '-' && !rest_are_files) {
817 if(strcmp(argv[i], "-o") == 0) {
818 check_opt(argc, argv, i, 1, "<output filename>");
819 strncpy(output_filename, argv[i+1], BUFFER_SIZE);
823 else if(strcmp(argv[i], "-s") == 0) {
824 check_opt(argc, argv, i, 1, "<pattern separator>");
825 pattern_separator = argv[i+1][0];
829 else if(strcmp(argv[i], "-x") == 0) {
830 check_opt(argc, argv, i, 1, "<label separator>");
831 label_separator = argv[i+1][0];
835 else if(strcmp(argv[i], "-v") == 0) {
836 output_to_vt_buffer = 1;
840 else if(strcmp(argv[i], "-w") == 0) {
845 else if(strcmp(argv[i], "-m") == 0) {
850 else if(strcmp(argv[i], "-q") == 0) {
855 else if(strcmp(argv[i], "-f") == 0) {
856 check_opt(argc, argv, i, 1, "<input filename>");
857 strncpy(input_filename, argv[i+1], BUFFER_SIZE);
861 else if(strcmp(argv[i], "-i") == 0) {
866 else if(strcmp(argv[i], "-b") == 0) {
871 else if(strcmp(argv[i], "-z") == 0) {
876 else if(strcmp(argv[i], "-d") == 0) {
877 remove_duplicates = 1;
881 else if(strcmp(argv[i], "-e") == 0) {
886 else if(strcmp(argv[i], "-a") == 0) {
891 else if(strcmp(argv[i], "-t") == 0) {
892 check_opt(argc, argv, i, 1, "<title>");
894 title = safe_malloc((strlen(argv[i+1]) + 1) * sizeof(char));
895 strcpy(title, argv[i+1]);
899 else if(strcmp(argv[i], "-l") == 0) {
900 check_opt(argc, argv, i, 1, "<maximum number of lines>");
901 nb_lines_max = string_to_positive_integer(argv[i+1]);
905 else if(strcmp(argv[i], "-c") == 0) {
906 check_opt(argc, argv, i, 4,
907 "<fg modeline> <bg modeline> <fg highlight> <bg highlight>");
908 color_fg_modeline = string_to_positive_integer(argv[i + 1]);
909 color_bg_modeline = string_to_positive_integer(argv[i + 2]);
910 color_fg_highlight = string_to_positive_integer(argv[i + 3]);
911 color_bg_highlight = string_to_positive_integer(argv[i + 4]);
915 else if(strcmp(argv[i], "--") == 0) {
920 else if(strcmp(argv[i], "-h") == 0) {
926 fprintf(stderr, "Selector: Unknown option %s.\n", argv[i]);
931 if(show_help || error) {
941 lines = safe_malloc(nb_lines_max * sizeof(char *));
945 if(remove_duplicates) {
946 hash_table = new_hash_table(nb_lines_max * 10);
951 if(input_filename[0]) {
952 read_file(hash_table,
954 nb_lines_max, &nb_lines, lines);
958 read_file(hash_table,
960 nb_lines_max, &nb_lines, lines);
965 free_hash_table(hash_table);
968 /* Now remove the null strings */
971 for(k = 0; k < nb_lines; k++) {
973 lines[n++] = lines[k];
980 for(i = 0; i < nb_lines / 2; i++) {
981 char *s = lines[nb_lines - 1 - i];
982 lines[nb_lines - 1 - i] = lines[i];
987 /* Build the labels from the strings, take only the part before the
988 label_separator and transform control characters to printable
991 labels = safe_malloc(nb_lines * sizeof(char *));
993 for(l = 0; l < nb_lines; l++) {
999 while(*t && *t != label_separator) {
1004 labels[l] = safe_malloc((e + 1) * sizeof(char));
1007 while(*t && *t != label_separator) {
1009 while(*u) { *s++ = *u++; }
1016 cursor_position = 0;
1018 /* Here we start to display with curse */
1023 intrflush(stdscr, FALSE);
1025 /* So that the arrow keys work */
1026 keypad(stdscr, TRUE);
1028 attr_error = A_STANDOUT;
1029 attr_modeline = A_REVERSE;
1030 attr_focus_line = A_STANDOUT;
1032 if(with_colors && has_colors()) {
1036 if(color_fg_modeline < 0 || color_fg_modeline >= COLORS ||
1037 color_bg_modeline < 0 || color_bg_modeline >= COLORS ||
1038 color_fg_highlight < 0 || color_bg_highlight >= COLORS ||
1039 color_bg_highlight < 0 || color_bg_highlight >= COLORS) {
1042 fprintf(stderr, "Selector: Color numbers have to be between 0 and %d.\n",
1047 init_pair(1, color_fg_modeline, color_bg_modeline);
1048 attr_modeline = COLOR_PAIR(1);
1050 init_pair(2, color_fg_highlight, color_bg_highlight);
1051 attr_focus_line = COLOR_PAIR(2);
1053 init_pair(3, COLOR_WHITE, COLOR_RED);
1054 attr_error = COLOR_PAIR(3);
1058 current_focus_line = 0;
1059 displayed_focus_line = 0;
1061 update_screen(¤t_focus_line, &displayed_focus_line,
1063 nb_lines, labels, cursor_position, pattern);
1070 if(key >= ' ' && key <= '~') { /* Insert character */
1071 insert_char(pattern, &cursor_position, key);
1074 else if(key == KEY_BACKSPACE ||
1075 key == '\010' || /* ^H */
1076 key == '\177') { /* ^? */
1077 backspace_char(pattern, &cursor_position);
1080 else if(key == KEY_DC ||
1081 key == '\004') { /* ^D */
1082 delete_char(pattern, &cursor_position);
1085 else if(key == KEY_HOME) {
1086 current_focus_line = 0;
1089 else if(key == KEY_END) {
1090 current_focus_line = nb_lines - 1;
1093 else if(key == KEY_NPAGE) {
1097 else if(key == KEY_PPAGE) {
1101 else if(key == KEY_DOWN ||
1102 key == '\016') { /* ^N */
1106 else if(key == KEY_UP ||
1107 key == '\020') { /* ^P */
1111 else if(key == KEY_LEFT ||
1112 key == '\002') { /* ^B */
1113 if(cursor_position > 0) cursor_position--;
1114 else error_feedback();
1117 else if(key == KEY_RIGHT ||
1118 key == '\006') { /* ^F */
1119 if(pattern[cursor_position]) cursor_position++;
1120 else error_feedback();
1123 else if(key == '\001') { /* ^A */
1124 cursor_position = 0;
1127 else if(key == '\005') { /* ^E */
1128 cursor_position = strlen(pattern);
1131 else if(key == '\022') { /* ^R */
1132 use_regexp = !use_regexp;
1135 else if(key == '\011') { /* ^I */
1136 case_sensitive = !case_sensitive;
1139 else if(key == '\025') { /* ^U */
1140 kill_before_cursor(pattern, &cursor_position);
1143 else if(key == '\013') { /* ^K */
1144 kill_after_cursor(pattern, &cursor_position);
1147 else if(key == '\014') { /* ^L */
1148 /* I suspect that we may sometime mess up the display, so ^L is
1149 here to force a full refresh */
1153 update_screen(¤t_focus_line, &displayed_focus_line,
1155 nb_lines, labels, cursor_position, pattern);
1157 } while(key != '\007' && /* ^G */
1158 key != '\033' && /* ^[ (escape) */
1165 /* Here we come back to standard display */
1167 if((key == KEY_ENTER || key == '\n')) {
1171 if(displayed_focus_line >= 0 && displayed_focus_line < nb_lines) {
1172 t = lines[displayed_focus_line];
1173 if(label_separator) {
1174 while(*t && *t != label_separator) t++;
1181 if(output_to_vt_buffer && t) {
1182 inject_into_tty_buffer(t, add_control_qs);
1185 if(output_filename[0]) {
1186 FILE *out = fopen(output_filename, "w");
1194 "Selector: Can not open %s for writing.\n",
1202 printf("Aborted.\n");
1205 for(l = 0; l < nb_lines; l++) {