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)
40 #include <sys/ioctl.h>
47 #define BUFFER_SIZE 4096
49 /* Yeah, global variables! */
51 int nb_lines_max = 1000;
52 char pattern_separator = ';';
53 char label_separator = '\0';
54 int output_to_vt_buffer = 0;
55 int add_control_qs = 0;
59 int inverse_order = 0;
60 int remove_duplicates = 0;
62 int case_sensitive = 0;
66 int attr_modeline, attr_focus_line, attr_error;
68 /*********************************************************************/
70 void inject_into_tty_buffer(char *string, int add_control_qs) {
71 struct termios oldtio, newtio;
73 const char control_q = '\021';
74 tcgetattr(STDIN_FILENO, &oldtio);
75 memset(&newtio, 0, sizeof(newtio));
76 /* Set input mode (non-canonical, *no echo*,...) */
77 tcsetattr(STDIN_FILENO, TCSANOW, &newtio);
78 /* Put the selected string in the tty input buffer */
79 for(k = string; *k; k++) {
80 if(add_control_qs && !(*k >= ' ' && *k <= '~')) {
81 /* Add ^Q to quote control characters */
82 ioctl(STDIN_FILENO, TIOCSTI, &control_q);
84 ioctl(STDIN_FILENO, TIOCSTI, k);
86 /* Restore the old settings */
87 tcsetattr(STDIN_FILENO, TCSANOW, &oldtio);
90 /*********************************************************************/
92 void check_opt(int argc, char **argv, int n_opt, int n, const char *help) {
93 if(n_opt + n >= argc) {
94 fprintf(stderr, "Selector: Missing argument for %s, expecting %s.\n",
100 int string_to_positive_integer(char *string) {
106 for(s = string; *s; s++) {
107 if(*s >= '0' && *s <= '9') {
108 result = result * 10 + (int) (*s - '0');
115 "Selector: Value `%s' is not a positive integer.\n",
123 void error_feedback() {
131 /*********************************************************************/
133 /* A quick and dirty hash table */
135 /* The table itself stores indexes of the strings taken in a char**
136 table. When a string is added, if it was already in the table, the
137 new index replaces the previous one. */
139 struct hash_table_t {
144 struct hash_table_t *new_hash_table(int size) {
146 struct hash_table_t *hash_table;
148 hash_table = (struct hash_table_t *) malloc(sizeof(struct hash_table_t));
150 hash_table->size = size;
151 hash_table->entries = (int *) malloc(hash_table->size * sizeof(int));
153 for(k = 0; k < hash_table->size; k++) {
154 hash_table->entries[k] = -1;
160 void free_hash_table(struct hash_table_t *hash_table) {
161 free(hash_table->entries);
165 /* Adds new_string in the table, associated to new_index. If this
166 string was not already in the table, returns -1. Otherwise, returns
167 the previous index it had. */
169 int add_and_get_previous_index(struct hash_table_t *hash_table,
170 const char *new_string, int new_index,
173 unsigned int code = 0;
176 /* This is my recipe. I checked, it seems to work (as long as
177 hash_table->size is not a multiple of 387433 that should be
180 for(k = 0; new_string[k]; k++) {
181 code = code * 387433 + (unsigned int) (new_string[k]);
184 code = code % hash_table->size;
186 while(hash_table->entries[code] >= 0) {
187 /* There is a string with that code */
188 if(strcmp(new_string, strings[hash_table->entries[code]]) == 0) {
189 /* It is the same string, we keep a copy of the stored index */
190 int result = hash_table->entries[code];
191 /* Put the new one */
192 hash_table->entries[code] = new_index;
193 /* And return the previous one */
196 /* This collision was not the same string, let's move to the next
198 code = (code + 1) % hash_table->size;
201 /* This string was not already in there, store the index in the
202 table and return -1 */
204 hash_table->entries[code] = new_index;
208 /*********************************************************************
209 A matcher matches either with a collection of substrings, or with a
217 char *splitted_patterns, **patterns;
220 int match(char *string, matcher_t *matcher) {
222 if(matcher->nb_patterns >= 0) {
223 if(matcher->case_sensitive) {
224 for(n = 0; n < matcher->nb_patterns; n++) {
225 if(strstr(string, matcher->patterns[n]) == 0) return 0;
228 for(n = 0; n < matcher->nb_patterns; n++) {
229 if(strcasestr(string, matcher->patterns[n]) == 0) return 0;
234 return regexec(&matcher->preg, string, 0, 0, 0) == 0;
238 void free_matcher(matcher_t *matcher) {
239 if(matcher->nb_patterns < 0) {
240 if(!matcher->regexp_error) regfree(&matcher->preg);
242 free(matcher->splitted_patterns);
243 free(matcher->patterns);
247 void initialize_matcher(int use_regexp, int case_sensitive,
248 matcher_t *matcher, const char *pattern) {
250 char *t, *last_pattern_start;
254 matcher->nb_patterns = -1;
255 matcher->regexp_error = regcomp(&matcher->preg, pattern,
256 case_sensitive ? 0 : REG_ICASE);
258 matcher->regexp_error = 0;
259 matcher->nb_patterns = 1;
260 matcher->case_sensitive = case_sensitive;
262 for(s = pattern; *s; s++) {
263 if(*s == pattern_separator) {
264 matcher->nb_patterns++;
268 matcher->splitted_patterns =
269 (char *) malloc((strlen(pattern) + 1) * sizeof(char));
272 (char **) malloc(matcher->nb_patterns * sizeof(char *));
274 strcpy(matcher->splitted_patterns, pattern);
277 last_pattern_start = matcher->splitted_patterns;
278 for(t = matcher->splitted_patterns; n < matcher->nb_patterns; t++) {
279 if(*t == pattern_separator || *t == '\0') {
281 matcher->patterns[n++] = last_pattern_start;
282 last_pattern_start = t + 1;
288 /*********************************************************************
291 void delete_char(char *buffer, int *position) {
292 if(buffer[*position]) {
294 while(c < BUFFER_SIZE && buffer[c]) {
295 buffer[c] = buffer[c+1];
298 } else error_feedback();
301 void backspace_char(char *buffer, int *position) {
303 if(buffer[*position]) {
304 int c = *position - 1;
306 buffer[c] = buffer[c+1];
310 buffer[*position - 1] = '\0';
314 } else error_feedback();
317 void insert_char(char *buffer, int *position, char character) {
318 if(strlen(buffer) < BUFFER_SIZE - 1) {
320 char t = buffer[c], u;
329 buffer[(*position)++] = character;
330 } else error_feedback();
333 void kill_before_cursor(char *buffer, int *position) {
335 while(buffer[*position + s]) {
336 buffer[s] = buffer[*position + s];
343 void kill_after_cursor(char *buffer, int *position) {
344 buffer[*position] = '\0';
347 /*********************************************************************/
349 int previous_visible(int current_line, int nb_lines, char **lines,
350 matcher_t *matcher) {
351 int line = current_line - 1;
352 while(line >= 0 && !match(lines[line], matcher)) line--;
356 int next_visible(int current_line, int nb_lines, char **lines,
357 matcher_t *matcher) {
358 int line = current_line + 1;
359 while(line < nb_lines && !match(lines[line], matcher)) line++;
367 /*********************************************************************/
369 /* The line highlighted is the first one matching the matcher in that
370 order: (1) current_focus_line after motion, if it does not match,
371 then (2) the first with a greater index, if none matches, then (3)
372 the first with a lesser index.
374 The index of the line actually shown highlighted is written in
375 displayed_focus_line (it can be -1 if no line at all matches the
378 If there is a motion and a line is actually shown highlighted, its
379 value is written in current_focus_line. */
381 void update_screen(int *current_focus_line, int *displayed_focus_line,
383 int nb_lines, char **lines,
387 char buffer[BUFFER_SIZE];
390 int console_width, console_height;
391 int nb_printed_lines = 0;
394 initialize_matcher(use_regexp, case_sensitive, &matcher, pattern);
396 console_width = getmaxx(stdscr);
397 console_height = getmaxy(stdscr);
399 use_default_colors();
401 /* Add an empty line where we will print the modeline at the end */
405 /* If the regexp is erroneous, print a message saying so */
407 if(matcher.regexp_error) {
409 addnstr("Regexp syntax error", console_width);
413 /* Else, and we do have lines to select from, find a visible line. */
415 else if(nb_lines > 0) {
417 if(match(lines[*current_focus_line], &matcher)) {
418 new_focus_line = *current_focus_line;
420 new_focus_line = next_visible(*current_focus_line, nb_lines, lines,
422 if(new_focus_line < 0) {
423 new_focus_line = previous_visible(*current_focus_line, nb_lines, lines,
428 /* If we found a visible line and we should move, let's move */
430 if(new_focus_line >= 0 && motion != 0) {
431 int l = new_focus_line;
433 /* We want to go down, let's find the first visible line below */
434 for(m = 0; l >= 0 && m < motion; m++) {
435 l = next_visible(l, nb_lines, lines, &matcher);
441 /* We want to go up, let's find the first visible line above */
442 for(m = 0; l >= 0 && m < -motion; m++) {
443 l = previous_visible(l, nb_lines, lines, &matcher);
451 /* Here new_focus_line is either a line number matching the
454 if(new_focus_line >= 0) {
456 int first_line = new_focus_line, last_line = new_focus_line;
459 /* We find the first and last line to show, so that the total of
460 visible lines between them (them included) is
463 while(nb_match < console_height-1 &&
464 (first_line > 0 || last_line < nb_lines - 1)) {
468 while(first_line > 0 && !match(lines[first_line], &matcher)) {
471 if(match(lines[first_line], &matcher)) {
476 if(nb_match < console_height - 1 && last_line < nb_lines - 1) {
478 while(last_line < nb_lines - 1 && !match(lines[last_line], &matcher)) {
482 if(match(lines[last_line], &matcher)) {
488 /* Now we display them */
490 for(l = first_line; l <= last_line; l++) {
491 if(match(lines[l], &matcher)) {
494 while(lines[l][k] && k < BUFFER_SIZE - 2 && k < console_width - 2) {
495 buffer[k] = lines[l][k];
499 /* We fill the rest of the line with blanks if this is the
502 if(l == new_focus_line) {
503 while(k < console_width) {
513 /* Highlight the highlighted line ... */
515 if(l == new_focus_line) {
516 attron(attr_focus_line);
517 addnstr(buffer, console_width);
518 attroff(attr_focus_line);
520 addnstr(buffer, console_width);
527 /* If we are on a focused line and we moved, this become the new
531 *current_focus_line = new_focus_line;
535 *displayed_focus_line = new_focus_line;
537 if(nb_printed_lines == 0) {
539 addnstr("No selection", console_width);
544 /* Else, print a message saying that there are no lines to select from */
548 addnstr("Empty choice", console_width);
554 /* Draw the modeline */
558 attron(attr_modeline);
560 for(k = 0; k < console_width; k++) buffer[k] = ' ';
561 buffer[console_width] = '\0';
562 addnstr(buffer, console_width);
566 /* There must be a more elegant way of moving the cursor at a
567 location met during display */
574 cursor_x += strlen(title) + 1;
577 sprintf(buffer, "%d/%d ", nb_printed_lines, nb_lines);
579 cursor_x += strlen(buffer);
581 addnstr(pattern, cursor_position);
582 cursor_x += cursor_position;
584 if(pattern[cursor_position]) {
585 addstr(pattern + cursor_position);
590 /* Add a few info about the mode we are in (regexp and/or case
593 if(use_regexp || case_sensitive) {
610 attroff(attr_modeline);
615 free_matcher(&matcher);
618 /*********************************************************************/
620 void store_line(struct hash_table_t *hash_table,
621 const char *new_line,
622 int *nb_lines, char **lines) {
625 /* Remove the zsh history prefix */
627 if(zsh_history && *new_line == ':') {
628 while(*new_line && *new_line != ';') new_line++;
629 if(*new_line == ';') new_line++;
632 /* Remove the bash history prefix */
635 while(*new_line == ' ') new_line++;
636 while(*new_line >= '0' && *new_line <= '9') new_line++;
637 while(*new_line == ' ') new_line++;
640 /* Check for duplicates with the hash table and insert the line in
641 the list if necessary */
644 dup = add_and_get_previous_index(hash_table,
645 new_line, *nb_lines, lines);
651 lines[*nb_lines] = (char *) malloc((strlen(new_line) + 1) * sizeof(char));
652 strcpy(lines[*nb_lines], new_line);
654 /* The string was already in there, so we do not allocate a new
655 string but use the pointer to the first occurence of it */
656 lines[*nb_lines] = lines[dup];
663 void read_file(struct hash_table_t *hash_table,
664 const char *input_filename,
665 int nb_lines_max, int *nb_lines, char **lines) {
667 char raw_line[BUFFER_SIZE];
668 int start, end, eol, k;
671 file = fopen(input_filename, "r");
674 fprintf(stderr, "Selector: Can not open `%s'.\n", input_filename);
681 while(*nb_lines < nb_lines_max && (end > start || !feof(file))) {
684 /* Look for the end of a line in what is already in the buffer */
685 while(eol < end && raw_line[eol] != '\n') eol++;
687 /* if we did not find the of a line, move what has not been
688 processed and is in the buffer to the beginning of the buffer,
689 fill the buffer with new data from the file, and look for the
692 for(k = 0; k < end - start; k++) {
693 raw_line[k] = raw_line[k + start];
698 end += fread(raw_line + end, sizeof(char), BUFFER_SIZE - end, file);
699 while(eol < end && raw_line[eol] != '\n') eol++;
702 /* The end of the line is the buffer size, which means the line is
705 if(eol == BUFFER_SIZE) {
706 raw_line[BUFFER_SIZE - 1] = '\0';
707 fprintf(stderr, "Selector: Line too long (max is %d characters):\n",
709 fprintf(stderr, raw_line);
710 fprintf(stderr, "\n");
714 /* If we got a line, we replace the carriage return by a \0 to
717 raw_line[eol] = '\0';
719 store_line(hash_table, raw_line + start,
728 /*********************************************************************/
730 int main(int argc, char **argv) {
732 char input_filename[BUFFER_SIZE], output_filename[BUFFER_SIZE];
733 char pattern[BUFFER_SIZE];
736 int error = 0, show_help = 0;
737 int rest_are_files = 0;
739 int current_focus_line, displayed_focus_line;
741 int color_fg_modeline, color_bg_modeline;
742 int color_fg_highlight, color_bg_highlight;
744 char **lines, **labels;
746 struct hash_table_t *hash_table;
748 if(!ttyname(STDIN_FILENO)) {
749 fprintf(stderr, "Selector: The standard input is not a tty.\n");
753 color_fg_modeline = COLOR_WHITE;
754 color_bg_modeline = COLOR_BLACK;
755 color_fg_highlight = COLOR_BLACK;
756 color_bg_highlight = COLOR_YELLOW;
758 setlocale(LC_ALL, "");
760 strcpy(input_filename, "");
761 strcpy(output_filename, "");
765 while(!error && !show_help &&
766 i < argc && argv[i][0] == '-' &&
769 if(strcmp(argv[i], "-o") == 0) {
770 check_opt(argc, argv, i, 1, "<output filename>");
771 strncpy(output_filename, argv[i+1], BUFFER_SIZE);
775 else if(strcmp(argv[i], "-s") == 0) {
776 check_opt(argc, argv, i, 1, "<pattern separator>");
777 pattern_separator = argv[i+1][0];
781 else if(strcmp(argv[i], "-x") == 0) {
782 check_opt(argc, argv, i, 1, "<label separator>");
783 label_separator = argv[i+1][0];
787 else if(strcmp(argv[i], "-v") == 0) {
788 output_to_vt_buffer = 1;
792 else if(strcmp(argv[i], "-w") == 0) {
797 else if(strcmp(argv[i], "-m") == 0) {
802 else if(strcmp(argv[i], "-q") == 0) {
807 else if(strcmp(argv[i], "-f") == 0) {
808 check_opt(argc, argv, i, 1, "<input filename>");
809 strncpy(input_filename, argv[i+1], BUFFER_SIZE);
813 else if(strcmp(argv[i], "-i") == 0) {
818 else if(strcmp(argv[i], "-b") == 0) {
823 else if(strcmp(argv[i], "-z") == 0) {
828 else if(strcmp(argv[i], "-d") == 0) {
829 remove_duplicates = 1;
833 else if(strcmp(argv[i], "-e") == 0) {
838 else if(strcmp(argv[i], "-a") == 0) {
843 else if(strcmp(argv[i], "-t") == 0) {
844 check_opt(argc, argv, i, 1, "<title>");
846 title = (char *) malloc((strlen(argv[i+1]) + 1) * sizeof(char));
847 strcpy(title, argv[i+1]);
851 else if(strcmp(argv[i], "-l") == 0) {
852 check_opt(argc, argv, i, 1, "<maximum number of lines>");
853 nb_lines_max = string_to_positive_integer(argv[i+1]);
857 else if(strcmp(argv[i], "-c") == 0) {
858 check_opt(argc, argv, i, 4,
859 "<fg modeline> <bg modeline> <fg highlight> <bg highlight>");
860 color_fg_modeline = string_to_positive_integer(argv[i + 1]);
861 color_bg_modeline = string_to_positive_integer(argv[i + 2]);
862 color_fg_highlight = string_to_positive_integer(argv[i + 3]);
863 color_bg_highlight = string_to_positive_integer(argv[i + 4]);
867 else if(strcmp(argv[i], "--") == 0) {
872 else if(strcmp(argv[i], "-h") == 0) {
878 fprintf(stderr, "Selector: Unknown option %s.\n", argv[i]);
883 if(show_help || error) {
891 fprintf(out, "Selector version %s-R%s\n", VERSION, REVISION_NUMBER);
892 fprintf(out, "Written by Francois Fleuret <francois@fleuret.org>.\n");
894 fprintf(out, "Usage: %s [options] [<filename1> [<filename2> ...]]\n",
897 fprintf(out, " -h show this help\n");
898 fprintf(out, " -v inject the selected line in the tty\n");
899 fprintf(out, " -w quote control characters with ^Qs when using -v\n");
900 fprintf(out, " -d remove duplicated lines\n");
901 fprintf(out, " -b remove the bash history line prefix\n");
902 fprintf(out, " -z remove the zsh history line prefix\n");
903 fprintf(out, " -i invert the order of lines\n");
904 fprintf(out, " -e start in regexp mode\n");
905 fprintf(out, " -a start in case sensitive mode\n");
906 fprintf(out, " -m monochrome mode\n");
907 fprintf(out, " -q make a flash instead of a beep on an edition error\n");
908 fprintf(out, " -- all following arguments are filenames\n");
909 fprintf(out, " -t <title>\n");
910 fprintf(out, " add a title in the modeline\n");
911 fprintf(out, " -c <fg modeline> <bg modeline> <fg highlight> <bg highlight>\n");
912 fprintf(out, " set the display colors\n");
913 fprintf(out, " -o <output filename>\n");
914 fprintf(out, " set a file to write the selected line to\n");
915 fprintf(out, " -s <pattern separator>\n");
916 fprintf(out, " set the symbol to separate substrings in the pattern\n");
917 fprintf(out, " -x <label separator>\n");
918 fprintf(out, " set the symbol to terminate the label\n");
919 fprintf(out, " -l <max number of lines>\n");
920 fprintf(out, " set the maximum number of lines to take into account\n");
925 lines = (char **) malloc(nb_lines_max * sizeof(char *));
929 if(remove_duplicates) {
930 hash_table = new_hash_table(nb_lines_max * 10);
935 if(input_filename[0]) {
936 read_file(hash_table,
938 nb_lines_max, &nb_lines, lines);
942 read_file(hash_table,
944 nb_lines_max, &nb_lines, lines);
949 free_hash_table(hash_table);
952 /* Now remove the null strings */
955 for(k = 0; k < nb_lines; k++) {
957 lines[n++] = lines[k];
964 for(i = 0; i < nb_lines / 2; i++) {
965 char *s = lines[nb_lines - 1 - i];
966 lines[nb_lines - 1 - i] = lines[i];
971 /* Build the labels from the strings, take only the part before the
972 label_separator and transform control characters to printable
975 labels = (char **) malloc(nb_lines * sizeof(char *));
977 for(l = 0; l < nb_lines; l++) {
983 while(*t && *t != label_separator) {
988 labels[l] = (char *) malloc((e + 1) * sizeof(char));
991 while(*t && *t != label_separator) {
993 while(*u) { *s++ = *u++; }
1000 cursor_position = 0;
1002 /* Here we start to display with curse */
1007 intrflush(stdscr, FALSE);
1009 /* So that the arrow keys work */
1010 keypad(stdscr, TRUE);
1012 attr_error = A_STANDOUT;
1013 attr_modeline = A_REVERSE;
1014 attr_focus_line = A_STANDOUT;
1016 if(with_colors && has_colors()) {
1020 if(color_fg_modeline < 0 || color_fg_modeline >= COLORS ||
1021 color_bg_modeline < 0 || color_bg_modeline >= COLORS ||
1022 color_fg_highlight < 0 || color_bg_highlight >= COLORS ||
1023 color_bg_highlight < 0 || color_bg_highlight >= COLORS) {
1026 fprintf(stderr, "Selector: Color numbers have to be between 0 and %d.\n",
1031 init_pair(1, color_fg_modeline, color_bg_modeline);
1032 attr_modeline = COLOR_PAIR(1);
1034 init_pair(2, color_fg_highlight, color_bg_highlight);
1035 attr_focus_line = COLOR_PAIR(2);
1037 init_pair(3, COLOR_WHITE, COLOR_RED);
1038 attr_error = COLOR_PAIR(3);
1042 current_focus_line = 0;
1043 displayed_focus_line = 0;
1045 update_screen(¤t_focus_line, &displayed_focus_line,
1047 nb_lines, labels, cursor_position, pattern);
1054 if(key >= ' ' && key <= '~') { /* Insert character */
1055 insert_char(pattern, &cursor_position, key);
1058 else if(key == KEY_BACKSPACE ||
1059 key == '\010' || /* ^H */
1060 key == '\177') { /* ^? */
1061 backspace_char(pattern, &cursor_position);
1064 else if(key == KEY_DC ||
1065 key == '\004') { /* ^D */
1066 delete_char(pattern, &cursor_position);
1069 else if(key == KEY_HOME) {
1070 current_focus_line = 0;
1073 else if(key == KEY_END) {
1074 current_focus_line = nb_lines - 1;
1077 else if(key == KEY_NPAGE) {
1081 else if(key == KEY_PPAGE) {
1085 else if(key == KEY_DOWN ||
1086 key == '\016') { /* ^N */
1090 else if(key == KEY_UP ||
1091 key == '\020') { /* ^P */
1095 else if(key == KEY_LEFT ||
1096 key == '\002') { /* ^B */
1097 if(cursor_position > 0) cursor_position--;
1098 else error_feedback();
1101 else if(key == KEY_RIGHT ||
1102 key == '\006') { /* ^F */
1103 if(pattern[cursor_position]) cursor_position++;
1104 else error_feedback();
1107 else if(key == '\001') { /* ^A */
1108 cursor_position = 0;
1111 else if(key == '\005') { /* ^E */
1112 cursor_position = strlen(pattern);
1115 else if(key == '\022') { /* ^R */
1116 use_regexp = !use_regexp;
1119 else if(key == '\011') { /* ^I */
1120 case_sensitive = !case_sensitive;
1123 else if(key == '\025') { /* ^U */
1124 kill_before_cursor(pattern, &cursor_position);
1127 else if(key == '\013') { /* ^K */
1128 kill_after_cursor(pattern, &cursor_position);
1131 else if(key == '\014') { /* ^L */
1132 /* I suspect that we may sometime mess up the display, so ^L is
1133 here to force a full refresh */
1137 update_screen(¤t_focus_line, &displayed_focus_line,
1139 nb_lines, labels, cursor_position, pattern);
1141 } while(key != '\007' && /* ^G */
1142 key != '\033' && /* ^[ (escape) */
1149 /* Here we come back to standard display */
1151 if((key == KEY_ENTER || key == '\n')) {
1155 if(displayed_focus_line >= 0 && displayed_focus_line < nb_lines) {
1156 t = lines[displayed_focus_line];
1157 if(label_separator) {
1158 while(*t && *t != label_separator) t++;
1165 if(output_to_vt_buffer && t) {
1166 inject_into_tty_buffer(t, add_control_qs);
1169 if(output_filename[0]) {
1170 FILE *out = fopen(output_filename, "w");
1178 "Selector: Can not open %s for writing.\n",
1186 printf("Aborted.\n");
1189 for(l = 0; l < nb_lines; l++) {