From: Francois Fleuret Date: Sat, 27 Jun 2009 12:03:14 +0000 (+0200) Subject: Finished the conversion to pure C. X-Git-Url: https://fleuret.org/cgi-bin/gitweb/gitweb.cgi?p=selector.git;a=commitdiff_plain;h=56b5c4cef39a4e3734e651331b579627f1e4ed01 Finished the conversion to pure C. --- diff --git a/Makefile b/Makefile index 8e18fe3..bdc1863 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ MAN_PATH = /usr/share/man/man1 LDFLAGS=-lcurses -REVISION_NUMBER=$(shell cat REVISION_NUMBER) +REVISION_NUMBER=\"$(shell cat REVISION_NUMBER)\" ifeq ($(DEBUG),yes) OPTIMIZE_FLAG = -ggdb3 -DDEBUG -fno-omit-frame-pointer @@ -34,12 +34,12 @@ else OPTIMIZE_FLAG = -ggdb3 -O3 endif -CXXFLAGS = -DREVISION_NUMBER=$(REVISION_NUMBER) -Wall $(OPTIMIZE_FLAG) +CFLAGS = -DREVISION_NUMBER=$(REVISION_NUMBER) -Wall $(OPTIMIZE_FLAG) all: selector selector: selector.o - $(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) + $(CC) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) install: selector install -m 755 selector $(BINARY_PATH) diff --git a/selector.c b/selector.c index 82d7116..b757357 100644 --- a/selector.c +++ b/selector.c @@ -25,8 +25,7 @@ // To use it as a super-history-search for bash: // selector -q -b -i -d -v -w -l ${HISTSIZE} <(history) -// #include -// #include +#define _GNU_SOURCE #include #include @@ -794,7 +793,7 @@ int main(int argc, char **argv) { } if(show_help || error) { - /* fprintf(stderr, "Selector version %s-R%s\n", VERSION, REVISION_NUMBER); */ + fprintf(stderr, "Selector version %s-R%s\n", VERSION, REVISION_NUMBER); fprintf(stderr, "Written by Francois Fleuret .\n"); fprintf(stderr, "Usage: %s [options] [ [ ...]]\n", argv[0]); fprintf(stderr, "\n"); diff --git a/selector.cc b/selector.cc deleted file mode 100644 index fb08373..0000000 --- a/selector.cc +++ /dev/null @@ -1,1095 +0,0 @@ - -/* - * selector is a simple command line utility for selection of strings - * with a dynamic pattern-matching. - * - * Copyright (c) 2009 Francois Fleuret - * Written by Francois Fleuret - * - * This file is part of selector. - * - * selector is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as - * published by the Free Software Foundation. - * - * selector is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with selector. If not, see . - * - */ - -// To use it as a super-history-search for bash: -// selector -q -b -i -d -v -w -l ${HISTSIZE} <(history) - -// #include -// #include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std; - -#define VERSION "1.0" - -const int buffer_size = 4096; - -// Yeah, global variables! - -int nb_lines_max = 1000; -char pattern_separator = ';'; -char label_separator = '\0'; -int output_to_vt_buffer = 0; -int add_control_qs = 0; -int with_colors = 1; -int zsh_history = 0; -int bash_history = 0; -int inverse_order = 0; -int remove_duplicates = 0; -int use_regexp = 0; -int case_sensitive = 0; -char *title = 0; -int error_flash = 0; - -int attr_modeline, attr_focus_line, attr_error; - -////////////////////////////////////////////////////////////////////// - -void inject_into_tty_buffer(char *string) { - struct termios oldtio, newtio; - tcgetattr(STDIN_FILENO, &oldtio); - memset(&newtio, 0, sizeof(newtio)); - // Set input mode (non-canonical, *no echo*,...) - tcsetattr(STDIN_FILENO, TCSANOW, &newtio); - const char control_q = '\021'; - // Put the selected string in the tty input buffer - for(const char *k = string; *k; k++) { - if(add_control_qs && !(*k >= ' ' && *k <= '~')) { - // Add ^Q to quote control characters - ioctl(STDIN_FILENO, TIOCSTI, &control_q); - } - ioctl(STDIN_FILENO, TIOCSTI, k); - } - // Restore the old settings - tcsetattr(STDIN_FILENO, TCSANOW, &oldtio); -} - -////////////////////////////////////////////////////////////////////// - -void check_opt(int argc, char **argv, int n_opt, int n, const char *help) { - if(n_opt + n >= argc) { - fprintf(stderr, "Missing argument for %s, expecting %s.\n", - argv[n_opt], help); - exit(1); - } -} - -int string_to_positive_integer(char *string) { - int error = 0; - int result = 0; - - if(*string) { - for(char *s = string; *s; s++) { - if(*s >= '0' && *s <= '9') { - result = result * 10 + int(*s - '0'); - } else error = 1; - } - } else error = 1; - - if(error) { - fprintf(stderr, "Value `%s' is not a positive integer.\n", string); - exit(1); - } - - return result; -} - -void error_feedback() { - if(error_flash) { - flash(); - } else { - beep(); - } -} - -////////////////////////////////////////////////////////////////////// -// A quick and dirty hash table - -// The table itself stores indexes of the strings taken in a char -// **table. When a string is added, if it was already in the table, -// the new index replaces the previous one. - -int *new_hash_table(int hash_table_size) { - int *result; - result = new int[hash_table_size]; - for(int k = 0; k < hash_table_size; k++) { - result[k] = -1; - } - return result; -} - -// Adds new_string in the table, associated to new_index. If this -// string was not already in the table, returns -1. Otherwise, returns -// the previous index it had. - -int test_and_add(char *new_string, int new_index, - char **strings, - int *hash_table, int hash_table_size) { - - unsigned int code = 0; - - // This is my recipe. I checked, it seems to work (as long as - // hash_table_size is not a multiple of 387433 that should be okay) - - for(int k = 0; new_string[k]; k++) { - code = code * 387433 + (unsigned int) (new_string[k]); - } - - code = code % hash_table_size; - - while(hash_table[code] >= 0) { - // There is a string with that code - if(strcmp(new_string, strings[hash_table[code]]) == 0) { - // It is the same string, we keep a copy of the stored index - int result = hash_table[code]; - // Put the new one - hash_table[code] = new_index; - // And return the previous one - return result; - } - // This collision was not the same string, let's move to the next - // in the table - code = (code + 1) % hash_table_size; - } - - // This string was not already in there, store the index in the - // table and return -1 - hash_table[code] = new_index; - return -1; -} - -////////////////////////////////////////////////////////////////////// -// A matcher matches either with a collection of substrings, or with a -// regexp - -struct matcher_t { - regex_t preg; - int regexp_error; - int nb_patterns; - int case_sensitive; - char *splitted_patterns, **patterns; -}; - -int match(char *string, matcher_t *matcher) { - if(matcher->nb_patterns >= 0) { - if(matcher->case_sensitive) { - for(int n = 0; n < matcher->nb_patterns; n++) { - if(strstr(string, matcher->patterns[n]) == 0) return 0; - } - } else { - for(int n = 0; n < matcher->nb_patterns; n++) { - if(strcasestr(string, matcher->patterns[n]) == 0) return 0; - } - } - return 1; - } else { - return regexec(&matcher->preg, string, 0, 0, 0) == 0; - } -} - -void free_matcher(matcher_t *matcher) { - if(matcher->nb_patterns < 0) { - if(!matcher->regexp_error) regfree(&matcher->preg); - } else { - delete[] matcher->splitted_patterns; - delete[] matcher->patterns; - } -} - -void initialize_matcher(int use_regexp, int case_sensitive, - matcher_t *matcher, const char *pattern) { - - if(use_regexp) { - matcher->nb_patterns = -1; - matcher->regexp_error = regcomp(&matcher->preg, pattern, case_sensitive ? 0 : REG_ICASE); - } else { - matcher->regexp_error = 0; - matcher->nb_patterns = 1; - matcher->case_sensitive = case_sensitive; - - for(const char *s = pattern; *s; s++) { - if(*s == pattern_separator) { - matcher->nb_patterns++; - } - } - - matcher->splitted_patterns = new char[strlen(pattern) + 1]; - matcher->patterns = new char*[matcher->nb_patterns]; - - strcpy(matcher->splitted_patterns, pattern); - - int n = 0; - char *last_pattern_start = matcher->splitted_patterns; - for(char *s = matcher->splitted_patterns; n < matcher->nb_patterns; s++) { - if(*s == pattern_separator || *s == '\0') { - *s = '\0'; - matcher->patterns[n++] = last_pattern_start; - last_pattern_start = s + 1; - } - } - } -} - -////////////////////////////////////////////////////////////////////// -// Buffer edition - -void delete_char(char *buffer, int *position) { - if(buffer[*position]) { - int c = *position; - while(c < buffer_size && buffer[c]) { - buffer[c] = buffer[c+1]; - c++; - } - } else error_feedback(); -} - -void backspace_char(char *buffer, int *position) { - if(*position > 0) { - if(buffer[*position]) { - int c = *position - 1; - while(buffer[c]) { - buffer[c] = buffer[c+1]; - c++; - } - } else { - buffer[*position - 1] = '\0'; - } - - (*position)--; - } else error_feedback(); -} - -void insert_char(char *buffer, int *position, char character) { - if(strlen(buffer) < buffer_size - 1) { - int c = *position; - char t = buffer[c], u; - while(t) { - c++; - u = buffer[c]; - buffer[c] = t; - t = u; - } - c++; - buffer[c] = '\0'; - buffer[(*position)++] = character; - } else error_feedback(); -} - -void kill_before_cursor(char *buffer, int *position) { - int s = 0; - while(buffer[*position + s]) { - buffer[s] = buffer[*position + s]; - s++; - } - buffer[s] = '\0'; - *position = 0; -} - -void kill_after_cursor(char *buffer, int *position) { - buffer[*position] = '\0'; -} - -////////////////////////////////////////////////////////////////////// - -int previous_visible(int current_line, int nb_lines, char **lines, matcher_t *matcher) { - int line = current_line - 1; - while(line >= 0 && !match(lines[line], matcher)) line--; - return line; -} - -int next_visible(int current_line, int nb_lines, char **lines, matcher_t *matcher) { - int line = current_line + 1; - while(line < nb_lines && !match(lines[line], matcher)) line++; - - if(line < nb_lines) - return line; - else - return -1; -} - -////////////////////////////////////////////////////////////////////// - -// The value passed to this routine in current_focus_line is the index -// of the line we should have highlited if there was no motion and if -// it matched the matcher. So, the line actually highlighted is the -// first one matching the matcher in that order: (1) -// current_focus_line after motion, (2) the first with a greater -// index, (3) the first with a lesser index. - -// The index of the line actually shown highlighted is written in -// displayed_focus_line (it can be -1) - -// If there is a motion and a line is actually shown highlighted, its -// value is written in current_focus_line. - -void update_screen(int *current_focus_line, int *displayed_focus_line, - int motion, - int nb_lines, char **lines, - int cursor_position, - char *pattern) { - - char buffer[buffer_size]; - matcher_t matcher; - - initialize_matcher(use_regexp, case_sensitive, &matcher, pattern); - - int console_width = getmaxx(stdscr); - int console_height = getmaxy(stdscr); - - // First, we find a visible line. - - int nb_printed_lines = 0; - - use_default_colors(); - - addstr("\n"); - - if(matcher.regexp_error) { - attron(attr_error); - addnstr("Regexp syntax error", console_width); - attroff(attr_error); - } else if(nb_lines > 0) { - int new_focus_line; - if(match(lines[*current_focus_line], &matcher)) { - new_focus_line = *current_focus_line; - } else { - new_focus_line = next_visible(*current_focus_line, nb_lines, lines, &matcher); - if(new_focus_line < 0) { - new_focus_line = previous_visible(*current_focus_line, nb_lines, lines, &matcher); - } - } - - // If we found a visible line and we should move, let's move - - if(new_focus_line >= 0 && motion != 0) { - int l = new_focus_line; - if(motion > 0) { - // We want to go down, let's find the first visible line below - for(int m = 0; l >= 0 && m < motion; m++) { - l = next_visible(l, nb_lines, lines, &matcher); - if(l >= 0) { - new_focus_line = l; - } - } - } else { - // We want to go up, let's find the first visible line above - for(int m = 0; l >= 0 && m < -motion; m++) { - l = previous_visible(l, nb_lines, lines, &matcher); - if(l >= 0) { - new_focus_line = l; - } - } - } - } - - // Here new_focus_line is either a line number matching the pattern, or -1 - - if(new_focus_line >= 0) { - - int first_line = new_focus_line, last_line = new_focus_line, nb_match = 1; - - // We find the first and last line to show, so that the total of - // visible lines between them (them included) is console_height-1 - - while(nb_match < console_height-1 && (first_line > 0 || last_line < nb_lines - 1)) { - - if(first_line > 0) { - first_line--; - while(first_line > 0 && !match(lines[first_line], &matcher)) { - first_line--; - } - if(match(lines[first_line], &matcher)) { - nb_match++; - } - } - - if(nb_match < console_height - 1 && last_line < nb_lines - 1) { - last_line++; - while(last_line < nb_lines - 1 && !match(lines[last_line], &matcher)) { - last_line++; - } - - if(match(lines[last_line], &matcher)) { - nb_match++; - } - } - } - - // Now we display them - - for(int l = first_line; l <= last_line; l++) { - if(match(lines[l], &matcher)) { - int k = 0; - - while(lines[l][k] && k < buffer_size - 2 && k < console_width - 2) { - buffer[k] = lines[l][k]; - k++; - } - - // We fill the rest of the line with blanks if this is the - // highlighted line - - if(l == new_focus_line) { - while(k < console_width) { - buffer[k++] = ' '; - } - } - - buffer[k++] = '\n'; - buffer[k++] = '\0'; - - clrtoeol(); - - // Highlight the highlighted line ... - - if(l == new_focus_line) { - attron(attr_focus_line); - addnstr(buffer, console_width); - attroff(attr_focus_line); - } else { - addnstr(buffer, console_width); - } - - nb_printed_lines++; - } - } - - // If we are on a focused line and we moved, this become the new - // focus line - - if(motion != 0) { - *current_focus_line = new_focus_line; - } - } - - *displayed_focus_line = new_focus_line; - - if(nb_printed_lines == 0) { - attron(attr_error); - addnstr("No selection", console_width); - attroff(attr_error); - } - } else { - attron(attr_error); - addnstr("Empty choice", console_width); - attroff(attr_error); - } - - clrtobot(); - - // Draw the modeline - - move(0, 0); - - attron(attr_modeline); - - for(int k = 0; k < console_width; k++) buffer[k] = ' '; - buffer[console_width] = '\0'; - addnstr(buffer, console_width); - - move(0, 0); - - // There must be a more elegant way of moving the cursor at a - // location met during display - - int cursor_x = 0; - - if(title) { - addstr(title); - addstr(" "); - cursor_x += strlen(title) + 1; - } - - sprintf(buffer, "%d/%d ", nb_printed_lines, nb_lines); - addstr(buffer); - cursor_x += strlen(buffer); - - addnstr(pattern, cursor_position); - cursor_x += cursor_position; - - if(pattern[cursor_position]) { - addstr(pattern + cursor_position); - } else { - addstr(" "); - } - - if(use_regexp || case_sensitive) { - addstr(" ["); - if(use_regexp) { - addstr("regexp"); - } - - if(case_sensitive) { - if(use_regexp) { - addstr(","); - } - addstr("case"); - } - addstr("]"); - } - - move(0, cursor_x); - - attroff(attr_modeline); - - // We are done - - refresh(); - free_matcher(&matcher); -} - -////////////////////////////////////////////////////////////////////// - -void read_file(const char *input_filename, - int nb_lines_max, int *nb_lines, char **lines, - int hash_table_size, int *hash_table) { - - char raw_line[buffer_size]; - - FILE *file = fopen(input_filename, "r"); - - if(!file) { - fprintf(stderr, "Can not open `%s'.\n", input_filename); - exit(1); - } - - int start = 0, end = 0; - - while(*nb_lines < nb_lines_max && (end > start || !feof(file))) { - int eol = start; - while(eol < end && raw_line[eol] != '\n') eol++; - - if(eol == end) { - for(int k = 0; k < end - start; k++) { - raw_line[k] = raw_line[k + start]; - } - end -= start; - eol -= start; - start = 0; - end += fread(raw_line + end, sizeof(char), buffer_size - end, file); - while(eol < end && raw_line[eol] != '\n') eol++; - } - - if(eol == buffer_size) { - raw_line[buffer_size - 1] = '\0'; - fprintf(stderr, "Line too long:\n"); - fprintf(stderr, raw_line); - fprintf(stderr, "\n"); - exit(1); - } - - raw_line[eol] = '\0'; - - char *t = raw_line + start; - - // Remove the zsh history prefix - - if(zsh_history && *t == ':') { - while(*t && *t != ';') t++; - if(*t == ';') t++; - } - - // Remove the bash history prefix - - if(bash_history) { - while(*t == ' ') t++; - while(*t >= '0' && *t <= '9') t++; - while(*t == ' ') t++; - } - - // Check for duplicates with the hash table and insert the line - // in the list if necessary - - int dup; - - if(hash_table) { - dup = test_and_add(t, *nb_lines, lines, hash_table, hash_table_size); - } else { - dup = -1; - } - - if(dup < 0) { - lines[*nb_lines] = new char[strlen(t) + 1]; - strcpy(lines[*nb_lines], t); - } else { - // The string was already in there, so we do not allocate a - // new string but use the pointer to the first occurence of it - lines[*nb_lines] = lines[dup]; - lines[dup] = 0; - } - - (*nb_lines)++; - - start = eol + 1; - } -} - -////////////////////////////////////////////////////////////////////// - -int main(int argc, char **argv) { - - if(!ttyname(STDIN_FILENO)) { - fprintf(stderr, "The standard input is not a tty.\n"); - exit(1); - } - - int color_fg_modeline, color_bg_modeline; - int color_fg_highlight, color_bg_highlight; - - color_fg_modeline = COLOR_WHITE; - color_bg_modeline = COLOR_BLACK; - color_fg_highlight = COLOR_BLACK; - color_bg_highlight = COLOR_YELLOW; - - setlocale(LC_ALL, ""); - - char input_filename[buffer_size], output_filename[buffer_size]; - - strcpy(input_filename, ""); - strcpy(output_filename, ""); - - int i = 1; - int error = 0, show_help = 0; - int rest_are_files = 0; - - while(!error && !show_help && i < argc && argv[i][0] == '-' && !rest_are_files) { - - if(strcmp(argv[i], "-o") == 0) { - check_opt(argc, argv, i, 1, ""); - strncpy(output_filename, argv[i+1], buffer_size); - i += 2; - } - - else if(strcmp(argv[i], "-s") == 0) { - check_opt(argc, argv, i, 1, ""); - pattern_separator = argv[i+1][0]; - i += 2; - } - - else if(strcmp(argv[i], "-x") == 0) { - check_opt(argc, argv, i, 1, "