X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=miscutils%2Fless.c;h=596490483e419ae5e2b3023183282bf476c3500a;hb=eea561871b45a2335ab6a09f14dad627ffcdc1cd;hp=43bc6737e2cbc7558c8f79e9a60b7f1174ae3d51;hpb=9200e79d21dbe3d91498f8fc7d33531286ae7d0d;p=oweals%2Fbusybox.git diff --git a/miscutils/less.c b/miscutils/less.c index 43bc6737e..596490483 100644 --- a/miscutils/less.c +++ b/miscutils/less.c @@ -2,54 +2,46 @@ /* * Mini less implementation for busybox * - * * Copyright (C) 2005 by Rob Sullivan * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - * - * This program needs a lot of development, so consider it in a beta stage - * at best. + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +/* + * This program needs a lot of development, so consider it in a beta stage + * at best. * - * TODO: - * - Add more regular expression support - search modifiers, certain matches, etc. - * - Add more complex bracket searching - currently, nested brackets are - * not considered. - * - Add support for "F" as an input. This causes less to act in - * a similar way to tail -f. - * - Check for binary files, and prompt the user if a binary file - * is detected. - * - Allow horizontal scrolling. Currently, lines simply continue onto - * the next line, per the terminal's discretion + * TODO: + * - Add more regular expression support - search modifiers, certain matches, etc. + * - Add more complex bracket searching - currently, nested brackets are + * not considered. + * - Add support for "F" as an input. This causes less to act in + * a similar way to tail -f. + * - Check for binary files, and prompt the user if a binary file + * is detected. + * - Allow horizontal scrolling. Currently, lines simply continue onto + * the next line, per the terminal's discretion * - * Notes: - * - filename is an array and not a pointer because that avoids all sorts - * of complications involving the fact that something that is pointed to - * will be changed if the pointer is changed. - * - the inp file pointer is used so that keyboard input works after - * redirected input has been read from stdin + * Notes: + * - filename is an array and not a pointer because that avoids all sorts + * of complications involving the fact that something that is pointed to + * will be changed if the pointer is changed. + * - the inp file pointer is used so that keyboard input works after + * redirected input has been read from stdin */ +#include "busybox.h" #include #include #include #include #include -#include #include -#include "busybox.h" + +#ifdef CONFIG_FEATURE_LESS_REGEXP +#include "xregex.h" +#endif + /* These are the escape sequences corresponding to special keys */ #define REAL_KEY_UP 'A' @@ -72,110 +64,47 @@ #define NORMAL "\033[0m" /* The escape code to clear the screen */ -#define CLEAR "\033[2J" +#define CLEAR "\033[H\033[J" /* Maximum number of lines in a file */ #define MAXLINES 10000 -/* Get height and width of terminal */ -#define tty_width_height() get_terminal_width_height(0, &width, &height) - -/* Function prototypes */ -static void set_tty_cooked(void); -static void set_tty_raw(void); -static void tless_exit(int code); -static int tless_getch(void); -static void move_cursor(int x, int y); -static void clear_line(void); -static void data_readlines(void); -static void free_flines(void); -#ifdef CONFIG_FEATURE_LESS_FLAGS -static int calc_percent(void); -#endif -static int reverse_percent(int percentage); -#ifdef CONFIG_FEATURE_LESS_FLAGS -static void m_status_print(void); -static void medium_status_print(void); -#endif -static void status_print(void); -static void buffer_print(void); -static void buffer_init(void); -static void buffer_down(int nlines); -static void buffer_up(int nlines); -static void buffer_line(int linenum); -static void keypress_process(int keypress); -static void colon_process(void); -static void number_process(int first_digit); -#ifdef CONFIG_FEATURE_LESS_FLAGCS -static void flag_change(void); -static void show_flag_status(void); -#endif -static void examine_file(void); -static void next_file(void); -static void previous_file(void); -static void first_file(void); -static void remove_current_file(void); -static void full_repaint(void); -static void add_linenumbers(void); -static void save_input_to_file(void); -#ifdef CONFIG_FEATURE_LESS_MARKS -static void add_mark(void); -static void goto_mark(void); -#endif -#ifdef CONFIG_FEATURE_LESS_REGEXP -static void regex_process(void); -char *process_regex_on_line(char *line, regex_t *pattern); -char *insert_highlights(char *line, int start, int end); -static void goto_match (int match); -static void search_backwards(void); -#endif -#ifdef CONFIG_FEATURE_LESS_BRACKETS -static char opp_bracket (char bracket); -static void match_right_bracket (char bracket); -static void match_left_bracket (char bracket); -#endif -int less_main(int argc, char *argv[]); - static int height; static int width; static char **files; static char filename[256]; -static char buffer[100][256]; -static char *flines[MAXLINES]; +static char **buffer; +static char **flines; static int current_file = 1; -static int line_pos = 0; +static int line_pos; static int num_flines; static int num_files = 1; -static int past_eof = 0; +static int past_eof; /* Command line options */ -static int E_FLAG = 0; -static int M_FLAG = 0; -static int N_FLAG = 0; -static int m_FLAG = 0; -static int TILDE_FLAG = 0; +static unsigned long flags; +#define FLAG_E 1 +#define FLAG_M (1<<1) +#define FLAG_m (1<<2) +#define FLAG_N (1<<3) +#define FLAG_TILDE (1<<4) /* This is needed so that program behaviour changes when input comes from stdin */ -static int inp_stdin = 0; -/* This is required so that when a file is requested to be examined after - input has come from stdin (e.g. dmesg | less), the input stream from - the keyboard still stays the same. If it switched back to stdin, keyboard - input wouldn't work. */ -static int ea_inp_stdin = 0; +static int inp_stdin; #ifdef CONFIG_FEATURE_LESS_MARKS static int mark_lines[15][2]; -static int num_marks = 0; +static int num_marks; #endif #ifdef CONFIG_FEATURE_LESS_REGEXP -static int match_found = 0; -static int match_lines[100]; -static int match_pos = 0; -static int num_matches = 0; -static int match_backwards = 0; -static int num_back_match = 1; +static int match_found; +static int *match_lines; +static int match_pos; +static int num_matches; +static int match_backwards; +static regex_t old_pattern; #endif /* Needed termios structures */ @@ -185,151 +114,138 @@ static struct termios term_orig, term_vi; static FILE *inp; /* Reset terminal input to normal */ -static void set_tty_cooked() { - fflush(stdout); - tcsetattr(0, TCSANOW, &term_orig); +static void set_tty_cooked(void) +{ + fflush(stdout); + tcsetattr(fileno(inp), TCSANOW, &term_orig); } -/* Set terminal input to raw mode */ -static void set_tty_raw() { - tcgetattr(0, &term_orig); - term_vi = term_orig; - term_vi.c_lflag &= (~ICANON & ~ECHO); - term_vi.c_iflag &= (~IXON & ~ICRNL); - term_vi.c_oflag &= (~ONLCR); - term_vi.c_cc[VMIN] = 1; - term_vi.c_cc[VTIME] = 0; - tcsetattr(0, TCSANOW, &term_vi); +/* Set terminal input to raw mode (taken from vi.c) */ +static void set_tty_raw(void) +{ + tcsetattr(fileno(inp), TCSANOW, &term_vi); } /* Exit the program gracefully */ -static void tless_exit(int code) { - +static void tless_exit(int code) +{ /* TODO: We really should save the terminal state when we start, - and restore it when we exit. Less does this with the + and restore it when we exit. Less does this with the "ti" and "te" termcap commands; can this be done with only termios.h? */ - + putchar('\n'); exit(code); } /* Grab a character from input without requiring the return key. If the character is ASCII \033, get more characters and assign certain sequences - special return codes. Note that this function works best with raw input. */ -int tless_getch() { - + special return codes. Note that this function works best with raw input. */ +static int tless_getch(void) +{ + int input; + set_tty_raw(); - char input_key[3]; - - input_key[0] = getc(inp); + + input = getc(inp); /* Detect escape sequences (i.e. arrow keys) and handle them accordingly */ - - if (input_key[0] == '\033') { - input_key[1] = getc(inp); - input_key[2] = getc(inp); + + if (input == '\033' && getc(inp) == '[') { + input = getc(inp); set_tty_cooked(); - if (input_key[1] == '[') { - if (input_key[2] == REAL_KEY_UP) - return KEY_UP; - else if (input_key[2] == REAL_KEY_DOWN) - return KEY_DOWN; - else if (input_key[2] == REAL_KEY_RIGHT) - return KEY_RIGHT; - else if (input_key[2] == REAL_KEY_LEFT) - return KEY_LEFT; - else if (input_key[2] == REAL_PAGE_UP) - return PAGE_UP; - else if (input_key[2] == REAL_PAGE_DOWN) - return PAGE_DOWN; - } + if (input == REAL_KEY_UP) + return KEY_UP; + else if (input == REAL_KEY_DOWN) + return KEY_DOWN; + else if (input == REAL_KEY_RIGHT) + return KEY_RIGHT; + else if (input == REAL_KEY_LEFT) + return KEY_LEFT; + else if (input == REAL_PAGE_UP) + return PAGE_UP; + else if (input == REAL_PAGE_DOWN) + return PAGE_DOWN; } /* The input is a normal ASCII value */ else { set_tty_cooked(); - return input_key[0]; + return input; } return 0; } -/* Move the cursor to a position (x,y), where (0,0) is the +/* Move the cursor to a position (x,y), where (0,0) is the top-left corner of the console */ -static void move_cursor(int x, int y) { +static void move_cursor(int x, int y) +{ printf("\033[%i;%iH", x, y); } -static void clear_line() { +static void clear_line(void) +{ move_cursor(height, 0); printf("\033[K"); } -static void data_readlines() { - +/* This adds line numbers to every line, as the -N flag necessitates */ +static void add_linenumbers(void) +{ + char current_line[256]; + int i; + + for (i = 0; i <= num_flines; i++) { + safe_strncpy(current_line, flines[i], 256); + flines[i] = bb_xasprintf("%5d %s", i + 1, current_line); + } +} + +static void data_readlines(void) +{ int i; char current_line[256]; FILE *fp; - - fp = (inp_stdin) ? stdin : bb_xfopen(filename, "rt"); - - for (i = 0; (!feof(fp)) && (i <= MAXLINES); i++) { + + fp = (inp_stdin) ? stdin : bb_xfopen(filename, "r"); + flines = NULL; + for (i = 0; (feof(fp)==0) && (i <= MAXLINES); i++) { strcpy(current_line, ""); fgets(current_line, 256, fp); - bb_xferror(fp, filename); - flines[i] = (char *) bb_xstrndup(current_line, (strlen(current_line) + 1) * sizeof(char)); + if (fp != stdin) + bb_xferror(fp, filename); + flines = xrealloc(flines, (i+1) * sizeof(char *)); + flines[i] = bb_xstrdup(current_line); } num_flines = i - 2; -/* Reset variables for a new file */ - + /* Reset variables for a new file */ + line_pos = 0; past_eof = 0; - + fclose(fp); - if (inp_stdin) - inp = fopen(CURRENT_TTY, "r"); - else - inp = stdin; + if (inp == NULL) + inp = (inp_stdin) ? bb_xfopen(CURRENT_TTY, "r") : stdin; - if (ea_inp_stdin) { - fclose(inp); - inp = fopen(CURRENT_TTY, "r"); - } - - if (N_FLAG) + if (flags & FLAG_N) add_linenumbers(); } -/* Free the file data */ -static void free_flines() { - - int i; - - for (i = 0; i <= num_flines; i++) - free(flines[i]); -} - #ifdef CONFIG_FEATURE_LESS_FLAGS -/* Calculate the percentage the current line position is through the file */ -int calc_percent() { - return ((100 * (line_pos + height - 2) / num_flines) + 1); -} -#endif -/* Turn a percentage into a line number */ -int reverse_percent(int percentage) { - double linenum = percentage; - linenum = ((linenum / 100) * num_flines) - 1; - return(linenum); +/* Interestingly, writing calc_percent as a function and not a prototype saves around 32 bytes + * on my build. */ +static int calc_percent(void) +{ + return ((100 * (line_pos + height - 2) / num_flines) + 1); } -#ifdef CONFIG_FEATURE_LESS_FLAGS /* Print a status line if -M was specified */ -static void m_status_print() { - +static void m_status_print(void) +{ int percentage; - + if (!past_eof) { if (!line_pos) { if (num_files > 1) @@ -341,7 +257,7 @@ static void m_status_print() { else { printf("%s %s lines %i-%i/%i ", HIGHLIGHT, filename, line_pos + 1, line_pos + height - 1, num_flines + 1); } - + if (line_pos == num_flines - height + 2) { printf("(END) %s", NORMAL); if ((num_files > 1) && (current_file != num_files)) @@ -349,7 +265,7 @@ static void m_status_print() { } else { percentage = calc_percent(); - printf("%i%s %s", percentage, "%", NORMAL); + printf("%i%% %s", percentage, NORMAL); } } else { @@ -361,28 +277,28 @@ static void m_status_print() { } /* Print a status line if -m was specified */ -static void medium_status_print() { - +static void medium_status_print(void) +{ int percentage; percentage = calc_percent(); - + if (!line_pos) - printf("%s%s %i%s%s", HIGHLIGHT, filename, percentage, "%", NORMAL); + printf("%s%s %i%%%s", HIGHLIGHT, filename, percentage, NORMAL); else if (line_pos == num_flines - height + 2) printf("%s(END)%s", HIGHLIGHT, NORMAL); else - printf("%s%i%s%s", HIGHLIGHT, percentage, "%", NORMAL); + printf("%s%i%%%s", HIGHLIGHT, percentage, NORMAL); } #endif /* Print the status line */ -static void status_print() { - +static void status_print(void) +{ /* Change the status if flags have been set */ -#ifdef CONFIG_FEATURE_LESS_FLAGS - if (M_FLAG) +#ifdef CONFIG_FEATURE_LESS_FLAGS + if (flags & FLAG_M) m_status_print(); - else if (m_FLAG) + else if (flags & FLAG_m) medium_status_print(); /* No flags set */ else { @@ -398,7 +314,7 @@ static void status_print() { printf("%s%s%s%s", HIGHLIGHT, "- Next: ", files[current_file], NORMAL); } else { - printf("%c", ':'); + putchar(':'); } #ifdef CONFIG_FEATURE_LESS_FLAGS } @@ -406,100 +322,110 @@ static void status_print() { } /* Print the buffer */ -static void buffer_print() { - +static void buffer_print(void) +{ int i; - + + printf("%s", CLEAR); if (num_flines >= height - 2) { - printf("%s", CLEAR); - move_cursor(0,0); for (i = 0; i < height - 1; i++) printf("%s", buffer[i]); - status_print(); } else { - printf("%s", CLEAR); - move_cursor(0,0); for (i = 1; i < (height - 1 - num_flines); i++) putchar('\n'); for (i = 0; i < height - 1; i++) printf("%s", buffer[i]); - status_print(); } + + status_print(); } /* Initialise the buffer */ -static void buffer_init() { - +static void buffer_init(void) +{ int i; - - for (i = 0; i < (height - 1); i++) - memset(buffer[i], '\0', 256); - - /* Fill the buffer until the end of the file or the + + if (buffer == NULL) { + /* malloc the number of lines needed for the buffer */ + buffer = xrealloc(buffer, height * sizeof(char *)); + } else { + for (i = 0; i < (height - 1); i++) + free(buffer[i]); + } + + /* Fill the buffer until the end of the file or the end of the buffer is reached */ for (i = 0; (i < (height - 1)) && (i <= num_flines); i++) { - strcpy(buffer[i], flines[i]); + buffer[i] = bb_xstrdup(flines[i]); } - + /* If the buffer still isn't full, fill it with blank lines */ for (; i < (height - 1); i++) { - strcpy(buffer[i], ""); + buffer[i] = bb_xstrdup(""); } } /* Move the buffer up and down in the file in order to scroll */ -static void buffer_down(int nlines) { - +static void buffer_down(int nlines) +{ int i; - + if (!past_eof) { if (line_pos + (height - 3) + nlines < num_flines) { line_pos += nlines; - for (i = 0; i < (height - 1); i++) - strcpy(buffer[i], flines[line_pos + i]); + for (i = 0; i < (height - 1); i++) { + free(buffer[i]); + buffer[i] = bb_xstrdup(flines[line_pos + i]); + } } else { /* As the number of lines requested was too large, we just move - to the end of the file */ - while (line_pos + (height - 3) + 1 < num_flines) { + to the end of the file */ + while (line_pos + (height - 3) + 1 < num_flines) { line_pos += 1; - for (i = 0; i < (height - 1); i++) - strcpy(buffer[i], flines[line_pos + i]); + for (i = 0; i < (height - 1); i++) { + free(buffer[i]); + buffer[i] = bb_xstrdup(flines[line_pos + i]); + } } } /* We exit if the -E flag has been set */ - if (E_FLAG && (line_pos + (height - 2) == num_flines)) + if ((flags & FLAG_E) && (line_pos + (height - 2) == num_flines)) tless_exit(0); } } -static void buffer_up(int nlines) { - +static void buffer_up(int nlines) +{ int i; int tilde_line; - + if (!past_eof) { if (line_pos - nlines >= 0) { line_pos -= nlines; - for (i = 0; i < (height - 1); i++) - strcpy(buffer[i], flines[line_pos + i]); + for (i = 0; i < (height - 1); i++) { + free(buffer[i]); + buffer[i] = bb_xstrdup(flines[line_pos + i]); + } } else { /* As the requested number of lines to move was too large, we move one line up at a time until we can't. */ while (line_pos != 0) { line_pos -= 1; - for (i = 0; i < (height - 1); i++) - strcpy(buffer[i], flines[line_pos + i]); + for (i = 0; i < (height - 1); i++) { + free(buffer[i]); + buffer[i] = bb_xstrdup(flines[line_pos + i]); + } } } } else { /* Work out where the tildes start */ tilde_line = num_flines - line_pos + 3; - + line_pos -= nlines; /* Going backwards nlines lines has taken us to a point where nothing is past the EOF, so we revert to normal. */ @@ -509,254 +435,346 @@ static void buffer_up(int nlines) { } else { /* We only move part of the buffer, as the rest - is past the EOF */ + is past the EOF */ for (i = 0; i < (height - 1); i++) { + free(buffer[i]); if (i < tilde_line - nlines + 1) - strcpy(buffer[i], flines[line_pos + i]); + buffer[i] = bb_xstrdup(flines[line_pos + i]); else { if (line_pos >= num_flines - height + 2) - strcpy(buffer[i], "~\n"); + buffer[i] = bb_xstrdup("~\n"); } } - } + } } } -static void buffer_line(int linenum) { - +static void buffer_line(int linenum) +{ int i; - past_eof = 0; - if (linenum < 1 || linenum > num_flines) { + if (linenum < 0 || linenum > num_flines) { clear_line(); - printf("%s%s%i%s", HIGHLIGHT, "Cannot seek to line number ", linenum, NORMAL); + printf("%s%s%i%s", HIGHLIGHT, "Cannot seek to line number ", linenum + 1, NORMAL); } else if (linenum < (num_flines - height - 2)) { - for (i = 0; i < (height - 1); i++) - strcpy(buffer[i], flines[linenum + i]); + for (i = 0; i < (height - 1); i++) { + free(buffer[i]); + buffer[i] = bb_xstrdup(flines[linenum + i]); + } line_pos = linenum; + buffer_print(); } else { for (i = 0; i < (height - 1); i++) { + free(buffer[i]); if (linenum + i < num_flines + 2) - strcpy(buffer[i], flines[linenum + i]); + buffer[i] = bb_xstrdup(flines[linenum + i]); else - strcpy(buffer[i], (TILDE_FLAG) ? "\n" : "~\n"); + buffer[i] = bb_xstrdup((flags & FLAG_TILDE) ? "\n" : "~\n"); } line_pos = linenum; /* Set past_eof so buffer_down and buffer_up act differently */ past_eof = 1; + buffer_print(); + } +} + +/* Reinitialise everything for a new file - free the memory and start over */ +static void reinitialise(void) +{ + int i; + + for (i = 0; i <= num_flines; i++) + free(flines[i]); + free(flines); + + data_readlines(); + buffer_init(); + buffer_print(); +} + +static void examine_file(void) +{ + int newline_offset; + + clear_line(); + printf("Examine: "); + fgets(filename, 256, inp); + + /* As fgets adds a newline to the end of an input string, we + need to remove it */ + newline_offset = strlen(filename) - 1; + filename[newline_offset] = '\0'; + + files[num_files] = bb_xstrdup(filename); + current_file = num_files + 1; + num_files++; + + inp_stdin = 0; + reinitialise(); +} + +/* This function changes the file currently being paged. direction can be one of the following: + * -1: go back one file + * 0: go to the first file + * 1: go forward one file +*/ +static void change_file(int direction) +{ + if (current_file != ((direction > 0) ? num_files : 1)) { + current_file = direction ? current_file + direction : 1; + strcpy(filename, files[current_file - 1]); + reinitialise(); + } + else { + clear_line(); + printf("%s%s%s", HIGHLIGHT, (direction > 0) ? "No next file" : "No previous file", NORMAL); + } +} + +static void remove_current_file(void) +{ + int i; + + if (current_file != 1) { + change_file(-1); + for (i = 3; i <= num_files; i++) + files[i - 2] = files[i - 1]; + num_files--; + buffer_print(); + } + else { + change_file(1); + for (i = 2; i <= num_files; i++) + files[i - 2] = files[i - 1]; + num_files--; + current_file--; + buffer_print(); } } -static void keypress_process(int keypress) { +static void colon_process(void) +{ + int keypress; + + /* Clear the current line and print a prompt */ + clear_line(); + printf(" :"); + + keypress = tless_getch(); switch (keypress) { - case KEY_DOWN: case 'e': case 'j': case '\015': - buffer_down(1); - buffer_print(); - break; - case KEY_UP: case 'y': case 'k': - buffer_up(1); - buffer_print(); - break; - case PAGE_DOWN: case ' ': case 'z': - buffer_down(height - 1); - buffer_print(); - break; - case PAGE_UP: case 'w': case 'b': - buffer_up(height - 1); - buffer_print(); - break; case 'd': - buffer_down((height - 1) / 2); - buffer_print(); - break; - case 'u': - buffer_up((height - 1) / 2); - buffer_print(); - break; - case 'g': case 'p': case '<': case '%': - buffer_up(num_flines + 1); - buffer_print(); - break; - case 'G': case '>': - buffer_down(num_flines + 1); - buffer_print(); - break; - case 'q': case 'Q': - tless_exit(0); - break; -#ifdef CONFIG_FEATURE_LESS_MARKS - case 'm': - add_mark(); - buffer_print(); - break; - case '\'': - goto_mark(); - buffer_print(); - break; -#endif - case 'r': - buffer_print(); - break; - case 'R': - full_repaint(); - break; - case 's': - if (inp_stdin) - save_input_to_file(); + remove_current_file(); break; - case 'E': + case 'e': examine_file(); break; #ifdef CONFIG_FEATURE_LESS_FLAGS - case '=': + case 'f': clear_line(); m_status_print(); break; #endif -#ifdef CONFIG_FEATURE_LESS_REGEXP - case '/': - regex_process(); - buffer_print(); - break; case 'n': - goto_match(match_pos + 1); - buffer_print(); - break; - case 'N': - goto_match(match_pos - 1); - buffer_print(); + change_file(1); break; - case '?': - search_backwards(); - buffer_print(); + case 'p': + change_file(-1); break; -#endif -#ifdef CONFIG_FEATURE_LESS_FLAGCS - case '-': - flag_change(); - buffer_print(); + case 'q': + tless_exit(0); break; - case '_': - show_flag_status(); + case 'x': + change_file(0); break; -#endif -#ifdef CONFIG_FEATURE_LESS_BRACKETS - case '{': case '(': case '[': - match_right_bracket(keypress); - break; - case '}': case ')': case ']': - match_left_bracket(keypress); - break; -#endif - case ':': - colon_process(); - break; - default: + default: break; } - if (isdigit(keypress)) - number_process(keypress); } -static void colon_process() { +#ifdef CONFIG_FEATURE_LESS_REGEXP +/* The below two regular expression handler functions NEED development. */ + +/* Get a regular expression from the user, and then go through the current + file line by line, running a processing regex function on each one. */ + +static char *process_regex_on_line(char *line, regex_t *pattern, int action) +{ + /* This function takes the regex and applies it to the line. + Each part of the line that matches has the HIGHLIGHT + and NORMAL escape sequences placed around it by + insert_highlights if action = 1, or has the escape sequences + removed if action = 0, and then the line is returned. */ + int match_status; + char *line2 = (char *) xmalloc((sizeof(char) * (strlen(line) + 1)) + 64); + char *growline = ""; + regmatch_t match_structs; + + line2 = bb_xstrdup(line); + + match_found = 0; + match_status = regexec(pattern, line2, 1, &match_structs, 0); - int keypress; + while (match_status == 0) { + if (match_found == 0) + match_found = 1; + + if (action) { + growline = bb_xasprintf("%s%.*s%s%.*s%s", growline, match_structs.rm_so, line2, HIGHLIGHT, match_structs.rm_eo - match_structs.rm_so, line2 + match_structs.rm_so, NORMAL); + } + else { + growline = bb_xasprintf("%s%.*s%.*s", growline, match_structs.rm_so - 4, line2, match_structs.rm_eo - match_structs.rm_so, line2 + match_structs.rm_so); + } + + line2 += match_structs.rm_eo; + match_status = regexec(pattern, line2, 1, &match_structs, REG_NOTBOL); + } - /* Clear the current line and print a prompt */ - clear_line(); - printf(" :"); + growline = bb_xasprintf("%s%s", growline, line2); - keypress = tless_getch(); - switch (keypress) { - case 'd': - remove_current_file(); - break; - case 'e': - examine_file(); - break; -#ifdef CONFIG_FEATURE_LESS_FLAGS - case 'f': - clear_line(); - m_status_print(); - break; -#endif - case 'n': - next_file(); - break; - case 'p': - previous_file(); - break; - case 'q': - tless_exit(0); - break; - case 'x': - first_file(); - break; - default: - break; + return (match_found ? growline : line); + + free(growline); + free(line2); +} + +static void goto_match(int match) +{ + /* This goes to a specific match - all line positions of matches are + stored within the match_lines[] array. */ + if ((match < num_matches) && (match >= 0)) { + buffer_line(match_lines[match]); + match_pos = match; } } -static void number_process(int first_digit) { +static void regex_process(void) +{ + char uncomp_regex[100]; + char *current_line; + int i; + int j = 0; + regex_t pattern; + /* Get the uncompiled regular expression from the user */ + clear_line(); + putchar((match_backwards) ? '?' : '/'); + uncomp_regex[0] = 0; + fgets(uncomp_regex, sizeof(uncomp_regex), inp); + + if (strlen(uncomp_regex) == 1) { + if (num_matches) + goto_match(match_backwards ? match_pos - 1 : match_pos + 1); + else + buffer_print(); + return; + } + uncomp_regex[strlen(uncomp_regex) - 1] = '\0'; + + /* Compile the regex and check for errors */ + xregcomp(&pattern, uncomp_regex, 0); + + if (num_matches) { + /* Get rid of all the highlights we added previously */ + for (i = 0; i <= num_flines; i++) { + current_line = process_regex_on_line(flines[i], &old_pattern, 0); + flines[i] = bb_xstrdup(current_line); + } + } + old_pattern = pattern; + /* Reset variables */ + match_lines = xrealloc(match_lines, sizeof(int)); + match_lines[0] = -1; + match_pos = 0; + num_matches = 0; + match_found = 0; + /* Run the regex on each line of the current file here */ + for (i = 0; i <= num_flines; i++) { + current_line = process_regex_on_line(flines[i], &pattern, 1); + flines[i] = bb_xstrdup(current_line); + if (match_found) { + match_lines = xrealloc(match_lines, (j + 1) * sizeof(int)); + match_lines[j] = i; + j++; + } + } + + num_matches = j; + if ((match_lines[0] != -1) && (num_flines > height - 2)) { + if (match_backwards) { + for (i = 0; i < num_matches; i++) { + if (match_lines[i] > line_pos) { + match_pos = i - 1; + buffer_line(match_lines[match_pos]); + break; + } + } + } + else + buffer_line(match_lines[0]); + } + else + buffer_init(); +} +#endif + +static void number_process(int first_digit) +{ int i = 1; int num; char num_input[80]; char keypress; + char *endptr; + num_input[0] = first_digit; - + /* Clear the current line, print a prompt, and then print the digit */ clear_line(); printf(":%c", first_digit); - - /* Receive input until a letter is given */ - while((num_input[i] = tless_getch()) && isdigit(num_input[i])) { - printf("%c",num_input[i]); + + /* Receive input until a letter is given (max 80 chars)*/ + while((i < 80) && (num_input[i] = tless_getch()) && isdigit(num_input[i])) { + putchar(num_input[i]); i++; } - + /* Take the final letter out of the digits string */ keypress = num_input[i]; num_input[i] = '\0'; - i--; - num = atoi(num_input); + num = strtol(num_input, &endptr, 10); + if (endptr==num_input || *endptr!='\0' || num < 1 || num > MAXLINES) { + buffer_print(); + return; + } /* We now know the number and the letter entered, so we process them */ switch (keypress) { case KEY_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015': buffer_down(num); - buffer_print(); break; case KEY_UP: case 'b': case 'w': case 'y': case 'u': buffer_up(num); - buffer_print(); break; case 'g': case '<': case 'G': case '>': if (num_flines >= height - 2) buffer_line(num - 1); - buffer_print(); break; case 'p': case '%': - buffer_line(reverse_percent(num)); - buffer_print(); + buffer_line(((num / 100) * num_flines) - 1); break; #ifdef CONFIG_FEATURE_LESS_REGEXP case 'n': - goto_match(match_pos + num - 1); - buffer_print(); + goto_match(match_pos + num); break; case '/': + match_backwards = 0; regex_process(); - goto_match(num - 1); - buffer_print(); break; case '?': - num_back_match = num; - search_backwards(); - buffer_print(); + match_backwards = 1; + regex_process(); break; #endif default: @@ -765,188 +783,87 @@ static void number_process(int first_digit) { } #ifdef CONFIG_FEATURE_LESS_FLAGCS -static void flag_change() { - +static void flag_change(void) +{ int keypress; - + clear_line(); - printf("-"); + putchar('-'); keypress = tless_getch(); - + switch (keypress) { case 'M': - M_FLAG = !M_FLAG; + flags ^= FLAG_M; break; case 'm': - m_FLAG = !m_FLAG; + flags ^= FLAG_m; break; case 'E': - E_FLAG = !E_FLAG; + flags ^= FLAG_E; break; case '~': - TILDE_FLAG = !TILDE_FLAG; + flags ^= FLAG_TILDE; break; default: break; } } -static void show_flag_status() { - +static void show_flag_status(void) +{ int keypress; int flag_val; - + clear_line(); - printf("_"); + putchar('_'); keypress = tless_getch(); switch (keypress) { case 'M': - flag_val = M_FLAG; + flag_val = flags & FLAG_M; break; case 'm': - flag_val = m_FLAG; + flag_val = flags & FLAG_m; break; case '~': - flag_val = TILDE_FLAG; + flag_val = flags & FLAG_TILDE; break; case 'N': - flag_val = N_FLAG; + flag_val = flags & FLAG_N; break; case 'E': - flag_val = E_FLAG; + flag_val = flags & FLAG_E; break; default: flag_val = 0; break; } - - clear_line(); - printf("%s%s%i%s", HIGHLIGHT, "The status of the flag is: ", flag_val, NORMAL); -} -#endif -static void examine_file() { - - int newline_offset; - clear_line(); - printf("Examine: "); - fgets(filename, 256, inp); - - /* As fgets adds a newline to the end of an input string, we - need to remove it */ - newline_offset = strlen(filename) - 1; - filename[newline_offset] = '\0'; - - files[num_files] = bb_xstrndup(filename, (strlen(filename) + 1) * sizeof(char)); - current_file = num_files + 1; - num_files++; - - inp_stdin = 0; - ea_inp_stdin = 1; - free_flines(); - data_readlines(); - buffer_init(); - buffer_print(); -} - -static void next_file() { - if (current_file != num_files) { - current_file++; - strcpy(filename, files[current_file - 1]); - free_flines(); - data_readlines(); - buffer_init(); - buffer_print(); - } - else { - clear_line(); - printf("%s%s%s", HIGHLIGHT, "No next file", NORMAL); - } -} - -static void previous_file() { - if (current_file != 1) { - current_file--; - strcpy(filename, files[current_file - 1]); - - free_flines(); - data_readlines(); - buffer_init(); - buffer_print(); - } - else { - clear_line(); - printf("%s%s%s", HIGHLIGHT, "No previous file", NORMAL); - } -} - -static void first_file() { - if (current_file != 1) { - current_file = 1; - strcpy(filename, files[current_file - 1]); - free_flines(); - data_readlines(); - buffer_init(); - buffer_print(); - } + printf("%s%s%i%s", HIGHLIGHT, "The status of the flag is: ", flag_val != 0, NORMAL); } +#endif -static void remove_current_file() { - - int i; - - if (current_file != 1) { - previous_file(); - for (i = 3; i <= num_files; i++) - files[i - 2] = files[i - 1]; - num_files--; - buffer_print(); - } - else { - next_file(); - for (i = 2; i <= num_files; i++) - files[i - 2] = files[i - 1]; - num_files--; - current_file--; - buffer_print(); - } -} - -static void full_repaint() { - +static void full_repaint(void) +{ int temp_line_pos = line_pos; data_readlines(); buffer_init(); buffer_line(temp_line_pos); - buffer_print(); } -/* This adds line numbers to every line, as the -N flag necessitates */ -static void add_linenumbers() { - - char current_line[256]; - int i; - - for (i = 0; i <= num_flines; i++) { - safe_strncpy(current_line, flines[i], 256); - flines[i] = xrealloc(flines[i], strlen(current_line) + 7 ); - sprintf(flines[i],"%5d %s", i+1, current_line); - } -} -static void save_input_to_file() { - +static void save_input_to_file(void) +{ char current_line[256]; int i; FILE *fp; - + clear_line(); printf("Log file: "); fgets(current_line, 256, inp); current_line[strlen(current_line) - 1] = '\0'; - if (strlen(current_line)) { + if (strlen(current_line) > 1) { fp = bb_xfopen(current_line, "w"); for (i = 0; i < num_flines; i++) fprintf(fp, "%s", flines[i]); @@ -958,18 +875,18 @@ static void save_input_to_file() { } #ifdef CONFIG_FEATURE_LESS_MARKS -static void add_mark() { - +static void add_mark(void) +{ int letter; int mark_line; - + clear_line(); printf("Mark: "); letter = tless_getch(); - + if (isalpha(letter)) { mark_line = line_pos; - + /* If we exceed 15 marks, start overwriting previous ones */ if (num_marks == 14) num_marks = 0; @@ -984,168 +901,35 @@ static void add_mark() { } } -static void goto_mark() { - +static void goto_mark(void) +{ int letter; int i; - + clear_line(); printf("Go to mark: "); letter = tless_getch(); + clear_line(); + if (isalpha(letter)) { for (i = 0; i <= num_marks; i++) if (letter == mark_lines[i][0]) { buffer_line(mark_lines[i][1]); break; } - if ((num_marks == 14) && (letter != mark_lines[14][0])) { - clear_line(); + if ((num_marks == 14) && (letter != mark_lines[14][0])) printf("%s%s%s", HIGHLIGHT, "Mark not set", NORMAL); - } } - else { - clear_line(); + else printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL); - } } #endif -#ifdef CONFIG_FEATURE_LESS_REGEXP -/* The below two regular expression handler functions NEED development. */ - -/* Get a regular expression from the user, and then go through the current - file line by line, running a processing regex function on each one. */ -static void regex_process() { - - char uncomp_regex[100]; - char current_line[256]; - int i; - int j = 0; - regex_t *pattern; - - /* Reset variables */ - match_lines[0] = -1; - match_pos = 0; - num_matches = 0; - match_found = 0; - - pattern = (regex_t *) malloc(sizeof(regex_t)); - memset(pattern, 0, sizeof(regex_t)); - - /* Get the uncompiled regular expression from the user */ - clear_line(); - if (match_backwards) - printf("?"); - else - printf("/"); - scanf("%s", uncomp_regex); - - /* Compile the regex and check for errors */ - xregcomp(pattern, uncomp_regex, 0); - - /* Run the regex on each line of the current file here */ - for (i = 0; i <= num_flines; i++) { - strcpy(current_line, process_regex_on_line(flines[i], pattern)); - flines[i] = (char *) bb_xstrndup(current_line, sizeof(char) * (strlen(current_line)+1)); - - if (match_found) { - match_lines[j] = i; - j++; - } - } - - num_matches = j; - - if ((match_lines[0] != -1) && (num_flines > height - 2)) - buffer_line(match_lines[0]); - else - buffer_init(); -} - -char *process_regex_on_line(char *line, regex_t *pattern) { - /* This function takes the regex and applies it to the line. - Each part of the line that matches has the HIGHLIGHT - and NORMAL escape sequences placed around it by - insert_highlights, and then the line is returned. */ - - int match_status; - char *line2 = (char *) malloc((sizeof(char) * (strlen(line) + 1)) + 64); - char sub_line[256]; - int prev_eo = 0; - memset(sub_line, 0, 256); - strcpy(line2, line); - regmatch_t match_structs; - - match_found = 0; - match_status = regexec(pattern, line2, 1, &match_structs, 0); - - while (match_status == 0) { - - memset(sub_line, 0, 256); - - if (match_found == 0) - match_found = 1; - - line2 = insert_highlights(line2, match_structs.rm_so + prev_eo, match_structs.rm_eo + prev_eo); - if (match_structs.rm_eo + 11 + prev_eo < strlen(line2)) - strcat(sub_line, line2 + match_structs.rm_eo + 11 + prev_eo); - - prev_eo += match_structs.rm_eo + 11; - match_status = regexec(pattern, sub_line, 1, &match_structs, REG_NOTBOL); - } - - return line2; -} - -char *insert_highlights (char *line, int start, int end) { - - char *new_line = (char *) malloc((sizeof(char) * (strlen(line) + 1)) + 10); - memset(new_line, 0, ((sizeof(char) * (strlen(line) + 1)) + 10)); - strncat(new_line, line, start); - strcat(new_line, HIGHLIGHT); - strncat(new_line, line + start, end - start); - strcat(new_line, NORMAL); - strncat(new_line, line + end, strlen(line) - end); - - return new_line; -} - -static void goto_match(int match) { - - /* This goes to a specific match - all line positions of matches are - stored within the match_lines[] array. */ - if ((match < num_matches) && (match >= 0)) { - buffer_line(match_lines[match]); - match_pos = match; - } -} - -static void search_backwards() { - - int current_linepos = line_pos; - int i; - - match_backwards = 1; - regex_process(); - - for (i = 0; i < num_matches; i++) { - if (match_lines[i] > current_linepos) { - buffer_line(match_lines[i - num_back_match]); - break; - } - } - - /* Reset variables */ - match_backwards = 0; - num_back_match = 1; - -} -#endif #ifdef CONFIG_FEATURE_LESS_BRACKETS -static char opp_bracket (char bracket) { - +static char opp_bracket(char bracket) +{ switch (bracket) { case '{': case '[': return bracket + 2; @@ -1165,15 +949,15 @@ static char opp_bracket (char bracket) { } } -static void match_right_bracket(char bracket) { - +static void match_right_bracket(char bracket) +{ int bracket_line = -1; int i; - - if (strchr(flines[line_pos], bracket) == NULL) { - clear_line(); + + clear_line(); + + if (strchr(flines[line_pos], bracket) == NULL) printf("%s%s%s", HIGHLIGHT, "No bracket in top line", NORMAL); - } else { for (i = line_pos + 1; i < num_flines; i++) { if (strchr(flines[i], opp_bracket(bracket)) != NULL) { @@ -1182,23 +966,21 @@ static void match_right_bracket(char bracket) { } } - if (bracket_line == -1) { - clear_line(); + if (bracket_line == -1) printf("%s%s%s", HIGHLIGHT, "No matching bracket found", NORMAL); - } buffer_line(bracket_line - height + 2); - buffer_print(); } } -static void match_left_bracket (char bracket) { - +static void match_left_bracket(char bracket) +{ int bracket_line = -1; int i; + clear_line(); + if (strchr(flines[line_pos + height - 2], bracket) == NULL) { - clear_line(); printf("%s%s%s", HIGHLIGHT, "No bracket in bottom line", NORMAL); printf("%s", flines[line_pos + height]); sleep(4); @@ -1211,35 +993,135 @@ static void match_left_bracket (char bracket) { } } - if (bracket_line == -1) { - clear_line(); + if (bracket_line == -1) printf("%s%s%s", HIGHLIGHT, "No matching bracket found", NORMAL); - } - + buffer_line(bracket_line); - buffer_print(); } } +#endif /* CONFIG_FEATURE_LESS_BRACKETS */ + +static void keypress_process(int keypress) +{ + switch (keypress) { + case KEY_DOWN: case 'e': case 'j': case '\015': + buffer_down(1); + buffer_print(); + break; + case KEY_UP: case 'y': case 'k': + buffer_up(1); + buffer_print(); + break; + case PAGE_DOWN: case ' ': case 'z': + buffer_down(height - 1); + buffer_print(); + break; + case PAGE_UP: case 'w': case 'b': + buffer_up(height - 1); + buffer_print(); + break; + case 'd': + buffer_down((height - 1) / 2); + buffer_print(); + break; + case 'u': + buffer_up((height - 1) / 2); + buffer_print(); + break; + case 'g': case 'p': case '<': case '%': + buffer_line(0); + break; + case 'G': case '>': + buffer_line(num_flines - height + 2); + break; + case 'q': case 'Q': + tless_exit(0); + break; +#ifdef CONFIG_FEATURE_LESS_MARKS + case 'm': + add_mark(); + buffer_print(); + break; + case '\'': + goto_mark(); + buffer_print(); + break; +#endif + case 'r': + buffer_print(); + break; + case 'R': + full_repaint(); + break; + case 's': + if (inp_stdin) + save_input_to_file(); + break; + case 'E': + examine_file(); + break; +#ifdef CONFIG_FEATURE_LESS_FLAGS + case '=': + clear_line(); + m_status_print(); + break; +#endif +#ifdef CONFIG_FEATURE_LESS_REGEXP + case '/': + match_backwards = 0; + regex_process(); + break; + case 'n': + goto_match(match_pos + 1); + break; + case 'N': + goto_match(match_pos - 1); + break; + case '?': + match_backwards = 1; + regex_process(); + break; +#endif +#ifdef CONFIG_FEATURE_LESS_FLAGCS + case '-': + flag_change(); + buffer_print(); + break; + case '_': + show_flag_status(); + break; #endif +#ifdef CONFIG_FEATURE_LESS_BRACKETS + case '{': case '(': case '[': + match_right_bracket(keypress); + break; + case '}': case ')': case ']': + match_left_bracket(keypress); + break; +#endif + case ':': + colon_process(); + break; + default: + break; + } + + if (isdigit(keypress)) + number_process(keypress); +} int less_main(int argc, char **argv) { - - unsigned long flags; + int keypress; - - flags = bb_getopt_ulflags(argc, argv, "EMNm~"); - E_FLAG = (flags & 1); - M_FLAG = (flags & 2); - N_FLAG = (flags & 4); - m_FLAG = (flags & 8); - TILDE_FLAG = (flags & 16); + + flags = bb_getopt_ulflags(argc, argv, "EMmN~"); argc -= optind; argv += optind; files = argv; num_files = argc; - + if (!num_files) { if (ttyname(STDIN_FILENO) == NULL) inp_stdin = 1; @@ -1248,13 +1130,20 @@ int less_main(int argc, char **argv) { bb_show_usage(); } } - - strcpy(filename, (inp_stdin) ? "stdin" : files[0]); - tty_width_height(); + + strcpy(filename, (inp_stdin) ? bb_msg_standard_input : files[0]); + get_terminal_width_height(0, &width, &height); data_readlines(); + tcgetattr(fileno(inp), &term_orig); + term_vi = term_orig; + term_vi.c_lflag &= (~ICANON & ~ECHO); + term_vi.c_iflag &= (~IXON & ~ICRNL); + term_vi.c_oflag &= (~ONLCR); + term_vi.c_cc[VMIN] = 1; + term_vi.c_cc[VTIME] = 0; buffer_init(); buffer_print(); - + while (1) { keypress = tless_getch(); keypress_process(keypress);