From a6b3f9f1af8a871fe357195898912c63b895ccf1 Mon Sep 17 00:00:00 2001 From: josuah Date: Sat, 29 Oct 2016 18:14:57 -0400 Subject: [PATCH] Split sources over multiple files --- Makefile | 14 +- README | 4 +- buffer.c | 175 +++++++++++++++ buffer.h | 8 + draw.c | 231 +++++++++++++++++++ draw.h | 9 + input.c | 178 +++++++++++++++ input.h | 8 + main.c | 766 +++++++-------------------------------------------------------- main.h | 50 ++--- util.c | 44 ++++ util.h | 12 + 12 files changed, 775 insertions(+), 724 deletions(-) create mode 100644 buffer.c create mode 100644 buffer.h create mode 100644 draw.c create mode 100644 draw.h create mode 100644 input.c create mode 100644 input.h rewrite main.c (93%) create mode 100644 util.c create mode 100644 util.h diff --git a/Makefile b/Makefile index 7172ee7..3462c50 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,17 @@ CFLAGS = -std=c89 -pedantic -Wall -Wextra -g +SRC = main.c buffer.c util.c draw.c input.c +OBJ = ${SRC:.c=.o} all: clean iomenu -iomenu: - $(CC) main.c -o $@ $(CFLAGS) +.c.o: + ${CC} -c ${CFLAGS} $< + +iomenu: ${OBJ} + ${CC} -o $@ ${OBJ} ${LDFLAGS} clean: - rm -f iomenu + rm -f iomenu ${OBJ} install: iomenu cp iomenu $(PREFIX)/bin/iomenu - -.PHONY: all clean install - diff --git a/README b/README index b58131e..1d589f4 100644 --- a/README +++ b/README @@ -1,7 +1,9 @@ * , ,--. --.-. ,--. ---. , , | | | | | | |--' | | | | - ' `--' ' ' ' `--' ' ' `--` 2016-10-20 + ' `--' ' ' ' `--' ' ' `--` +-------------------------------------------------------------------------------- + IOMENU 2016-10-29 -------------------------------------------------------------------------------- Filter lines form stdin with an interactive menu diff --git a/buffer.c b/buffer.c new file mode 100644 index 0000000..5889473 --- /dev/null +++ b/buffer.c @@ -0,0 +1,175 @@ +#include +#include +#include +#include + +#include "main.h" +#include "util.h" +#include "buffer.h" + +/* + * Fill the buffer apropriately with the lines and headers. + */ +Buffer * +fill_buffer(char *separator) +{ + /* fill buffer with string */ + char string[LINE_SIZE]; + Line *last = NULL; + Buffer *buffer = malloc(sizeof(Buffer)); + FILE *fp = stdin; + + if (!fp) { + die("Can not open file for reading."); + } + + /* read the file into a doubly linked list of lines */ + while (fgets(string, LINE_SIZE, fp)) { + buffer->total++; + last = add_line(buffer, buffer->total, string, separator, last); + } + + /* set the buffer stats */ + buffer->current = buffer->first; + + /* empty line */ + buffer->empty = malloc(sizeof(Line)); + buffer->empty->content = ""; + buffer->empty->comment = ""; + buffer->empty->number = 0; + buffer->empty->matches = 0; + buffer->empty->next = buffer->first; + buffer->empty->prev = buffer->first; + + return buffer; +} + +/* + * Add a line to the end of the current buffer. + * + * This requires to create a new line with a link to the previous line + * and to NULL as the next line. + * + * The previous line's 'next' should be relinked to this new line. + * + * The header's last line have to point to this last line + */ +Line * +add_line(Buffer *buffer, int number, char *string, char *separator, Line *prev) +{ + /* allocate new line */ + Line *line = malloc(sizeof(Line)); + line = parse_line(string, separator); + line->next = NULL; + line->prev = NULL; + buffer->last = line; + line->number = number; + + /* interlink with previous line if exists */ + if (number == 1) { + buffer->first = line; + } else { + prev->next = line; + line->prev = prev; + } + + return line; +} + +/* + * Parse the line content to determine if it is a header and identify the + * separator if any. + */ +Line * +parse_line(char *s, char *separator) +{ + Line *line = malloc(sizeof(Line)); + char *sep = separator ? strstr(s, separator) : NULL; + int pos = sep ? (int) (sep - s) : (int) strlen(s) - 1; + + /* strip trailing newline */ + s[strlen(s) - 1] = '\0'; + + /* fill line->content */ + line->content = malloc((pos + 1) * sizeof(char)); + strncpy(line->content, s, pos); + + /* fill line->comment */ + if (sep) { + line->comment = malloc((strlen(s) - pos) * sizeof(char)); + strcpy(line->comment, s + pos + strlen(separator)); + } else { + line->comment = ""; + } + + /* strip trailing whitespaces from line->content */ + for (pos--; pos > 0 && isspace(line->content[pos]); pos--) { + line->content[pos] = '\0'; + } + + /* strip leading whitespaces from line->comment */ + for (pos = 0; isspace(line->comment[pos]); pos++) + ; + line->comment += pos; + + return line; +} + +/* + * Set buffer->candidates to an array of lines that match and update + * buffer->matching to number of matching candidates. + */ +void +filter_lines(Buffer *buffer, Opt *opt) +{ + Line * line = buffer->first; + buffer->matching = 0; + + while (line) { + line->matches = line_match_input(line, buffer->input, opt); + buffer->matching += line->matches; + + line = line->next; + } +} + +/* + * Check if line matches and return TRUE or FALSE + */ +int +line_match_input(Line *line, char *input, Opt *opt) +{ + if (opt->complete_mode) { + if (!strncmp(input, line->content, strlen(input))) { + return TRUE; + } + } else { + if (strstr(line->content, input)) { + return TRUE; + } + } + + return FALSE; +} + +/* + * Seek the previous matching line, or NULL if none matches. + */ +Line * +matching_prev(Line *line) +{ + while ((line = line->prev) && !line->matches) + ; + return line; +} + +/* + * Seek the next matching line, or NULL if none matches. + */ +Line * +matching_next(Line *line) +{ + while ((line = line->next) && !line->matches) + ; + return line; +} diff --git a/buffer.h b/buffer.h new file mode 100644 index 0000000..7e1f28b --- /dev/null +++ b/buffer.h @@ -0,0 +1,8 @@ +Buffer * fill_buffer(char *); +Line * add_line(Buffer *, int, char *, char *, Line *); +Line * parse_line(char *, char *); +Line * matching_next(Line *); +Line * matching_prev(Line *); +int line_match_input(Line *, char *, Opt *); +void filter_lines(Buffer *, Opt *); + diff --git a/draw.c b/draw.c new file mode 100644 index 0000000..eb7ea44 --- /dev/null +++ b/draw.c @@ -0,0 +1,231 @@ +#include +#include +#include +#include + +#include "draw.h" +#include "util.h" + +/* + * Replace tab as a multiple of 8 spaces in a line. + */ +char * +expand_tabs(char *line) +{ + size_t i, n; + char *converted = malloc(sizeof(char) * (strlen(line) * 8 + 1)); + + for (i = 0, n = 0; i < strlen(line); i++, n++) { + if (line[i] == '\t') { + converted[n] = ' '; + n++; + + for (; (n) % 8 != 0; n++) { + converted[n] = ' '; + } + + n--; + } else { + converted[n] = line[i]; + } + } + + converted[n] = '\0'; + + return converted; +} + +/* + * Print a line to stderr. + */ +void +draw_line(Line *line, int current, int cols, Opt *opt) +{ + size_t i; + int n = 0; + char *content = expand_tabs(line->content); + char *comment = expand_tabs(line->comment); + + /* clean the line in case it was not empty */ + fputs("\033[K", stderr); + + /* line number if option set */ + if (opt->line_numbers) { + if (current) { + fputs("\033[1m", stderr); + } else { + fputs("\033[1;30m", stderr); + } + + fprintf(stderr, "%7d\033[0m ", line->number); + + + n += 8; + } + + /* highlight current line */ + if (current) { + fputs("\033[1;33m", stderr); + } + + /* print content without overflowing terminal width */ + for (i = 0; i < strlen(content) && n < cols; n++, i++) { + fputc(content[i], stderr); + } + + /* print spaces without overflowing terminal width */ + for (i = n; i <= 40 && n < cols; n++, i++) { + fputc(' ', stderr); + } + + /* comments in grey */ + fputs("\033[1;30m", stderr); + + /* print comment without overflowing terminal width */ + for (i = 0; i < strlen(comment) && n < cols; n++, i++) { + fputc(comment[i], stderr); + } + + fputs("\033[0m\n", stderr); + + free(content); + free(comment); +} + +/* + * Print a header title. + */ +void +draw_header() +{ +} + +/* + * Print all the lines from an array of pointer to lines. + * + * The total number oflines printed shall not excess 'count'. + */ +void +draw_lines(Buffer *buffer, int count, int offset, int cols, Opt *opt) +{ + Line *line = buffer->current; + int i = 0; + int j = 0; + + /* seek back from current line to the first line to print */ + while (line && i < count - offset) { + i = line->matches ? i + 1 : i; + line = line->prev; + } + line = line ? line : buffer->first; + + /* print up to count lines that match the input */ + while (line && j < count) { + if (line->matches) { + draw_line(line, line == buffer->current, cols, opt); + j++; + } + + line = line->next; + } + + /* continue up to the end of the screen clearing it */ + for (; j < count; j++) { + fputs("\r\033[K\n", stderr); + } +} + +/* + * Update the screen interface and print all candidates. + * + * This also has to clear the previous lines. + */ +void +draw_screen(Buffer *buffer, int offset, int tty_fd, Opt *opt) +{ + struct winsize w; + int count; + + ioctl(tty_fd, TIOCGWINSZ, &w); + count = MIN(opt->lines, w.ws_row - 2); + + fputs("\n", stderr); + draw_lines(buffer, count, offset, w.ws_col, opt); + + /* go up to the prompt position and update it */ + fprintf(stderr, "\033[%dA", count + 1); + draw_prompt(buffer, w.ws_col, opt); +} + +void draw_clear(int count) +{ + int i; + for (i = 0; i < count + 1; i++) { + fputs("\r\033[K\n", stderr); + } + + fprintf(stderr, "\033[%dA", count + 1); +} + + +/* + * Print the prompt, before the input, with the number of candidates that + * match. + */ +void +draw_prompt(Buffer *buffer, int cols, Opt *opt) +{ + size_t i; + int matching = buffer->matching; + int total = buffer->total; + char *input = expand_tabs(buffer->input); + char *suggest = expand_tabs(buffer->current->content); + + /* for the '/' separator between the numbers */ + cols--; + + /* number of digits */ + for (i = matching; i; i /= 10, cols--) + ; + for (i = total; i; i /= 10, cols--) + ; + + /* 0 also has one digit*/ + cols -= !matching ? 1 : 0; + + /* actual prompt */ + fprintf(stderr, "\r%s\033[K> ", opt->prompt); + cols -= 2 + strlen(opt->prompt); + + /* input without overflowing terminal width */ + for (i = 0; i < strlen(input) && cols > 0; cols--, i++) { + fputc(input[i], stderr); + } + + /* save the cursor position at the end of the input */ + fputs("\033[s", stderr); + + /* grey */ + fputs("\033[1;30m", stderr); + + /* suggest without overflowing terminal width */ + if (opt->complete_mode) { + for (; i < strlen(suggest) && cols > 0; cols--, i++) { + fputc(suggest[i], stderr); + } + } + + /* go to the end of the line */ + for (i = 0; cols > 0; cols--, i++) { + fputc(' ', stderr); + } + + /* total match and line count at the end of the line */ + fprintf(stderr, "%d/%d", matching, total); + + /* restore cursor position at the end of the input */ + fputs("\033[0m\033[u", stderr); + + free(input); + free(suggest); +} diff --git a/draw.h b/draw.h new file mode 100644 index 0000000..8a551b0 --- /dev/null +++ b/draw.h @@ -0,0 +1,9 @@ +#include "main.h" + +char * expand_tabs(char *); +void draw_screen(Buffer *, int, int, Opt *); +void draw_clear(); +void draw_line(Line *, int, int, Opt *); +void draw_header(); +void draw_lines(Buffer *, int, int, int, Opt *); +void draw_prompt(Buffer *, int, Opt *); diff --git a/input.c b/input.c new file mode 100644 index 0000000..94e5128 --- /dev/null +++ b/input.c @@ -0,0 +1,178 @@ +#include +#include +#include +#include + +#include "input.h" +#include "util.h" +#include "draw.h" +#include "buffer.h" + +/* + * Listen for the user input and call the appropriate functions. + */ +void +input_get(Buffer *buffer, int offset, int tty_fd, Opt *opt) +{ + FILE *tty_fp = fopen("/dev/tty", "r"); + + /* receive one character at a time from the terminal */ + struct termios termio_old = set_terminal(tty_fd); + + /* get input char by char from the keyboard. */ + while (input_key(fgetc(tty_fp), buffer, opt)) { + draw_screen(buffer, offset, tty_fd, opt); + } + + /* resets the terminal to the previous state. */ + tcsetattr(tty_fd, TCSANOW, &termio_old); + + fclose(tty_fp); +} + +/* + * Perform action associated with key + */ +int +input_key(char key, Buffer *buffer, Opt *opt) +{ + if (key == opt->validate_key) { + action_print_selection(buffer, opt); + return FALSE; + } + + switch (key) { + + case CONTROL('C'): + return FALSE; + + case CONTROL('U'): + buffer->input[0] = '\0'; + buffer->current = buffer->first; + filter_lines(buffer, opt); + break; + + case CONTROL('W'): + action_remove_word_input(buffer); + filter_lines(buffer, opt); + break; + + case 127: + case CONTROL('H'): /* backspace */ + buffer->input[strlen(buffer->input) - 1] = '\0'; + filter_lines(buffer, opt); + + if (!buffer->current->matches) { + action_jump(buffer, BOTH, opt); + } + break; + + case CONTROL('N'): + action_jump(buffer, NEXT, opt); + break; + + case CONTROL('P'): + action_jump(buffer, PREV, opt); + break; + + case CONTROL('I'): /* tab */ + strcpy(buffer->input, buffer->current->content); + filter_lines(buffer, opt); + break; + + case CONTROL('M'): + case CONTROL('J'): /* enter */ + action_print_selection(buffer, opt); + return FALSE; + + default: + action_add_character(buffer, key, opt); + } + + return TRUE; +} + +/* + * Set the current line to the next/previous/closest matching line. + */ +void +action_jump(Buffer *buffer, int direction, Opt *opt) +{ + Line * line = buffer->current; + + if (direction == BOTH) { + line = matching_next(buffer->current); + line = (line) ? line : matching_prev(buffer->current); + } else if (direction == NEXT) { + line = matching_next(line); + } else if (direction == PREV) { + line = matching_prev(line); + } + + buffer->current = (line) ? line : buffer->current; + + if (opt->print_numbers) + action_print_selection(buffer, opt); +} + +/* + * Send the selection to stdout. + */ +void +action_print_selection(Buffer *buffer, Opt *opt) +{ + fputs("\r\033[K", stderr); + + if (opt->print_numbers) { + printf("%d\n", buffer->current->number); + } else { + if (opt->complete_mode) { + puts(buffer->input); + } else { + puts(buffer->current->content); + } + } +} + +/* + * Remove the last word from the buffer's input + */ +void +action_remove_word_input(Buffer *buffer) +{ + size_t length = strlen(buffer->input) - 1; + int i; + + for (i = length; i >= 0 && isspace(buffer->input[i]); i--) { + buffer->input[i] = '\0'; + } + + length = strlen(buffer->input) - 1; + for (i = length; i >= 0 && !isspace(buffer->input[i]); i--) { + buffer->input[i] = '\0'; + } +} + +/* + * Add a character to the buffer input and filter lines again. + */ +void +action_add_character(Buffer *buffer, char key, Opt *opt) +{ + size_t length = strlen(buffer->input); + + if (isprint(key)) { + buffer->input[length] = key; + buffer->input[length + 1] = '\0'; + } + + filter_lines(buffer, opt); + + if (!buffer->current->matches) { + action_jump(buffer, BOTH, opt); + } + + if (!buffer->current->matches) { + buffer->current = buffer->empty; + } +} diff --git a/input.h b/input.h new file mode 100644 index 0000000..0b1705a --- /dev/null +++ b/input.h @@ -0,0 +1,8 @@ +#include "main.h" + +void input_get(Buffer *, int, int, Opt *); +int input_key(char, Buffer *, Opt *); +void action_jump(Buffer *, int, Opt *); +void action_print_selection(Buffer *, Opt *); +void action_remove_word_input(Buffer *); +void action_add_character(Buffer *, char, Opt *); diff --git a/main.c b/main.c dissimilarity index 93% index a9cb9e8..a8931d6 100644 --- a/main.c +++ b/main.c @@ -1,684 +1,82 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "main.h" -#include "config.h" - -/* add abstraction at no cost, but may add more complexity as well? */ -enum { FALSE = 0, TRUE = 1 }; -enum { NEXT = 0, PREV = 1, BOTH = 2 }; - -/* preprocessor macros */ -#define LENGTH(x) (sizeof(x) / sizeof(*x)) -#define CONTROL(char) (char ^ 0x40) -#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y)) -#define MAX(X, Y) (((X) > (Y)) ? (X) : (Y)) - -/* command line options */ -int opt_line_numbers = FALSE; -int opt_complete_mode = FALSE; -int opt_print_numbers = FALSE; -char opt_validate_key = CONTROL('M'); -char* opt_separator = NULL; -int opt_lines = 30; -char* opt_prompt = ""; - -/* - * Fill the buffer apropriately with the lines and headers. - */ -Buffer * -fill_buffer(char *separator) -{ - /* fill buffer with string */ - char string[LINE_SIZE]; - Line *last = NULL; - Buffer *buffer = malloc(sizeof(Buffer)); - FILE *fp = stdin; - - if (!fp) { - die("Can not open file for reading."); - } - - /* read the file into a doubly linked list of lines */ - while (fgets(string, LINE_SIZE, fp)) { - buffer->total++; - last = add_line(buffer, buffer->total, string, separator, last); - } - - /* set the buffer stats */ - buffer->current = buffer->first; - - /* empty line */ - buffer->empty = malloc(sizeof(Line)); - buffer->empty->content = ""; - buffer->empty->comment = ""; - buffer->empty->number = 0; - buffer->empty->matches = 0; - buffer->empty->next = buffer->first; - buffer->empty->prev = buffer->first; - - return buffer; -} - -/* - * Add a line to the end of the current buffer. - * - * This requires to create a new line with a link to the previous line - * and to NULL as the next line. - * - * The previous line's 'next' should be relinked to this new line. - * - * The header's last line have to point to this last line - */ -Line * -add_line(Buffer *buffer, int number, char *string, char *separator, Line *prev) -{ - /* allocate new line */ - Line *line = malloc(sizeof(Line)); - line = parse_line(string, separator); - line->next = NULL; - line->prev = NULL; - buffer->last = line; - line->number = number; - - /* interlink with previous line if exists */ - if (number == 1) { - buffer->first = line; - } else { - prev->next = line; - line->prev = prev; - } - - return line; -} - -/* - * Parse the line content to determine if it is a header and identify the - * separator if any. - */ -Line * -parse_line(char *s, char *separator) -{ - Line *line = malloc(sizeof(Line)); - char *sep = separator ? strstr(s, separator) : NULL; - int pos = sep ? (int) (sep - s) : (int) strlen(s) - 1; - - /* strip trailing newline */ - s[strlen(s) - 1] = '\0'; - - /* fill line->content */ - line->content = malloc((pos + 1) * sizeof(char)); - strncpy(line->content, s, pos); - - /* fill line->comment */ - if (sep) { - line->comment = malloc((strlen(s) - pos) * sizeof(char)); - strcpy(line->comment, s + pos + strlen(separator)); - } else { - line->comment = ""; - } - - /* strip trailing whitespaces from line->content */ - for (pos--; pos > 0 && isspace(line->content[pos]); pos--) { - line->content[pos] = '\0'; - } - - /* strip leading whitespaces from line->comment */ - for (pos = 0; isspace(line->comment[pos]); pos++) - ; - line->comment += pos; - - return line; -} - -/* - * Set buffer->candidates to an array of lines that match and update - * buffer->matching to number of matching candidates. - */ -void -filter_lines(Buffer *buffer) -{ - Line * line = buffer->first; - buffer->matching = 0; - - while (line) { - line->matches = line_match_input(line, buffer->input); - buffer->matching += line->matches; - - line = line->next; - } -} - -/* - * Check if line matches and return TRUE or FALSE - */ -int -line_match_input(Line *line, char *input) -{ - if (opt_complete_mode) { - if (!strncmp(input, line->content, strlen(input))) { - return TRUE; - } - } else { - if (strstr(line->content, input)) { - return TRUE; - } - } - - return FALSE; -} - -/* - * Seek the previous matching line, or NULL if none matches. - */ -Line * -matching_prev(Line *line) -{ - while ((line = line->prev) && !line->matches) - ; - return line; -} - -/* - * Seek the next matching line, or NULL if none matches. - */ -Line * -matching_next(Line *line) -{ - while ((line = line->next) && !line->matches) - ; - return line; -} - -/* - * Replace tab as a multiple of 8 spaces in a line. - */ -char * -expand_tabs(char *line) -{ - size_t i, n; - char *converted = malloc(sizeof(char) * (strlen(line) * 8 + 1)); - - for (i = 0, n = 0; i < strlen(line); i++, n++) { - if (line[i] == '\t') { - converted[n] = ' '; - n++; - - for (; (n) % 8 != 0; n++) { - converted[n] = ' '; - } - - n--; - } else { - converted[n] = line[i]; - } - } - - converted[n] = '\0'; - - return converted; -} - -/* - * Print a line to stderr. - */ -void -print_line(Line *line, int current, int cols) -{ - size_t i; - int n = 0; - char *content = expand_tabs(line->content); - char *comment = expand_tabs(line->comment); - - /* clean the line in case it was not empty */ - fputs("\033[K", stderr); - - /* line number if option set */ - if (opt_line_numbers) { - if (current) { - fputs("\033[1m", stderr); - } else { - fputs("\033[1;30m", stderr); - } - - fprintf(stderr, "%7d\033[0m ", line->number); - - - n += 8; - } - - /* highlight current line */ - if (current) { - fputs("\033[1;33m", stderr); - } - - /* print content without overflowing terminal width */ - for (i = 0; i < strlen(content) && n < cols; n++, i++) { - fputc(content[i], stderr); - } - - /* print spaces without overflowing terminal width */ - for (i = n; i <= 40 && n < cols; n++, i++) { - fputc(' ', stderr); - } - - /* comments in grey */ - fputs("\033[1;30m", stderr); - - /* print comment without overflowing terminal width */ - for (i = 0; i < strlen(comment) && n < cols; n++, i++) { - fputc(comment[i], stderr); - } - - fputs("\033[0m\n", stderr); - - free(content); - free(comment); -} - -/* - * Print a header title. - */ -void -print_header() -{ -} - -/* - * Print all the lines from an array of pointer to lines. - * - * The total number oflines printed shall not excess 'count'. - */ -void -print_lines(Buffer *buffer, int count, int offset, int cols) -{ - Line *line = buffer->current; - int i = 0; - int j = 0; - - /* seek back from current line to the first line to print */ - while (line && i < count - offset) { - i = line->matches ? i + 1 : i; - line = line->prev; - } - line = line ? line : buffer->first; - - /* print up to count lines that match the input */ - while (line && j < count) { - if (line->matches) { - print_line(line, line == buffer->current, cols); - j++; - } - - line = line->next; - } - - /* continue up to the end of the screen clearing it */ - for (; j < count; j++) { - fputs("\r\033[K\n", stderr); - } -} - -/* - * Update the screen interface and print all candidates. - * - * This also has to clear the previous lines. - */ -void -update_screen(Buffer *buffer, int count, int offset, int tty_fd) -{ - struct winsize w; - ioctl(tty_fd, TIOCGWINSZ, &w); - count = MIN(count, w.ws_row - 2); - - fputs("\n", stderr); - print_lines(buffer, count, offset, w.ws_col); - - /* go up to the prompt position and update it */ - fprintf(stderr, "\033[%dA", count + 1); - print_prompt(buffer, w.ws_col); -} - -void clear_screen(int count) -{ - int i; - for (i = 0; i < count + 1; i++) { - fputs("\r\033[K\n", stderr); - } - - fprintf(stderr, "\033[%dA", count + 1); -} - -/* - * Set terminal to send one char at a time for interactive mode, and return the - * last terminal state. - */ -struct termios -set_terminal(int tty_fd) -{ - struct termios termio_old; - struct termios termio_new; - - /* set the terminal to send one key at a time. */ - - /* get the terminal's state */ - if (tcgetattr(tty_fd, &termio_old) < 0) { - die("Can not get terminal attributes with tcgetattr()."); - } - - /* create a new modified state by switching the binary flags */ - termio_new = termio_old; - termio_new.c_lflag &= ~(ICANON | ECHO | IGNBRK); - - /* apply this state to current terminal now (TCSANOW) */ - tcsetattr(tty_fd, TCSANOW, &termio_new); - - return termio_old; -} - -/* - * Listen for the user input and call the appropriate functions. - */ -void -get_input(Buffer *buffer, int count, int offset, int tty_fd) -{ - FILE *tty_fp = fopen("/dev/tty", "r"); - - /* receive one character at a time from the terminal */ - struct termios termio_old = set_terminal(tty_fd); - - /* get input char by char from the keyboard. */ - while (do_key(fgetc(tty_fp), buffer)) { - update_screen(buffer, count, offset, tty_fd); - } - - /* resets the terminal to the previous state. */ - tcsetattr(tty_fd, TCSANOW, &termio_old); - - fclose(tty_fp); -} - -/* - * Perform action associated with key - */ -int -do_key(char key, Buffer *buffer) -{ - if (key == opt_validate_key) { - do_print_selection(buffer); - return FALSE; - } - - switch (key) { - - case CONTROL('C'): - return FALSE; - - case CONTROL('U'): - buffer->input[0] = '\0'; - buffer->current = buffer->first; - filter_lines(buffer); - break; - - case CONTROL('W'): - do_remove_word_input(buffer); - filter_lines(buffer); - break; - - case 127: - case CONTROL('H'): /* backspace */ - buffer->input[strlen(buffer->input) - 1] = '\0'; - filter_lines(buffer); - - if (!buffer->current->matches) { - do_jump(buffer, BOTH); - } - break; - - case CONTROL('N'): - do_jump(buffer, NEXT); - break; - - case CONTROL('P'): - do_jump(buffer, PREV); - break; - - case CONTROL('I'): /* tab */ - strcpy(buffer->input, buffer->current->content); - filter_lines(buffer); - break; - - case CONTROL('M'): - case CONTROL('J'): /* enter */ - do_print_selection(buffer); - return FALSE; - - default: - do_add_character(buffer, key); - } - - return TRUE; -} - -/* - * Set the current line to the next/previous/closest matching line. - */ -void -do_jump(Buffer *buffer, int direction) -{ - Line * line = buffer->current; - - if (direction == BOTH) { - line = matching_next(buffer->current); - line = (line) ? line : matching_prev(buffer->current); - } else if (direction == NEXT) { - line = matching_next(line); - } else if (direction == PREV) { - line = matching_prev(line); - } - - buffer->current = (line) ? line : buffer->current; - - if (opt_print_numbers) - do_print_selection(buffer); -} - -/* - * Send the selection to stdout. - */ -void -do_print_selection(Buffer *buffer) -{ - fputs("\r\033[K", stderr); - - if (opt_print_numbers) { - printf("%d\n", buffer->current->number); - } else { - if (opt_complete_mode) { - puts(buffer->input); - } else { - puts(buffer->current->content); - } - } -} - -/* - * Remove the last word from the buffer's input - */ -void -do_remove_word_input(Buffer *buffer) -{ - size_t length = strlen(buffer->input) - 1; - int i; - - for (i = length; i >= 0 && isspace(buffer->input[i]); i--) { - buffer->input[i] = '\0'; - } - - length = strlen(buffer->input) - 1; - for (i = length; i >= 0 && !isspace(buffer->input[i]); i--) { - buffer->input[i] = '\0'; - } -} - -/* - * Add a character to the buffer input and filter lines again. - */ -void -do_add_character(Buffer *buffer, char key) -{ - size_t length = strlen(buffer->input); - - if (isprint(key)) { - buffer->input[length] = key; - buffer->input[length + 1] = '\0'; - } - - filter_lines(buffer); - - if (!buffer->current->matches) { - do_jump(buffer, BOTH); - } - - if (!buffer->current->matches) { - buffer->current = buffer->empty; - } -} - -/* - * Print the prompt, before the input, with the number of candidates that - * match. - */ -void -print_prompt(Buffer *buffer, int cols) -{ - size_t i; - int matching = buffer->matching; - int total = buffer->total; - char *input = expand_tabs(buffer->input); - char *suggest = expand_tabs(buffer->current->content); - - /* for the '/' separator between the numbers */ - cols--; - - /* number of digits */ - for (i = matching; i; i /= 10, cols--) - ; - for (i = total; i; i /= 10, cols--) - ; - - /* 0 also has one digit*/ - cols -= !matching ? 1 : 0; - - /* actual prompt */ - fprintf(stderr, "\r%s\033[K> ", opt_prompt); - cols -= 2 + strlen(opt_prompt); - - /* input without overflowing terminal width */ - for (i = 0; i < strlen(input) && cols > 0; cols--, i++) { - fputc(input[i], stderr); - } - - /* save the cursor position at the end of the input */ - fputs("\033[s", stderr); - - /* grey */ - fputs("\033[1;30m", stderr); - - /* suggest without overflowing terminal width */ - if (opt_complete_mode) { - for (; i < strlen(suggest) && cols > 0; cols--, i++) { - fputc(suggest[i], stderr); - } - } - - /* go to the end of the line */ - for (i = 0; cols > 0; cols--, i++) { - fputc(' ', stderr); - } - - /* total match and line count at the end of the line */ - fprintf(stderr, "%d/%d", matching, total); - - /* restore cursor position at the end of the input */ - fputs("\033[0m\033[u", stderr); - - free(input); - free(suggest); -} - -/* - * Reset the terminal state and exit with error. - */ -void -die(const char *s) -{ - /* tcsetattr(STDIN_FILENO, TCSANOW, &termio_old); */ - fprintf(stderr, "%s\n", s); - exit(EXIT_FAILURE); -} - -int -main(int argc, char *argv[]) -{ - int i; - Buffer *buffer = NULL; - int offset = 3; - int tty_fd = open("/dev/tty", O_RDWR); - - /* command line arguments */ - for (i = 0; i < argc; i++) { - if (argv[i][0] == '-') { - switch (argv[i][1]) { - case 'n': - opt_line_numbers = TRUE; - break; - case 'c': - opt_complete_mode = TRUE; - break; - case 'N': - opt_print_numbers = TRUE; - opt_line_numbers = TRUE; - break; - case 'k': - opt_validate_key = (argv[i++][0] == '^') ? - CONTROL(argv[i][1]): - argv[i][0]; - break; - case 's': - opt_separator = argv[++i]; - break; - case 'l': - if (sscanf(argv[++i], "%d", &opt_lines) <= 0) - die("Wrong number format after -l."); - break; - case 'p': - opt_prompt = argv[++i]; - break; - } - } - } - - /* command line arguments */ - buffer = fill_buffer(opt_separator); - - /* set the interface */ - filter_lines(buffer); - update_screen(buffer, opt_lines, offset, tty_fd); - - /* listen and interact to input */ - get_input(buffer, opt_lines, offset, tty_fd); - - clear_screen(opt_lines); - - /* close files descriptors and pointers, and free memory */ - close(tty_fd); - - return 0; -} +#include +#include +#include +#include +#include +#include +#include +#include + +#include "main.h" +#include "util.h" +#include "buffer.h" +#include "draw.h" +#include "input.h" + +int +main(int argc, char *argv[]) +{ + int i; + Buffer *buffer = NULL; + int offset = 3; + int tty_fd = open("/dev/tty", O_RDWR); + Opt *opt = malloc(sizeof(Opt)); + + opt->line_numbers = FALSE; + opt->complete_mode = FALSE; + opt->print_numbers = FALSE; + opt->validate_key = CONTROL('M'); + opt->separator = NULL; + opt->lines = 30; + opt->prompt = ""; + + /* command line arguments */ + for (i = 0; i < argc; i++) { + if (argv[i][0] == '-') { + switch (argv[i][1]) { + case 'n': + opt->line_numbers = TRUE; + break; + case 'c': + opt->complete_mode = TRUE; + break; + case 'N': + opt->print_numbers = TRUE; + opt->line_numbers = TRUE; + break; + case 'k': + opt->validate_key = (argv[i++][0] == '^') ? + CONTROL(argv[i][1]): + argv[i][0]; + break; + case 's': + opt->separator = argv[++i]; + break; + case 'l': + if (sscanf(argv[++i], "%d", &opt->lines) <= 0) + die("Wrong number format after -l."); + break; + case 'p': + opt->prompt = argv[++i]; + break; + } + } + } + + /* command line arguments */ + buffer = fill_buffer(opt->separator); + + /* set the interface */ + filter_lines(buffer, opt); + draw_screen(buffer, offset, tty_fd, opt); + + /* listen and interact to input */ + input_get(buffer, offset, tty_fd, opt); + + draw_clear(opt->lines); + + /* close files descriptors and pointers, and free memory */ + close(tty_fd); + + return 0; +} diff --git a/main.h b/main.h index b38fe3e..b868678 100644 --- a/main.h +++ b/main.h @@ -1,7 +1,21 @@ -#include - #include "config.h" +#ifndef MAIN_H +#define MAIN_H + +/* + * Options from the command line, to pass to each function that need some + */ +typedef struct Opt { + int line_numbers; + int complete_mode; + int print_numbers; + char validate_key; + char *separator; + int lines; + char *prompt; +} Opt; + /* * Line coming from stdin, wrapped in a header. */ @@ -52,34 +66,4 @@ typedef struct Buffer { Line *last; } Buffer; - -void die(const char *); - -/* buffer */ -Buffer * fill_buffer(char *); -Line * parse_line(char *, char *); -Line * add_line(Buffer *, int, char *, char *, Line *); -Line * matching_next(Line *); - -/* matching */ -int line_match_input(Line *, char *); -void filter_lines(Buffer *); - -/* drawing */ -char * expand_tabs(char *); -void print_line(Line *, int, int); -void print_header(); -void print_lines(Buffer *, int, int, int); -void update_screen(Buffer *, int, int, int); - -/* tty */ -struct termios terminal_set(int); - -/* input */ -void get_input(Buffer *, int, int, int); -int do_key(char, Buffer *); -void do_jump(Buffer *, int); -void do_print_selection(Buffer *); -void do_remove_word_input(Buffer *); -void do_add_character(Buffer *, char); -void print_prompt(Buffer *, int); +#endif diff --git a/util.c b/util.c new file mode 100644 index 0000000..7b0ca5c --- /dev/null +++ b/util.c @@ -0,0 +1,44 @@ +#include +#include +#include +#include + +#include "util.h" + +/* + * Reset the terminal state and exit with error. + */ +void +die(const char *s) +{ + /* tcsetattr(STDIN_FILENO, TCSANOW, &termio_old); */ + fprintf(stderr, "%s\n", s); + exit(EXIT_FAILURE); +} + +/* + * Set terminal to send one char at a time for interactive mode, and return the + * last terminal state. + */ +struct termios +set_terminal(int tty_fd) +{ + struct termios termio_old; + struct termios termio_new; + + /* set the terminal to send one key at a time. */ + + /* get the terminal's state */ + if (tcgetattr(tty_fd, &termio_old) < 0) { + die("Can not get terminal attributes with tcgetattr()."); + } + + /* create a new modified state by switching the binary flags */ + termio_new = termio_old; + termio_new.c_lflag &= ~(ICANON | ECHO | IGNBRK); + + /* apply this state to current terminal now (TCSANOW) */ + tcsetattr(tty_fd, TCSANOW, &termio_new); + + return termio_old; +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..6029be8 --- /dev/null +++ b/util.h @@ -0,0 +1,12 @@ +/* enums */ +enum { FALSE = 0, TRUE = 1 }; +enum { NEXT = 0, PREV = 1, BOTH = 2 }; + +/* macros */ +#define LENGTH(x) (sizeof(x) / sizeof(*x)) +#define CONTROL(char) (char ^ 0x40) +#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y)) +#define MAX(X, Y) (((X) > (Y)) ? (X) : (Y)) + +void die(const char *); +struct termios set_terminal(int); -- 2.11.4.GIT