3 * selector is a simple command line utility for selection of strings
4 * with a dynamic pattern-matching.
6 * Copyright (c) 2009, 2010 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>
49 #define BUFFER_SIZE 4096
51 /* Yeah, global variables! */
53 int nb_lines_max = 1000;
54 char pattern_separator = ';';
55 char label_separator = '\0';
56 int output_to_vt_buffer = 0;
57 int add_control_qs = 0;
61 int inverse_order = 0;
62 int remove_duplicates = 0;
64 int case_sensitive = 0;
68 int attr_modeline, attr_focus_line, attr_error;
70 /********************************************************************/
72 /* malloc with error checking. */
74 void *safe_malloc(size_t n) {
77 printf("Can not allocate memory: %s\n", strerror(errno));
83 /*********************************************************************/
85 void inject_into_tty_buffer(char *string, int add_control_qs) {
86 struct termios oldtio, newtio;
88 const char control_q = '\021';
89 tcgetattr(STDIN_FILENO, &oldtio);
90 memset(&newtio, 0, sizeof(newtio));
91 /* Set input mode (non-canonical, *no echo*,...) */
92 tcsetattr(STDIN_FILENO, TCSANOW, &newtio);
93 /* Put the selected string in the tty input buffer */
94 for(k = string; *k; k++) {
95 if(add_control_qs && !(*k >= ' ' && *k <= '~')) {
96 /* Add ^Q to quote control characters */
97 ioctl(STDIN_FILENO, TIOCSTI, &control_q);
99 ioctl(STDIN_FILENO, TIOCSTI, k);
101 /* Restore the old settings */
102 tcsetattr(STDIN_FILENO, TCSANOW, &oldtio);
105 /*********************************************************************/
107 void check_opt(int argc, char **argv, int n_opt, int n, const char *help) {
108 if(n_opt + n >= argc) {
109 fprintf(stderr, "Selector: Missing argument for %s, expecting %s.\n",
115 int string_to_positive_integer(char *string) {
121 for(s = string; *s && *s != ','; s++) {
122 if(*s >= '0' && *s <= '9') {
123 result = result * 10 + (int) (*s - '0');
130 "Selector: Value `%s' is not a positive integer.\n",
138 void error_feedback() {
146 void print_help(FILE *out) {
148 fprintf(out, "Selector version %s (%s)\n", VERSION, UNAME);
149 fprintf(out, "Written by Francois Fleuret <francois@fleuret.org>.\n");
151 fprintf(out, "Usage: selector [options] [<filename1> [<filename2> ...]]\n");
153 fprintf(out, " -h show this help\n");
154 fprintf(out, " -v inject the selected line in the tty\n");
155 fprintf(out, " -w quote control characters with ^Qs when using -v\n");
156 fprintf(out, " -d remove duplicated lines\n");
157 fprintf(out, " -b remove the bash history line prefix\n");
158 fprintf(out, " -z remove the zsh history line prefix\n");
159 fprintf(out, " -i invert the order of lines\n");
160 fprintf(out, " -e start in regexp mode\n");
161 fprintf(out, " -a start in case sensitive mode\n");
162 fprintf(out, " -m monochrome mode\n");
163 fprintf(out, " -q make a flash instead of a beep on an edition error\n");
164 fprintf(out, " -- all following arguments are filenames\n");
165 fprintf(out, " -t <title>\n");
166 fprintf(out, " add a title in the modeline\n");
167 fprintf(out, " -c <fg modeline> <bg modeline> <fg highlight> <bg highlight>\n");
168 fprintf(out, " set the display colors\n");
169 fprintf(out, " -o <output filename>\n");
170 fprintf(out, " set a file to write the selected line to\n");
171 fprintf(out, " -s <pattern separator>\n");
172 fprintf(out, " set the symbol to separate substrings in the pattern\n");
173 fprintf(out, " -x <label separator>\n");
174 fprintf(out, " set the symbol to terminate the label\n");
175 fprintf(out, " -l <max number of lines>\n");
176 fprintf(out, " set the maximum number of lines to take into account\n");
180 /*********************************************************************/
182 /* A quick and dirty hash table */
184 /* The table itself stores indexes of the strings taken in a char**
185 table. When a string is added, if it was already in the table, the
186 new index replaces the previous one. */
188 struct hash_table_t {
193 struct hash_table_t *new_hash_table(int size) {
195 struct hash_table_t *hash_table;
197 hash_table = safe_malloc(sizeof(struct hash_table_t));
199 hash_table->size = size;
200 hash_table->entries = safe_malloc(hash_table->size * sizeof(int));
202 for(k = 0; k < hash_table->size; k++) {
203 hash_table->entries[k] = -1;
209 void free_hash_table(struct hash_table_t *hash_table) {
210 free(hash_table->entries);
214 /* Adds new_string in the table, associated to new_index. If this
215 string was not already in the table, returns -1. Otherwise, returns
216 the previous index it had. */
218 int add_and_get_previous_index(struct hash_table_t *hash_table,
219 const char *new_string, int new_index,
222 unsigned int code = 0;
225 /* This is my recipe. I checked, it seems to work (as long as
226 hash_table->size is not a multiple of 387433 that should be
229 for(k = 0; new_string[k]; k++) {
230 code = code * 387433 + (unsigned int) (new_string[k]);
233 code = code % hash_table->size;
235 while(hash_table->entries[code] >= 0) {
236 /* There is a string with that code */
237 if(strcmp(new_string, strings[hash_table->entries[code]]) == 0) {
238 /* It is the same string, we keep a copy of the stored index */
239 int result = hash_table->entries[code];
240 /* Put the new one */
241 hash_table->entries[code] = new_index;
242 /* And return the previous one */
245 /* This collision was not the same string, let's move to the next
247 code = (code + 1) % hash_table->size;
250 /* This string was not already in there, store the index in the
251 table and return -1 */
253 hash_table->entries[code] = new_index;
257 /*********************************************************************
258 A matcher matches either with a collection of substrings, or with a
266 char *splitted_patterns, **patterns;
269 int match(char *string, matcher_t *matcher) {
271 if(matcher->nb_patterns >= 0) {
272 if(matcher->case_sensitive) {
273 for(n = 0; n < matcher->nb_patterns; n++) {
274 if(strstr(string, matcher->patterns[n]) == 0) return 0;
277 for(n = 0; n < matcher->nb_patterns; n++) {
278 if(strcasestr(string, matcher->patterns[n]) == 0) return 0;
283 return regexec(&matcher->preg, string, 0, 0, 0) == 0;
287 void free_matcher(matcher_t *matcher) {
288 if(matcher->nb_patterns < 0) {
289 if(!matcher->regexp_error) regfree(&matcher->preg);
291 free(matcher->splitted_patterns);
292 free(matcher->patterns);
296 void initialize_matcher(int use_regexp, int case_sensitive,
297 matcher_t *matcher, const char *pattern) {
299 char *t, *last_pattern_start;
303 matcher->nb_patterns = -1;
304 matcher->regexp_error = regcomp(&matcher->preg, pattern,
305 case_sensitive ? 0 : REG_ICASE);
307 matcher->regexp_error = 0;
308 matcher->nb_patterns = 1;
309 matcher->case_sensitive = case_sensitive;
311 for(s = pattern; *s; s++) {
312 if(*s == pattern_separator) {
313 matcher->nb_patterns++;
317 matcher->splitted_patterns =
318 safe_malloc((strlen(pattern) + 1) * sizeof(char));
321 safe_malloc(matcher->nb_patterns * sizeof(char *));
323 strcpy(matcher->splitted_patterns, pattern);
326 last_pattern_start = matcher->splitted_patterns;
327 for(t = matcher->splitted_patterns; n < matcher->nb_patterns; t++) {
328 if(*t == pattern_separator || *t == '\0') {
330 matcher->patterns[n++] = last_pattern_start;
331 last_pattern_start = t + 1;
337 /*********************************************************************
340 void delete_char(char *buffer, int *position) {
341 if(buffer[*position]) {
343 while(c < BUFFER_SIZE && buffer[c]) {
344 buffer[c] = buffer[c+1];
347 } else error_feedback();
350 void backspace_char(char *buffer, int *position) {
352 if(buffer[*position]) {
353 int c = *position - 1;
355 buffer[c] = buffer[c+1];
359 buffer[*position - 1] = '\0';
363 } else error_feedback();
366 void insert_char(char *buffer, int *position, char character) {
367 if(strlen(buffer) < BUFFER_SIZE - 1) {
369 char t = buffer[c], u;
378 buffer[(*position)++] = character;
379 } else error_feedback();
382 void kill_before_cursor(char *buffer, int *position) {
384 while(buffer[*position + s]) {
385 buffer[s] = buffer[*position + s];
392 void kill_after_cursor(char *buffer, int *position) {
393 buffer[*position] = '\0';
396 /*********************************************************************/
398 int previous_visible(int current_line, int nb_lines, char **lines,
399 matcher_t *matcher) {
400 int line = current_line - 1;
401 while(line >= 0 && !match(lines[line], matcher)) line--;
405 int next_visible(int current_line, int nb_lines, char **lines,
406 matcher_t *matcher) {
407 int line = current_line + 1;
408 while(line < nb_lines && !match(lines[line], matcher)) line++;
416 /*********************************************************************/
418 /* The line highlighted is the first one matching the matcher in that
419 order: (1) current_focus_line after motion, if it does not match,
420 then (2) the first with a greater index, if none matches, then (3)
421 the first with a lesser index.
423 The index of the line actually shown highlighted is written in
424 displayed_focus_line (it can be -1 if no line at all matches the
427 If there is a motion and a line is actually shown highlighted, its
428 value is written in current_focus_line. */
430 void update_screen(int *current_focus_line, int *displayed_focus_line,
432 int nb_lines, char **lines,
436 char buffer[BUFFER_SIZE];
439 int console_width, console_height;
440 int nb_printed_lines = 0;
443 initialize_matcher(use_regexp, case_sensitive, &matcher, pattern);
445 console_width = getmaxx(stdscr);
446 console_height = getmaxy(stdscr);
448 use_default_colors();
450 /* Add an empty line where we will print the modeline at the end */
454 /* If the regexp is erroneous, print a message saying so */
456 if(matcher.regexp_error) {
458 addnstr("Regexp syntax error", console_width);
462 /* Else, and we do have lines to select from, find a visible line. */
464 else if(nb_lines > 0) {
466 if(match(lines[*current_focus_line], &matcher)) {
467 new_focus_line = *current_focus_line;
469 new_focus_line = next_visible(*current_focus_line, nb_lines, lines,
471 if(new_focus_line < 0) {
472 new_focus_line = previous_visible(*current_focus_line, nb_lines, lines,
477 /* If we found a visible line and we should move, let's move */
479 if(new_focus_line >= 0 && motion != 0) {
480 int l = new_focus_line;
482 /* We want to go down, let's find the first visible line below */
483 for(m = 0; l >= 0 && m < motion; m++) {
484 l = next_visible(l, nb_lines, lines, &matcher);
490 /* We want to go up, let's find the first visible line above */
491 for(m = 0; l >= 0 && m < -motion; m++) {
492 l = previous_visible(l, nb_lines, lines, &matcher);
500 /* Here new_focus_line is either a line number matching the
503 if(new_focus_line >= 0) {
505 int first_line = new_focus_line, last_line = new_focus_line;
508 /* We find the first and last line to show, so that the total of
509 visible lines between them (them included) is
512 while(nb_match < console_height-1 &&
513 (first_line > 0 || last_line < nb_lines - 1)) {
517 while(first_line > 0 && !match(lines[first_line], &matcher)) {
520 if(match(lines[first_line], &matcher)) {
525 if(nb_match < console_height - 1 && last_line < nb_lines - 1) {
527 while(last_line < nb_lines - 1 && !match(lines[last_line], &matcher)) {
531 if(match(lines[last_line], &matcher)) {
537 /* Now we display them */
539 for(l = first_line; l <= last_line; l++) {
540 if(match(lines[l], &matcher)) {
543 while(lines[l][k] && k < BUFFER_SIZE - 2 && k < console_width - 2) {
544 buffer[k] = lines[l][k];
548 /* We fill the rest of the line with blanks if this is the
551 if(l == new_focus_line) {
552 while(k < console_width) {
562 /* Highlight the highlighted line ... */
564 if(l == new_focus_line) {
565 attron(attr_focus_line);
566 addnstr(buffer, console_width);
567 attroff(attr_focus_line);
569 addnstr(buffer, console_width);
576 /* If we are on a focused line and we moved, this become the new
580 *current_focus_line = new_focus_line;
584 *displayed_focus_line = new_focus_line;
586 if(nb_printed_lines == 0) {
588 addnstr("No selection", console_width);
593 /* Else, print a message saying that there are no lines to select from */
597 addnstr("Empty choice", console_width);
603 /* Draw the modeline */
607 attron(attr_modeline);
609 for(k = 0; k < console_width; k++) buffer[k] = ' ';
610 buffer[console_width] = '\0';
611 addnstr(buffer, console_width);
615 /* There must be a more elegant way of moving the cursor at a
616 location met during display */
623 cursor_x += strlen(title) + 1;
626 sprintf(buffer, "%d/%d ", nb_printed_lines, nb_lines);
628 cursor_x += strlen(buffer);
630 addnstr(pattern, cursor_position);
631 cursor_x += cursor_position;
633 if(pattern[cursor_position]) {
634 addstr(pattern + cursor_position);
639 /* Add a few info about the mode we are in (regexp and/or case
642 if(use_regexp || case_sensitive) {
659 attroff(attr_modeline);
664 free_matcher(&matcher);
667 /*********************************************************************/
669 void store_line(struct hash_table_t *hash_table,
670 const char *new_line,
671 int *nb_lines, char **lines) {
674 /* Remove the zsh history prefix */
676 if(zsh_history && *new_line == ':') {
677 while(*new_line && *new_line != ';') new_line++;
678 if(*new_line == ';') new_line++;
681 /* Remove the bash history prefix */
684 while(*new_line == ' ') new_line++;
685 while(*new_line >= '0' && *new_line <= '9') new_line++;
686 while(*new_line == ' ') new_line++;
689 /* Check for duplicates with the hash table and insert the line in
690 the list if necessary */
693 dup = add_and_get_previous_index(hash_table,
694 new_line, *nb_lines, lines);
700 lines[*nb_lines] = safe_malloc((strlen(new_line) + 1) * sizeof(char));
701 strcpy(lines[*nb_lines], new_line);
703 /* The string was already in there, so we do not allocate a new
704 string but use the pointer to the first occurence of it */
705 lines[*nb_lines] = lines[dup];
712 void read_file(struct hash_table_t *hash_table,
713 const char *input_filename,
714 int nb_lines_max, int *nb_lines, char **lines) {
716 char raw_line[BUFFER_SIZE];
717 int start, end, eol, k;
720 file = fopen(input_filename, "r");
723 fprintf(stderr, "Selector: Can not open `%s'.\n", input_filename);
730 while(*nb_lines < nb_lines_max && (end > start || !feof(file))) {
733 /* Look for the end of a line in what is already in the buffer */
734 while(eol < end && raw_line[eol] != '\n') eol++;
736 /* if we did not find the of a line, move what has not been
737 processed and is in the buffer to the beginning of the buffer,
738 fill the buffer with new data from the file, and look for the
741 for(k = 0; k < end - start; k++) {
742 raw_line[k] = raw_line[k + start];
747 end += fread(raw_line + end, sizeof(char), BUFFER_SIZE - end, file);
748 while(eol < end && raw_line[eol] != '\n') eol++;
751 /* The end of the line is the buffer size, which means the line is
754 if(eol == BUFFER_SIZE) {
755 raw_line[BUFFER_SIZE - 1] = '\0';
756 fprintf(stderr, "Selector: Line too long (max is %d characters):\n",
758 fprintf(stderr, raw_line);
759 fprintf(stderr, "\n");
763 /* If we got a line, we replace the carriage return by a \0 to
766 raw_line[eol] = '\0';
768 store_line(hash_table, raw_line + start,
777 /*********************************************************************/
779 static struct option long_options[] = {
780 { "output-file", 1, 0, 'o' },
781 { "pattern-separator", 1, 0, 's' },
782 { "label-separator", 1, 0, 'x' },
783 { "inject-in-tty", no_argument, 0, 'v' },
784 { "add-control-qs", no_argument, 0, 'w' },
785 { "monochrome", no_argument, 0, 'm' },
786 { "no-beep", no_argument, 0, 'q' },
787 { "input-file", 1, 0, 'f' },
788 { "revert-order", no_argument, 0, 'i' },
789 { "remove-bash-prefix", no_argument, 0, 'b' },
790 { "remove-zsh-prefix", no_argument, 0, 'z' },
791 { "remove-duplicates", no_argument, 0, 'd' },
792 { "regexp", no_argument, 0, 'e' },
793 { "case-sensitive", no_argument, 0, 'a' },
794 { "title", 1, 0, 't' },
795 { "number-of-lines", 1, 0, 'l' },
796 { "colors", 1, 0, 'c' },
797 { "rest-are-files", no_argument, 0, '-' },
798 { "help", no_argument, 0, 'h' },
802 int main(int argc, char **argv) {
804 char input_filename[BUFFER_SIZE], output_filename[BUFFER_SIZE];
806 char pattern[BUFFER_SIZE];
809 int error = 0, show_help = 0;
810 int rest_are_files = 0;
812 int current_focus_line, displayed_focus_line;
814 int color_fg_modeline, color_bg_modeline;
815 int color_fg_highlight, color_bg_highlight;
817 char **lines, **labels;
819 struct hash_table_t *hash_table;
821 if(!isatty(STDIN_FILENO)) {
822 fprintf(stderr, "Selector: The standard input is not a tty.\n");
826 color_fg_modeline = COLOR_WHITE;
827 color_bg_modeline = COLOR_BLACK;
828 color_fg_highlight = COLOR_BLACK;
829 color_bg_highlight = COLOR_YELLOW;
831 setlocale(LC_ALL, "");
833 strcpy(input_filename, "");
834 strcpy(output_filename, "");
836 while (!rest_are_files &&
837 (c = getopt_long(argc, argv, "o:s:x:vwmqf:ibzdeat:l:c:-h",
838 long_options, NULL)) != -1) {
843 strncpy(output_filename, optarg, BUFFER_SIZE);
847 pattern_separator = optarg[0];
851 label_separator = optarg[0];
855 output_to_vt_buffer = 1;
871 strncpy(input_filename, optarg, BUFFER_SIZE);
887 remove_duplicates = 1;
900 title = safe_malloc((strlen(optarg) + 1) * sizeof(char));
901 strcpy(title, optarg);
905 nb_lines_max = string_to_positive_integer(optarg);
910 color_fg_modeline = string_to_positive_integer(s);
911 while(*s && *s != ',') s++; if(*s == ',') { s++; }
912 color_bg_modeline = string_to_positive_integer(s);
913 while(*s && *s != ',') s++; if(*s == ',') { s++; }
914 color_fg_highlight = string_to_positive_integer(s);
915 while(*s && *s != ',') s++; if(*s == ',') { s++; }
916 color_bg_highlight = string_to_positive_integer(s);
933 if(show_help || error) {
943 lines = safe_malloc(nb_lines_max * sizeof(char *));
947 if(remove_duplicates) {
948 hash_table = new_hash_table(nb_lines_max * 10);
953 if(input_filename[0]) {
954 read_file(hash_table,
956 nb_lines_max, &nb_lines, lines);
959 while(optind < argc) {
960 read_file(hash_table,
962 nb_lines_max, &nb_lines, lines);
967 free_hash_table(hash_table);
970 /* Now remove the null strings */
973 for(k = 0; k < nb_lines; k++) {
975 lines[n++] = lines[k];
982 for(l = 0; l < nb_lines / 2; l++) {
983 char *s = lines[nb_lines - 1 - l];
984 lines[nb_lines - 1 - l] = lines[l];
989 /* Build the labels from the strings, take only the part before the
990 label_separator and transform control characters to printable
993 labels = safe_malloc(nb_lines * sizeof(char *));
995 for(l = 0; l < nb_lines; l++) {
1001 while(*t && *t != label_separator) {
1006 labels[l] = safe_malloc((e + 1) * sizeof(char));
1009 while(*t && *t != label_separator) {
1011 while(*u) { *s++ = *u++; }
1018 cursor_position = 0;
1020 /* Here we start to display with curse */
1025 intrflush(stdscr, FALSE);
1027 /* So that the arrow keys work */
1028 keypad(stdscr, TRUE);
1030 attr_error = A_STANDOUT;
1031 attr_modeline = A_REVERSE;
1032 attr_focus_line = A_STANDOUT;
1034 if(with_colors && has_colors()) {
1038 if(color_fg_modeline < 0 || color_fg_modeline >= COLORS ||
1039 color_bg_modeline < 0 || color_bg_modeline >= COLORS ||
1040 color_fg_highlight < 0 || color_bg_highlight >= COLORS ||
1041 color_bg_highlight < 0 || color_bg_highlight >= COLORS) {
1044 fprintf(stderr, "Selector: Color numbers have to be between 0 and %d.\n",
1049 init_pair(1, color_fg_modeline, color_bg_modeline);
1050 attr_modeline = COLOR_PAIR(1);
1052 init_pair(2, color_fg_highlight, color_bg_highlight);
1053 attr_focus_line = COLOR_PAIR(2);
1055 init_pair(3, COLOR_WHITE, COLOR_RED);
1056 attr_error = COLOR_PAIR(3);
1060 current_focus_line = 0;
1061 displayed_focus_line = 0;
1063 update_screen(¤t_focus_line, &displayed_focus_line,
1065 nb_lines, labels, cursor_position, pattern);
1072 if(key >= ' ' && key <= '~') { /* Insert character */
1073 insert_char(pattern, &cursor_position, key);
1076 else if(key == KEY_BACKSPACE ||
1077 key == '\010' || /* ^H */
1078 key == '\177') { /* ^? */
1079 backspace_char(pattern, &cursor_position);
1082 else if(key == KEY_DC ||
1083 key == '\004') { /* ^D */
1084 delete_char(pattern, &cursor_position);
1087 else if(key == KEY_HOME) {
1088 current_focus_line = 0;
1091 else if(key == KEY_END) {
1092 current_focus_line = nb_lines - 1;
1095 else if(key == KEY_NPAGE) {
1099 else if(key == KEY_PPAGE) {
1103 else if(key == KEY_DOWN ||
1104 key == '\016') { /* ^N */
1108 else if(key == KEY_UP ||
1109 key == '\020') { /* ^P */
1113 else if(key == KEY_LEFT ||
1114 key == '\002') { /* ^B */
1115 if(cursor_position > 0) cursor_position--;
1116 else error_feedback();
1119 else if(key == KEY_RIGHT ||
1120 key == '\006') { /* ^F */
1121 if(pattern[cursor_position]) cursor_position++;
1122 else error_feedback();
1125 else if(key == '\001') { /* ^A */
1126 cursor_position = 0;
1129 else if(key == '\005') { /* ^E */
1130 cursor_position = strlen(pattern);
1133 else if(key == '\022') { /* ^R */
1134 use_regexp = !use_regexp;
1137 else if(key == '\011') { /* ^I */
1138 case_sensitive = !case_sensitive;
1141 else if(key == '\025') { /* ^U */
1142 kill_before_cursor(pattern, &cursor_position);
1145 else if(key == '\013') { /* ^K */
1146 kill_after_cursor(pattern, &cursor_position);
1149 else if(key == '\014') { /* ^L */
1150 /* I suspect that we may sometime mess up the display, so ^L is
1151 here to force a full refresh */
1155 update_screen(¤t_focus_line, &displayed_focus_line,
1157 nb_lines, labels, cursor_position, pattern);
1159 } while(key != '\007' && /* ^G */
1160 key != '\033' && /* ^[ (escape) */
1167 /* Here we come back to standard display */
1169 if((key == KEY_ENTER || key == '\n')) {
1173 if(displayed_focus_line >= 0 && displayed_focus_line < nb_lines) {
1174 t = lines[displayed_focus_line];
1175 if(label_separator) {
1176 while(*t && *t != label_separator) t++;
1183 if(output_to_vt_buffer && t) {
1184 inject_into_tty_buffer(t, add_control_qs);
1187 if(output_filename[0]) {
1188 FILE *out = fopen(output_filename, "w");
1196 "Selector: Can not open %s for writing.\n",
1204 printf("Aborted.\n");
1207 for(l = 0; l < nb_lines; l++) {