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');
114 fprintf(stderr, "Selector: Value `%s' is not a positive integer.\n", string);
121 void error_feedback() {
129 /* A quick and dirty hash table */
131 /* The table itself stores indexes of the strings taken in a char
132 **table. When a string is added, if it was already in the table,
133 **the new index replaces the previous one. */
140 hash_table_t *new_hash_table(int size) {
142 hash_table_t *hash_table;
144 hash_table = (hash_table_t *) malloc(sizeof(hash_table_t));
146 hash_table->size = size;
147 hash_table->entries = (int *) malloc(hash_table->size * sizeof(int));
149 for(k = 0; k < hash_table->size; k++) {
150 hash_table->entries[k] = -1;
156 void free_hash_table(hash_table_t *hash_table) {
157 free(hash_table->entries);
161 /* Adds new_string in the table, associated to new_index. If this
162 string was not already in the table, returns -1. Otherwise, returns
163 the previous index it had. */
165 int add_and_get_previous_index(hash_table_t *hash_table,
166 const char *new_string, int new_index, char **strings) {
168 unsigned int code = 0;
171 /* This is my recipe. I checked, it seems to work (as long as
172 hash_table->size is not a multiple of 387433 that should be
175 for(k = 0; new_string[k]; k++) {
176 code = code * 387433 + (unsigned int) (new_string[k]);
179 code = code % hash_table->size;
181 while(hash_table->entries[code] >= 0) {
182 /* There is a string with that code */
183 if(strcmp(new_string, strings[hash_table->entries[code]]) == 0) {
184 /* It is the same string, we keep a copy of the stored index */
185 int result = hash_table->entries[code];
186 /* Put the new one */
187 hash_table->entries[code] = new_index;
188 /* And return the previous one */
191 /* This collision was not the same string, let's move to the next
193 code = (code + 1) % hash_table->size;
196 /* This string was not already in there, store the index in the
197 table and return -1 */
199 hash_table->entries[code] = new_index;
203 /*********************************************************************
204 A matcher matches either with a collection of substrings, or with a
212 char *splitted_patterns, **patterns;
215 int match(char *string, matcher_t *matcher) {
217 if(matcher->nb_patterns >= 0) {
218 if(matcher->case_sensitive) {
219 for(n = 0; n < matcher->nb_patterns; n++) {
220 if(strstr(string, matcher->patterns[n]) == 0) return 0;
223 for(n = 0; n < matcher->nb_patterns; n++) {
224 if(strcasestr(string, matcher->patterns[n]) == 0) return 0;
229 return regexec(&matcher->preg, string, 0, 0, 0) == 0;
233 void free_matcher(matcher_t *matcher) {
234 if(matcher->nb_patterns < 0) {
235 if(!matcher->regexp_error) regfree(&matcher->preg);
237 free(matcher->splitted_patterns);
238 free(matcher->patterns);
242 void initialize_matcher(int use_regexp, int case_sensitive,
243 matcher_t *matcher, const char *pattern) {
245 char *t, *last_pattern_start;
249 matcher->nb_patterns = -1;
250 matcher->regexp_error = regcomp(&matcher->preg, pattern, case_sensitive ? 0 : REG_ICASE);
252 matcher->regexp_error = 0;
253 matcher->nb_patterns = 1;
254 matcher->case_sensitive = case_sensitive;
256 for(s = pattern; *s; s++) {
257 if(*s == pattern_separator) {
258 matcher->nb_patterns++;
262 matcher->splitted_patterns = (char *) malloc((strlen(pattern) + 1) * sizeof(char));
263 matcher->patterns = (char **) malloc(matcher->nb_patterns * sizeof(char *));
265 strcpy(matcher->splitted_patterns, pattern);
268 last_pattern_start = matcher->splitted_patterns;
269 for(t = matcher->splitted_patterns; n < matcher->nb_patterns; t++) {
270 if(*t == pattern_separator || *t == '\0') {
272 matcher->patterns[n++] = last_pattern_start;
273 last_pattern_start = t + 1;
279 /*********************************************************************
282 void delete_char(char *buffer, int *position) {
283 if(buffer[*position]) {
285 while(c < BUFFER_SIZE && buffer[c]) {
286 buffer[c] = buffer[c+1];
289 } else error_feedback();
292 void backspace_char(char *buffer, int *position) {
294 if(buffer[*position]) {
295 int c = *position - 1;
297 buffer[c] = buffer[c+1];
301 buffer[*position - 1] = '\0';
305 } else error_feedback();
308 void insert_char(char *buffer, int *position, char character) {
309 if(strlen(buffer) < BUFFER_SIZE - 1) {
311 char t = buffer[c], u;
320 buffer[(*position)++] = character;
321 } else error_feedback();
324 void kill_before_cursor(char *buffer, int *position) {
326 while(buffer[*position + s]) {
327 buffer[s] = buffer[*position + s];
334 void kill_after_cursor(char *buffer, int *position) {
335 buffer[*position] = '\0';
338 /*********************************************************************/
340 int previous_visible(int current_line, int nb_lines, char **lines, matcher_t *matcher) {
341 int line = current_line - 1;
342 while(line >= 0 && !match(lines[line], matcher)) line--;
346 int next_visible(int current_line, int nb_lines, char **lines, matcher_t *matcher) {
347 int line = current_line + 1;
348 while(line < nb_lines && !match(lines[line], matcher)) line++;
356 /*********************************************************************/
358 /* The line highlighted is the first one matching the matcher in that
359 order: (1) current_focus_line after motion, if it does not match,
360 then (2) the first with a greater index, if none matches, then (3)
361 the first with a lesser index.
363 The index of the line actually shown highlighted is written in
364 displayed_focus_line (it can be -1 if no line at all matches the
367 If there is a motion and a line is actually shown highlighted, its
368 value is written in current_focus_line. */
370 void update_screen(int *current_focus_line, int *displayed_focus_line,
372 int nb_lines, char **lines,
376 char buffer[BUFFER_SIZE];
379 int console_width, console_height;
380 int nb_printed_lines = 0;
383 initialize_matcher(use_regexp, case_sensitive, &matcher, pattern);
385 console_width = getmaxx(stdscr);
386 console_height = getmaxy(stdscr);
388 use_default_colors();
390 /* Add an empty line where we will print the modeline at the end */
394 /* If the regexp is erroneous, print a message saying so */
396 if(matcher.regexp_error) {
398 addnstr("Regexp syntax error", console_width);
402 /* Else, and we do have lines to select from, find a visible line. */
404 else if(nb_lines > 0) {
406 if(match(lines[*current_focus_line], &matcher)) {
407 new_focus_line = *current_focus_line;
409 new_focus_line = next_visible(*current_focus_line, nb_lines, lines, &matcher);
410 if(new_focus_line < 0) {
411 new_focus_line = previous_visible(*current_focus_line, nb_lines, lines, &matcher);
415 /* If we found a visible line and we should move, let's move */
417 if(new_focus_line >= 0 && motion != 0) {
418 int l = new_focus_line;
420 /* We want to go down, let's find the first visible line below */
421 for(m = 0; l >= 0 && m < motion; m++) {
422 l = next_visible(l, nb_lines, lines, &matcher);
428 /* We want to go up, let's find the first visible line above */
429 for(m = 0; l >= 0 && m < -motion; m++) {
430 l = previous_visible(l, nb_lines, lines, &matcher);
438 /* Here new_focus_line is either a line number matching the pattern, or -1 */
440 if(new_focus_line >= 0) {
442 int first_line = new_focus_line, last_line = new_focus_line, nb_match = 1;
444 /* We find the first and last line to show, so that the total of
445 visible lines between them (them included) is
448 while(nb_match < console_height-1 && (first_line > 0 || last_line < nb_lines - 1)) {
452 while(first_line > 0 && !match(lines[first_line], &matcher)) {
455 if(match(lines[first_line], &matcher)) {
460 if(nb_match < console_height - 1 && last_line < nb_lines - 1) {
462 while(last_line < nb_lines - 1 && !match(lines[last_line], &matcher)) {
466 if(match(lines[last_line], &matcher)) {
472 /* Now we display them */
474 for(l = first_line; l <= last_line; l++) {
475 if(match(lines[l], &matcher)) {
478 while(lines[l][k] && k < BUFFER_SIZE - 2 && k < console_width - 2) {
479 buffer[k] = lines[l][k];
483 /* We fill the rest of the line with blanks if this is the
486 if(l == new_focus_line) {
487 while(k < console_width) {
497 /* Highlight the highlighted line ... */
499 if(l == new_focus_line) {
500 attron(attr_focus_line);
501 addnstr(buffer, console_width);
502 attroff(attr_focus_line);
504 addnstr(buffer, console_width);
511 /* If we are on a focused line and we moved, this become the new
515 *current_focus_line = new_focus_line;
519 *displayed_focus_line = new_focus_line;
521 if(nb_printed_lines == 0) {
523 addnstr("No selection", console_width);
528 /* Else, print a message saying that there are no lines to select from */
532 addnstr("Empty choice", console_width);
538 /* Draw the modeline */
542 attron(attr_modeline);
544 for(k = 0; k < console_width; k++) buffer[k] = ' ';
545 buffer[console_width] = '\0';
546 addnstr(buffer, console_width);
550 /* There must be a more elegant way of moving the cursor at a
551 location met during display */
558 cursor_x += strlen(title) + 1;
561 sprintf(buffer, "%d/%d ", nb_printed_lines, nb_lines);
563 cursor_x += strlen(buffer);
565 addnstr(pattern, cursor_position);
566 cursor_x += cursor_position;
568 if(pattern[cursor_position]) {
569 addstr(pattern + cursor_position);
574 /* Add a few info about the mode we are in (regexp and/or case
577 if(use_regexp || case_sensitive) {
594 attroff(attr_modeline);
599 free_matcher(&matcher);
602 /*********************************************************************/
604 void store_line(hash_table_t *hash_table,
605 const char *new_line,
606 int *nb_lines, char **lines) {
609 /* Remove the zsh history prefix */
611 if(zsh_history && *new_line == ':') {
612 while(*new_line && *new_line != ';') new_line++;
613 if(*new_line == ';') new_line++;
616 /* Remove the bash history prefix */
619 while(*new_line == ' ') new_line++;
620 while(*new_line >= '0' && *new_line <= '9') new_line++;
621 while(*new_line == ' ') new_line++;
624 /* Check for duplicates with the hash table and insert the line in
625 the list if necessary */
628 dup = add_and_get_previous_index(hash_table,
629 new_line, *nb_lines, lines);
635 lines[*nb_lines] = (char *) malloc((strlen(new_line) + 1) * sizeof(char));
636 strcpy(lines[*nb_lines], new_line);
638 /* The string was already in there, so we do not allocate a new
639 string but use the pointer to the first occurence of it */
640 lines[*nb_lines] = lines[dup];
647 void read_file(hash_table_t *hash_table,
648 const char *input_filename,
649 int nb_lines_max, int *nb_lines, char **lines) {
651 char raw_line[BUFFER_SIZE];
652 int start, end, eol, k;
655 file = fopen(input_filename, "r");
658 fprintf(stderr, "Selector: Can not open `%s'.\n", input_filename);
665 while(*nb_lines < nb_lines_max && (end > start || !feof(file))) {
668 /* Look for the end of a line in what is already in the buffer */
669 while(eol < end && raw_line[eol] != '\n') eol++;
671 /* if we did not find the of a line, move what has not been
672 processed and is in the buffer to the beginning of the buffer,
673 fill the buffer with new data from the file, and look for the
676 for(k = 0; k < end - start; k++) {
677 raw_line[k] = raw_line[k + start];
682 end += fread(raw_line + end, sizeof(char), BUFFER_SIZE - end, file);
683 while(eol < end && raw_line[eol] != '\n') eol++;
686 /* The end of the line is the buffer size, which means the line is
689 if(eol == BUFFER_SIZE) {
690 raw_line[BUFFER_SIZE - 1] = '\0';
691 fprintf(stderr, "Selector: Line too long (max is %d characters):\n", BUFFER_SIZE);
692 fprintf(stderr, raw_line);
693 fprintf(stderr, "\n");
697 /* If we got a line, we replace the carriage return by a \0 to
700 raw_line[eol] = '\0';
702 store_line(hash_table, raw_line + start,
711 /*********************************************************************/
713 int main(int argc, char **argv) {
715 char input_filename[BUFFER_SIZE], output_filename[BUFFER_SIZE];
716 char pattern[BUFFER_SIZE];
719 int error = 0, show_help = 0;
720 int rest_are_files = 0;
722 int current_focus_line, displayed_focus_line;
724 int color_fg_modeline, color_bg_modeline;
725 int color_fg_highlight, color_bg_highlight;
727 char **lines, **labels;
729 hash_table_t *hash_table;
731 if(!ttyname(STDIN_FILENO)) {
732 fprintf(stderr, "Selector: The standard input is not a tty.\n");
736 color_fg_modeline = COLOR_WHITE;
737 color_bg_modeline = COLOR_BLACK;
738 color_fg_highlight = COLOR_BLACK;
739 color_bg_highlight = COLOR_YELLOW;
741 setlocale(LC_ALL, "");
743 strcpy(input_filename, "");
744 strcpy(output_filename, "");
747 while(!error && !show_help && i < argc && argv[i][0] == '-' && !rest_are_files) {
749 if(strcmp(argv[i], "-o") == 0) {
750 check_opt(argc, argv, i, 1, "<output filename>");
751 strncpy(output_filename, argv[i+1], BUFFER_SIZE);
755 else if(strcmp(argv[i], "-s") == 0) {
756 check_opt(argc, argv, i, 1, "<pattern separator>");
757 pattern_separator = argv[i+1][0];
761 else if(strcmp(argv[i], "-x") == 0) {
762 check_opt(argc, argv, i, 1, "<label separator>");
763 label_separator = argv[i+1][0];
767 else if(strcmp(argv[i], "-v") == 0) {
768 output_to_vt_buffer = 1;
772 else if(strcmp(argv[i], "-w") == 0) {
777 else if(strcmp(argv[i], "-m") == 0) {
782 else if(strcmp(argv[i], "-q") == 0) {
787 else if(strcmp(argv[i], "-f") == 0) {
788 check_opt(argc, argv, i, 1, "<input filename>");
789 strncpy(input_filename, argv[i+1], BUFFER_SIZE);
793 else if(strcmp(argv[i], "-i") == 0) {
798 else if(strcmp(argv[i], "-b") == 0) {
803 else if(strcmp(argv[i], "-z") == 0) {
808 else if(strcmp(argv[i], "-d") == 0) {
809 remove_duplicates = 1;
813 else if(strcmp(argv[i], "-e") == 0) {
818 else if(strcmp(argv[i], "-a") == 0) {
823 else if(strcmp(argv[i], "-t") == 0) {
824 check_opt(argc, argv, i, 1, "<title>");
826 title = (char *) malloc((strlen(argv[i+1]) + 1) * sizeof(char));
827 strcpy(title, argv[i+1]);
831 else if(strcmp(argv[i], "-l") == 0) {
832 check_opt(argc, argv, i, 1, "<maximum number of lines>");
833 nb_lines_max = string_to_positive_integer(argv[i+1]);
837 else if(strcmp(argv[i], "-c") == 0) {
838 check_opt(argc, argv, i, 4, "<fg modeline> <bg modeline> <fg highlight> <bg highlight>");
839 color_fg_modeline = string_to_positive_integer(argv[i + 1]);
840 color_bg_modeline = string_to_positive_integer(argv[i + 2]);
841 color_fg_highlight = string_to_positive_integer(argv[i + 3]);
842 color_bg_highlight = string_to_positive_integer(argv[i + 4]);
846 else if(strcmp(argv[i], "--") == 0) {
851 else if(strcmp(argv[i], "-h") == 0) {
857 fprintf(stderr, "Selector: Unknown option %s.\n", argv[i]);
862 if(show_help || error) {
870 fprintf(out, "Selector version %s-R%s\n", VERSION, REVISION_NUMBER);
871 fprintf(out, "Written by Francois Fleuret <francois@fleuret.org>.\n");
873 fprintf(out, "Usage: %s [options] [<filename1> [<filename2> ...]]\n", argv[0]);
875 fprintf(out, " -h show this help\n");
876 fprintf(out, " -v inject the selected line in the tty\n");
877 fprintf(out, " -w quote control characters with ^Qs when using -v\n");
878 fprintf(out, " -d remove duplicated lines\n");
879 fprintf(out, " -b remove the bash history line prefix\n");
880 fprintf(out, " -z remove the zsh history line prefix\n");
881 fprintf(out, " -i invert the order of lines\n");
882 fprintf(out, " -e start in regexp mode\n");
883 fprintf(out, " -a start in case sensitive mode\n");
884 fprintf(out, " -m monochrome mode\n");
885 fprintf(out, " -q make a flash instead of a beep on an edition error\n");
886 fprintf(out, " -- all following arguments are filenames\n");
887 fprintf(out, " -t <title>\n");
888 fprintf(out, " add a title in the modeline\n");
889 fprintf(out, " -c <fg modeline> <bg modeline> <fg highlight> <bg highlight>\n");
890 fprintf(out, " set the display colors\n");
891 fprintf(out, " -o <output filename>\n");
892 fprintf(out, " set a file to write the selected line to\n");
893 fprintf(out, " -s <pattern separator>\n");
894 fprintf(out, " set the symbol to separate substrings in the pattern\n");
895 fprintf(out, " -x <label separator>\n");
896 fprintf(out, " set the symbol to terminate the label\n");
897 fprintf(out, " -l <max number of lines>\n");
898 fprintf(out, " set the maximum number of lines to take into account\n");
903 lines = (char **) malloc(nb_lines_max * sizeof(char *));
907 if(remove_duplicates) {
908 hash_table = new_hash_table(nb_lines_max * 10);
913 if(input_filename[0]) {
914 read_file(hash_table,
916 nb_lines_max, &nb_lines, lines);
920 read_file(hash_table,
922 nb_lines_max, &nb_lines, lines);
927 free_hash_table(hash_table);
930 /* Now remove the null strings */
933 for(k = 0; k < nb_lines; k++) {
935 lines[n++] = lines[k];
942 for(i = 0; i < nb_lines / 2; i++) {
943 char *s = lines[nb_lines - 1 - i];
944 lines[nb_lines - 1 - i] = lines[i];
949 /* Build the labels from the strings, take only the part before the
950 label_separator and transform control characters to printable
953 labels = (char **) malloc(nb_lines * sizeof(char *));
955 for(l = 0; l < nb_lines; l++) {
961 while(*t && *t != label_separator) {
966 labels[l] = (char *) malloc((e + 1) * sizeof(char));
969 while(*t && *t != label_separator) {
971 while(*u) { *s++ = *u++; }
980 /* Here we start to display with curse */
986 intrflush(stdscr, FALSE);
988 /* So that the arrow keys work */
989 keypad(stdscr, TRUE);
991 attr_error = A_STANDOUT;
992 attr_modeline = A_REVERSE;
993 attr_focus_line = A_STANDOUT;
995 if(with_colors && has_colors()) {
999 if(color_fg_modeline < 0 || color_fg_modeline >= COLORS ||
1000 color_bg_modeline < 0 || color_bg_modeline >= COLORS ||
1001 color_fg_highlight < 0 || color_bg_highlight >= COLORS ||
1002 color_bg_highlight < 0 || color_bg_highlight >= COLORS) {
1005 fprintf(stderr, "Selector: Color numbers have to be between 0 and %d.\n", COLORS - 1);
1009 init_pair(1, color_fg_modeline, color_bg_modeline);
1010 attr_modeline = COLOR_PAIR(1);
1012 init_pair(2, color_fg_highlight, color_bg_highlight);
1013 attr_focus_line = COLOR_PAIR(2);
1015 init_pair(3, COLOR_WHITE, COLOR_RED);
1016 attr_error = COLOR_PAIR(3);
1020 current_focus_line = 0;
1021 displayed_focus_line = 0;
1023 update_screen(¤t_focus_line, &displayed_focus_line,
1025 nb_lines, labels, cursor_position, pattern);
1032 if(key >= ' ' && key <= '~') { /* Insert character */
1033 insert_char(pattern, &cursor_position, key);
1036 else if(key == KEY_BACKSPACE ||
1037 key == '\010' || /* ^H */
1038 key == '\177') { /* ^? */
1039 backspace_char(pattern, &cursor_position);
1042 else if(key == KEY_DC ||
1043 key == '\004') { /* ^D */
1044 delete_char(pattern, &cursor_position);
1047 else if(key == KEY_HOME) {
1048 current_focus_line = 0;
1051 else if(key == KEY_END) {
1052 current_focus_line = nb_lines - 1;
1055 else if(key == KEY_NPAGE) {
1059 else if(key == KEY_PPAGE) {
1063 else if(key == KEY_DOWN ||
1064 key == '\016') { /* ^N */
1068 else if(key == KEY_UP ||
1069 key == '\020') { /* ^P */
1073 else if(key == KEY_LEFT ||
1074 key == '\002') { /* ^B */
1075 if(cursor_position > 0) cursor_position--;
1076 else error_feedback();
1079 else if(key == KEY_RIGHT ||
1080 key == '\006') { /* ^F */
1081 if(pattern[cursor_position]) cursor_position++;
1082 else error_feedback();
1085 else if(key == '\001') { /* ^A */
1086 cursor_position = 0;
1089 else if(key == '\005') { /* ^E */
1090 cursor_position = strlen(pattern);
1093 else if(key == '\022') { /* ^R */
1094 use_regexp = !use_regexp;
1097 else if(key == '\011') { /* ^I */
1098 case_sensitive = !case_sensitive;
1101 else if(key == '\025') { /* ^U */
1102 kill_before_cursor(pattern, &cursor_position);
1105 else if(key == '\013') { /* ^K */
1106 kill_after_cursor(pattern, &cursor_position);
1109 else if(key == '\014') { /* ^L */
1110 /* I suspect that we may sometime mess up the display */
1114 update_screen(¤t_focus_line, &displayed_focus_line,
1116 nb_lines, labels, cursor_position, pattern);
1118 } while(key != '\007' && /* ^G */
1119 key != '\033' && /* ^[ (escape) */
1126 /* Here we come back to standard display */
1128 if((key == KEY_ENTER || key == '\n')) {
1132 if(displayed_focus_line >= 0 && displayed_focus_line < nb_lines) {
1133 t = lines[displayed_focus_line];
1134 if(label_separator) {
1135 while(*t && *t != label_separator) t++;
1142 if(output_to_vt_buffer && t) {
1143 inject_into_tty_buffer(t);
1146 if(output_filename[0]) {
1147 FILE *out = fopen(output_filename, "w");
1154 fprintf(stderr, "Selector: Can not open %s for writing.\n", output_filename);
1161 printf("Aborted.\n");
1164 for(l = 0; l < nb_lines; l++) {