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) {
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 /* A quick and dirty hash table */
133 /* The table itself stores indexes of the strings taken in a char**
134 table. When a string is added, if it was already in the table, the
135 new index replaces the previous one. */
142 hash_table_t *new_hash_table(int size) {
144 hash_table_t *hash_table;
146 hash_table = (hash_table_t *) malloc(sizeof(hash_table_t));
148 hash_table->size = size;
149 hash_table->entries = (int *) malloc(hash_table->size * sizeof(int));
151 for(k = 0; k < hash_table->size; k++) {
152 hash_table->entries[k] = -1;
158 void free_hash_table(hash_table_t *hash_table) {
159 free(hash_table->entries);
163 /* Adds new_string in the table, associated to new_index. If this
164 string was not already in the table, returns -1. Otherwise, returns
165 the previous index it had. */
167 int add_and_get_previous_index(hash_table_t *hash_table,
168 const char *new_string, int new_index,
171 unsigned int code = 0;
174 /* This is my recipe. I checked, it seems to work (as long as
175 hash_table->size is not a multiple of 387433 that should be
178 for(k = 0; new_string[k]; k++) {
179 code = code * 387433 + (unsigned int) (new_string[k]);
182 code = code % hash_table->size;
184 while(hash_table->entries[code] >= 0) {
185 /* There is a string with that code */
186 if(strcmp(new_string, strings[hash_table->entries[code]]) == 0) {
187 /* It is the same string, we keep a copy of the stored index */
188 int result = hash_table->entries[code];
189 /* Put the new one */
190 hash_table->entries[code] = new_index;
191 /* And return the previous one */
194 /* This collision was not the same string, let's move to the next
196 code = (code + 1) % hash_table->size;
199 /* This string was not already in there, store the index in the
200 table and return -1 */
202 hash_table->entries[code] = new_index;
206 /*********************************************************************
207 A matcher matches either with a collection of substrings, or with a
215 char *splitted_patterns, **patterns;
218 int match(char *string, matcher_t *matcher) {
220 if(matcher->nb_patterns >= 0) {
221 if(matcher->case_sensitive) {
222 for(n = 0; n < matcher->nb_patterns; n++) {
223 if(strstr(string, matcher->patterns[n]) == 0) return 0;
226 for(n = 0; n < matcher->nb_patterns; n++) {
227 if(strcasestr(string, matcher->patterns[n]) == 0) return 0;
232 return regexec(&matcher->preg, string, 0, 0, 0) == 0;
236 void free_matcher(matcher_t *matcher) {
237 if(matcher->nb_patterns < 0) {
238 if(!matcher->regexp_error) regfree(&matcher->preg);
240 free(matcher->splitted_patterns);
241 free(matcher->patterns);
245 void initialize_matcher(int use_regexp, int case_sensitive,
246 matcher_t *matcher, const char *pattern) {
248 char *t, *last_pattern_start;
252 matcher->nb_patterns = -1;
253 matcher->regexp_error = regcomp(&matcher->preg, pattern,
254 case_sensitive ? 0 : REG_ICASE);
256 matcher->regexp_error = 0;
257 matcher->nb_patterns = 1;
258 matcher->case_sensitive = case_sensitive;
260 for(s = pattern; *s; s++) {
261 if(*s == pattern_separator) {
262 matcher->nb_patterns++;
266 matcher->splitted_patterns =
267 (char *) malloc((strlen(pattern) + 1) * sizeof(char));
270 (char **) malloc(matcher->nb_patterns * sizeof(char *));
272 strcpy(matcher->splitted_patterns, pattern);
275 last_pattern_start = matcher->splitted_patterns;
276 for(t = matcher->splitted_patterns; n < matcher->nb_patterns; t++) {
277 if(*t == pattern_separator || *t == '\0') {
279 matcher->patterns[n++] = last_pattern_start;
280 last_pattern_start = t + 1;
286 /*********************************************************************
289 void delete_char(char *buffer, int *position) {
290 if(buffer[*position]) {
292 while(c < BUFFER_SIZE && buffer[c]) {
293 buffer[c] = buffer[c+1];
296 } else error_feedback();
299 void backspace_char(char *buffer, int *position) {
301 if(buffer[*position]) {
302 int c = *position - 1;
304 buffer[c] = buffer[c+1];
308 buffer[*position - 1] = '\0';
312 } else error_feedback();
315 void insert_char(char *buffer, int *position, char character) {
316 if(strlen(buffer) < BUFFER_SIZE - 1) {
318 char t = buffer[c], u;
327 buffer[(*position)++] = character;
328 } else error_feedback();
331 void kill_before_cursor(char *buffer, int *position) {
333 while(buffer[*position + s]) {
334 buffer[s] = buffer[*position + s];
341 void kill_after_cursor(char *buffer, int *position) {
342 buffer[*position] = '\0';
345 /*********************************************************************/
347 int previous_visible(int current_line, int nb_lines, char **lines,
348 matcher_t *matcher) {
349 int line = current_line - 1;
350 while(line >= 0 && !match(lines[line], matcher)) line--;
354 int next_visible(int current_line, int nb_lines, char **lines,
355 matcher_t *matcher) {
356 int line = current_line + 1;
357 while(line < nb_lines && !match(lines[line], matcher)) line++;
365 /*********************************************************************/
367 /* The line highlighted is the first one matching the matcher in that
368 order: (1) current_focus_line after motion, if it does not match,
369 then (2) the first with a greater index, if none matches, then (3)
370 the first with a lesser index.
372 The index of the line actually shown highlighted is written in
373 displayed_focus_line (it can be -1 if no line at all matches the
376 If there is a motion and a line is actually shown highlighted, its
377 value is written in current_focus_line. */
379 void update_screen(int *current_focus_line, int *displayed_focus_line,
381 int nb_lines, char **lines,
385 char buffer[BUFFER_SIZE];
388 int console_width, console_height;
389 int nb_printed_lines = 0;
392 initialize_matcher(use_regexp, case_sensitive, &matcher, pattern);
394 console_width = getmaxx(stdscr);
395 console_height = getmaxy(stdscr);
397 use_default_colors();
399 /* Add an empty line where we will print the modeline at the end */
403 /* If the regexp is erroneous, print a message saying so */
405 if(matcher.regexp_error) {
407 addnstr("Regexp syntax error", console_width);
411 /* Else, and we do have lines to select from, find a visible line. */
413 else if(nb_lines > 0) {
415 if(match(lines[*current_focus_line], &matcher)) {
416 new_focus_line = *current_focus_line;
418 new_focus_line = next_visible(*current_focus_line, nb_lines, lines,
420 if(new_focus_line < 0) {
421 new_focus_line = previous_visible(*current_focus_line, nb_lines, lines,
426 /* If we found a visible line and we should move, let's move */
428 if(new_focus_line >= 0 && motion != 0) {
429 int l = new_focus_line;
431 /* We want to go down, let's find the first visible line below */
432 for(m = 0; l >= 0 && m < motion; m++) {
433 l = next_visible(l, nb_lines, lines, &matcher);
439 /* We want to go up, let's find the first visible line above */
440 for(m = 0; l >= 0 && m < -motion; m++) {
441 l = previous_visible(l, nb_lines, lines, &matcher);
449 /* Here new_focus_line is either a line number matching the
452 if(new_focus_line >= 0) {
454 int first_line = new_focus_line, last_line = new_focus_line;
457 /* We find the first and last line to show, so that the total of
458 visible lines between them (them included) is
461 while(nb_match < console_height-1 &&
462 (first_line > 0 || last_line < nb_lines - 1)) {
466 while(first_line > 0 && !match(lines[first_line], &matcher)) {
469 if(match(lines[first_line], &matcher)) {
474 if(nb_match < console_height - 1 && last_line < nb_lines - 1) {
476 while(last_line < nb_lines - 1 && !match(lines[last_line], &matcher)) {
480 if(match(lines[last_line], &matcher)) {
486 /* Now we display them */
488 for(l = first_line; l <= last_line; l++) {
489 if(match(lines[l], &matcher)) {
492 while(lines[l][k] && k < BUFFER_SIZE - 2 && k < console_width - 2) {
493 buffer[k] = lines[l][k];
497 /* We fill the rest of the line with blanks if this is the
500 if(l == new_focus_line) {
501 while(k < console_width) {
511 /* Highlight the highlighted line ... */
513 if(l == new_focus_line) {
514 attron(attr_focus_line);
515 addnstr(buffer, console_width);
516 attroff(attr_focus_line);
518 addnstr(buffer, console_width);
525 /* If we are on a focused line and we moved, this become the new
529 *current_focus_line = new_focus_line;
533 *displayed_focus_line = new_focus_line;
535 if(nb_printed_lines == 0) {
537 addnstr("No selection", console_width);
542 /* Else, print a message saying that there are no lines to select from */
546 addnstr("Empty choice", console_width);
552 /* Draw the modeline */
556 attron(attr_modeline);
558 for(k = 0; k < console_width; k++) buffer[k] = ' ';
559 buffer[console_width] = '\0';
560 addnstr(buffer, console_width);
564 /* There must be a more elegant way of moving the cursor at a
565 location met during display */
572 cursor_x += strlen(title) + 1;
575 sprintf(buffer, "%d/%d ", nb_printed_lines, nb_lines);
577 cursor_x += strlen(buffer);
579 addnstr(pattern, cursor_position);
580 cursor_x += cursor_position;
582 if(pattern[cursor_position]) {
583 addstr(pattern + cursor_position);
588 /* Add a few info about the mode we are in (regexp and/or case
591 if(use_regexp || case_sensitive) {
608 attroff(attr_modeline);
613 free_matcher(&matcher);
616 /*********************************************************************/
618 void store_line(hash_table_t *hash_table,
619 const char *new_line,
620 int *nb_lines, char **lines) {
623 /* Remove the zsh history prefix */
625 if(zsh_history && *new_line == ':') {
626 while(*new_line && *new_line != ';') new_line++;
627 if(*new_line == ';') new_line++;
630 /* Remove the bash history prefix */
633 while(*new_line == ' ') new_line++;
634 while(*new_line >= '0' && *new_line <= '9') new_line++;
635 while(*new_line == ' ') new_line++;
638 /* Check for duplicates with the hash table and insert the line in
639 the list if necessary */
642 dup = add_and_get_previous_index(hash_table,
643 new_line, *nb_lines, lines);
649 lines[*nb_lines] = (char *) malloc((strlen(new_line) + 1) * sizeof(char));
650 strcpy(lines[*nb_lines], new_line);
652 /* The string was already in there, so we do not allocate a new
653 string but use the pointer to the first occurence of it */
654 lines[*nb_lines] = lines[dup];
661 void read_file(hash_table_t *hash_table,
662 const char *input_filename,
663 int nb_lines_max, int *nb_lines, char **lines) {
665 char raw_line[BUFFER_SIZE];
666 int start, end, eol, k;
669 file = fopen(input_filename, "r");
672 fprintf(stderr, "Selector: Can not open `%s'.\n", input_filename);
679 while(*nb_lines < nb_lines_max && (end > start || !feof(file))) {
682 /* Look for the end of a line in what is already in the buffer */
683 while(eol < end && raw_line[eol] != '\n') eol++;
685 /* if we did not find the of a line, move what has not been
686 processed and is in the buffer to the beginning of the buffer,
687 fill the buffer with new data from the file, and look for the
690 for(k = 0; k < end - start; k++) {
691 raw_line[k] = raw_line[k + start];
696 end += fread(raw_line + end, sizeof(char), BUFFER_SIZE - end, file);
697 while(eol < end && raw_line[eol] != '\n') eol++;
700 /* The end of the line is the buffer size, which means the line is
703 if(eol == BUFFER_SIZE) {
704 raw_line[BUFFER_SIZE - 1] = '\0';
705 fprintf(stderr, "Selector: Line too long (max is %d characters):\n",
707 fprintf(stderr, raw_line);
708 fprintf(stderr, "\n");
712 /* If we got a line, we replace the carriage return by a \0 to
715 raw_line[eol] = '\0';
717 store_line(hash_table, raw_line + start,
726 /*********************************************************************/
728 int main(int argc, char **argv) {
730 char input_filename[BUFFER_SIZE], output_filename[BUFFER_SIZE];
731 char pattern[BUFFER_SIZE];
734 int error = 0, show_help = 0;
735 int rest_are_files = 0;
737 int current_focus_line, displayed_focus_line;
739 int color_fg_modeline, color_bg_modeline;
740 int color_fg_highlight, color_bg_highlight;
742 char **lines, **labels;
744 hash_table_t *hash_table;
746 if(!ttyname(STDIN_FILENO)) {
747 fprintf(stderr, "Selector: The standard input is not a tty.\n");
751 color_fg_modeline = COLOR_WHITE;
752 color_bg_modeline = COLOR_BLACK;
753 color_fg_highlight = COLOR_BLACK;
754 color_bg_highlight = COLOR_YELLOW;
756 setlocale(LC_ALL, "");
758 strcpy(input_filename, "");
759 strcpy(output_filename, "");
763 while(!error && !show_help &&
764 i < argc && argv[i][0] == '-' &&
767 if(strcmp(argv[i], "-o") == 0) {
768 check_opt(argc, argv, i, 1, "<output filename>");
769 strncpy(output_filename, argv[i+1], BUFFER_SIZE);
773 else if(strcmp(argv[i], "-s") == 0) {
774 check_opt(argc, argv, i, 1, "<pattern separator>");
775 pattern_separator = argv[i+1][0];
779 else if(strcmp(argv[i], "-x") == 0) {
780 check_opt(argc, argv, i, 1, "<label separator>");
781 label_separator = argv[i+1][0];
785 else if(strcmp(argv[i], "-v") == 0) {
786 output_to_vt_buffer = 1;
790 else if(strcmp(argv[i], "-w") == 0) {
795 else if(strcmp(argv[i], "-m") == 0) {
800 else if(strcmp(argv[i], "-q") == 0) {
805 else if(strcmp(argv[i], "-f") == 0) {
806 check_opt(argc, argv, i, 1, "<input filename>");
807 strncpy(input_filename, argv[i+1], BUFFER_SIZE);
811 else if(strcmp(argv[i], "-i") == 0) {
816 else if(strcmp(argv[i], "-b") == 0) {
821 else if(strcmp(argv[i], "-z") == 0) {
826 else if(strcmp(argv[i], "-d") == 0) {
827 remove_duplicates = 1;
831 else if(strcmp(argv[i], "-e") == 0) {
836 else if(strcmp(argv[i], "-a") == 0) {
841 else if(strcmp(argv[i], "-t") == 0) {
842 check_opt(argc, argv, i, 1, "<title>");
844 title = (char *) malloc((strlen(argv[i+1]) + 1) * sizeof(char));
845 strcpy(title, argv[i+1]);
849 else if(strcmp(argv[i], "-l") == 0) {
850 check_opt(argc, argv, i, 1, "<maximum number of lines>");
851 nb_lines_max = string_to_positive_integer(argv[i+1]);
855 else if(strcmp(argv[i], "-c") == 0) {
856 check_opt(argc, argv, i, 4,
857 "<fg modeline> <bg modeline> <fg highlight> <bg highlight>");
858 color_fg_modeline = string_to_positive_integer(argv[i + 1]);
859 color_bg_modeline = string_to_positive_integer(argv[i + 2]);
860 color_fg_highlight = string_to_positive_integer(argv[i + 3]);
861 color_bg_highlight = string_to_positive_integer(argv[i + 4]);
865 else if(strcmp(argv[i], "--") == 0) {
870 else if(strcmp(argv[i], "-h") == 0) {
876 fprintf(stderr, "Selector: Unknown option %s.\n", argv[i]);
881 if(show_help || error) {
889 fprintf(out, "Selector version %s-R%s\n", VERSION, REVISION_NUMBER);
890 fprintf(out, "Written by Francois Fleuret <francois@fleuret.org>.\n");
892 fprintf(out, "Usage: %s [options] [<filename1> [<filename2> ...]]\n",
895 fprintf(out, " -h show this help\n");
896 fprintf(out, " -v inject the selected line in the tty\n");
897 fprintf(out, " -w quote control characters with ^Qs when using -v\n");
898 fprintf(out, " -d remove duplicated lines\n");
899 fprintf(out, " -b remove the bash history line prefix\n");
900 fprintf(out, " -z remove the zsh history line prefix\n");
901 fprintf(out, " -i invert the order of lines\n");
902 fprintf(out, " -e start in regexp mode\n");
903 fprintf(out, " -a start in case sensitive mode\n");
904 fprintf(out, " -m monochrome mode\n");
905 fprintf(out, " -q make a flash instead of a beep on an edition error\n");
906 fprintf(out, " -- all following arguments are filenames\n");
907 fprintf(out, " -t <title>\n");
908 fprintf(out, " add a title in the modeline\n");
909 fprintf(out, " -c <fg modeline> <bg modeline> <fg highlight> <bg highlight>\n");
910 fprintf(out, " set the display colors\n");
911 fprintf(out, " -o <output filename>\n");
912 fprintf(out, " set a file to write the selected line to\n");
913 fprintf(out, " -s <pattern separator>\n");
914 fprintf(out, " set the symbol to separate substrings in the pattern\n");
915 fprintf(out, " -x <label separator>\n");
916 fprintf(out, " set the symbol to terminate the label\n");
917 fprintf(out, " -l <max number of lines>\n");
918 fprintf(out, " set the maximum number of lines to take into account\n");
923 lines = (char **) malloc(nb_lines_max * sizeof(char *));
927 if(remove_duplicates) {
928 hash_table = new_hash_table(nb_lines_max * 10);
933 if(input_filename[0]) {
934 read_file(hash_table,
936 nb_lines_max, &nb_lines, lines);
940 read_file(hash_table,
942 nb_lines_max, &nb_lines, lines);
947 free_hash_table(hash_table);
950 /* Now remove the null strings */
953 for(k = 0; k < nb_lines; k++) {
955 lines[n++] = lines[k];
962 for(i = 0; i < nb_lines / 2; i++) {
963 char *s = lines[nb_lines - 1 - i];
964 lines[nb_lines - 1 - i] = lines[i];
969 /* Build the labels from the strings, take only the part before the
970 label_separator and transform control characters to printable
973 labels = (char **) malloc(nb_lines * sizeof(char *));
975 for(l = 0; l < nb_lines; l++) {
981 while(*t && *t != label_separator) {
986 labels[l] = (char *) malloc((e + 1) * sizeof(char));
989 while(*t && *t != label_separator) {
991 while(*u) { *s++ = *u++; }
1000 /* Here we start to display with curse */
1006 intrflush(stdscr, FALSE);
1008 /* So that the arrow keys work */
1009 keypad(stdscr, TRUE);
1011 attr_error = A_STANDOUT;
1012 attr_modeline = A_REVERSE;
1013 attr_focus_line = A_STANDOUT;
1015 if(with_colors && has_colors()) {
1019 if(color_fg_modeline < 0 || color_fg_modeline >= COLORS ||
1020 color_bg_modeline < 0 || color_bg_modeline >= COLORS ||
1021 color_fg_highlight < 0 || color_bg_highlight >= COLORS ||
1022 color_bg_highlight < 0 || color_bg_highlight >= COLORS) {
1025 fprintf(stderr, "Selector: Color numbers have to be between 0 and %d.\n",
1030 init_pair(1, color_fg_modeline, color_bg_modeline);
1031 attr_modeline = COLOR_PAIR(1);
1033 init_pair(2, color_fg_highlight, color_bg_highlight);
1034 attr_focus_line = COLOR_PAIR(2);
1036 init_pair(3, COLOR_WHITE, COLOR_RED);
1037 attr_error = COLOR_PAIR(3);
1041 current_focus_line = 0;
1042 displayed_focus_line = 0;
1044 update_screen(¤t_focus_line, &displayed_focus_line,
1046 nb_lines, labels, cursor_position, pattern);
1053 if(key >= ' ' && key <= '~') { /* Insert character */
1054 insert_char(pattern, &cursor_position, key);
1057 else if(key == KEY_BACKSPACE ||
1058 key == '\010' || /* ^H */
1059 key == '\177') { /* ^? */
1060 backspace_char(pattern, &cursor_position);
1063 else if(key == KEY_DC ||
1064 key == '\004') { /* ^D */
1065 delete_char(pattern, &cursor_position);
1068 else if(key == KEY_HOME) {
1069 current_focus_line = 0;
1072 else if(key == KEY_END) {
1073 current_focus_line = nb_lines - 1;
1076 else if(key == KEY_NPAGE) {
1080 else if(key == KEY_PPAGE) {
1084 else if(key == KEY_DOWN ||
1085 key == '\016') { /* ^N */
1089 else if(key == KEY_UP ||
1090 key == '\020') { /* ^P */
1094 else if(key == KEY_LEFT ||
1095 key == '\002') { /* ^B */
1096 if(cursor_position > 0) cursor_position--;
1097 else error_feedback();
1100 else if(key == KEY_RIGHT ||
1101 key == '\006') { /* ^F */
1102 if(pattern[cursor_position]) cursor_position++;
1103 else error_feedback();
1106 else if(key == '\001') { /* ^A */
1107 cursor_position = 0;
1110 else if(key == '\005') { /* ^E */
1111 cursor_position = strlen(pattern);
1114 else if(key == '\022') { /* ^R */
1115 use_regexp = !use_regexp;
1118 else if(key == '\011') { /* ^I */
1119 case_sensitive = !case_sensitive;
1122 else if(key == '\025') { /* ^U */
1123 kill_before_cursor(pattern, &cursor_position);
1126 else if(key == '\013') { /* ^K */
1127 kill_after_cursor(pattern, &cursor_position);
1130 else if(key == '\014') { /* ^L */
1131 /* I suspect that we may sometime mess up the display */
1135 update_screen(¤t_focus_line, &displayed_focus_line,
1137 nb_lines, labels, cursor_position, pattern);
1139 } while(key != '\007' && /* ^G */
1140 key != '\033' && /* ^[ (escape) */
1147 /* Here we come back to standard display */
1149 if((key == KEY_ENTER || key == '\n')) {
1153 if(displayed_focus_line >= 0 && displayed_focus_line < nb_lines) {
1154 t = lines[displayed_focus_line];
1155 if(label_separator) {
1156 while(*t && *t != label_separator) t++;
1163 if(output_to_vt_buffer && t) {
1164 inject_into_tty_buffer(t);
1167 if(output_filename[0]) {
1168 FILE *out = fopen(output_filename, "w");
1176 "Selector: Can not open %s for writing.\n",
1184 printf("Aborted.\n");
1187 for(l = 0; l < nb_lines; l++) {