X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=miscutils%2Fless.c;h=530a40a8c8314203a5b527f115a24a6eb236641b;hb=b2dc913527f2cb9a4590fe5e553bcc2c456007e0;hp=99149a51d06faf8fc65336dc1d819716d51ef074;hpb=2b306e906f6ba9fe990365a0faa00140d7f4dbe1;p=oweals%2Fbusybox.git diff --git a/miscutils/less.c b/miscutils/less.c index 99149a51d..530a40a8c 100644 --- a/miscutils/less.c +++ b/miscutils/less.c @@ -2,784 +2,1002 @@ /* * 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. - * - * 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 + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +/* + * 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. + * - Allow horizontal scrolling. * - * 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 -#include -#include -#include -#include -#include - -#include "busybox.h" - -#ifdef CONFIG_FEATURE_LESS_REGEXP -#include "xregex.h" -#endif + * Notes: + * - the inp file pointer is used so that keyboard input works after + * redirected input has been read from stdin + */ +#include /* sched_yield() */ -/* These are the escape sequences corresponding to special keys */ -#define REAL_KEY_UP 'A' -#define REAL_KEY_DOWN 'B' -#define REAL_KEY_RIGHT 'C' -#define REAL_KEY_LEFT 'D' -#define REAL_PAGE_UP '5' -#define REAL_PAGE_DOWN '6' +#include "libbb.h" +#if ENABLE_FEATURE_LESS_REGEXP +#include "xregex.h" +#endif -/* These are the special codes assigned by this program to the special keys */ -#define PAGE_UP 20 -#define PAGE_DOWN 21 -#define KEY_UP 22 -#define KEY_DOWN 23 -#define KEY_RIGHT 24 -#define KEY_LEFT 25 +/* FIXME: currently doesn't work right */ +#undef ENABLE_FEATURE_LESS_FLAGCS +#define ENABLE_FEATURE_LESS_FLAGCS 0 /* The escape codes for highlighted and normal text */ #define HIGHLIGHT "\033[7m" #define NORMAL "\033[0m" - /* The escape code to clear the screen */ -#define CLEAR "\033[2J" - -/* 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) - -static int height; -static int width; -static char **files; -static char filename[256]; -static char buffer[100][256]; -static char *flines[MAXLINES]; -static int current_file = 1; -static int line_pos; -static int num_flines; -static int num_files = 1; -static int past_eof; +#define CLEAR "\033[H\033[J" +/* The escape code to clear to end of line */ +#define CLEAR_2_EOL "\033[K" + +/* These are the escape sequences corresponding to special keys */ +enum { + REAL_KEY_UP = 'A', + REAL_KEY_DOWN = 'B', + REAL_KEY_RIGHT = 'C', + REAL_KEY_LEFT = 'D', + REAL_PAGE_UP = '5', + REAL_PAGE_DOWN = '6', + REAL_KEY_HOME = '7', // vt100? linux vt? or what? + REAL_KEY_END = '8', + REAL_KEY_HOME_ALT = '1', // ESC [1~ (vt100? linux vt? or what?) + REAL_KEY_END_ALT = '4', // ESC [4~ + REAL_KEY_HOME_XTERM = 'H', + REAL_KEY_END_XTERM = 'F', + +/* These are the special codes assigned by this program to the special keys */ + KEY_UP = 20, + KEY_DOWN = 21, + KEY_RIGHT = 22, + KEY_LEFT = 23, + PAGE_UP = 24, + PAGE_DOWN = 25, + KEY_HOME = 26, + KEY_END = 27, + +/* Absolute max of lines eaten */ + MAXLINES = CONFIG_FEATURE_LESS_MAXLINES, + +/* This many "after the end" lines we will show (at max) */ + TILDES = 1, +}; /* Command line options */ -static int E_FLAG; -static int M_FLAG; -static int N_FLAG; -static int m_FLAG; -static int TILDE_FLAG; - -/* This is needed so that program behaviour changes when input comes from - stdin */ -static int inp_stdin; -/* 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; - -#ifdef CONFIG_FEATURE_LESS_MARKS -static int mark_lines[15][2]; -static int num_marks; +enum { + FLAG_E = 1, + FLAG_M = 1 << 1, + FLAG_m = 1 << 2, + FLAG_N = 1 << 3, + FLAG_TILDE = 1 << 4, +/* hijack command line options variable for internal state vars */ + LESS_STATE_MATCH_BACKWARDS = 1 << 15, +}; + +#if !ENABLE_FEATURE_LESS_REGEXP +enum { pattern_valid = 0 }; #endif -#ifdef CONFIG_FEATURE_LESS_REGEXP -static int match_found; -static int match_lines[100]; -static int match_pos; -static int num_matches; -static int match_backwards; -static int num_back_match = 1; +struct globals { + int cur_fline; /* signed */ + int kbd_fd; /* fd to get input from */ + int less_gets_pos; +/* last position in last line, taking into account tabs */ + size_t linepos; + unsigned max_displayed_line; + unsigned max_fline; + unsigned max_lineno; /* this one tracks linewrap */ + unsigned width; + ssize_t eof_error; /* eof if 0, error if < 0 */ + ssize_t readpos; + ssize_t readeof; /* must be signed */ + const char **buffer; + const char **flines; + const char *empty_line_marker; + unsigned num_files; + unsigned current_file; + char *filename; + char **files; +#if ENABLE_FEATURE_LESS_MARKS + unsigned num_marks; + unsigned mark_lines[15][2]; #endif - -/* Needed termios structures */ -static struct termios term_orig, term_vi; - -/* File pointer to get input from */ -static FILE *inp; +#if ENABLE_FEATURE_LESS_REGEXP + unsigned *match_lines; + int match_pos; /* signed! */ + int wanted_match; /* signed! */ + int num_matches; + regex_t pattern; + smallint pattern_valid; +#endif + smallint terminated; + struct termios term_orig, term_less; +}; +#define G (*ptr_to_globals) +#define cur_fline (G.cur_fline ) +#define kbd_fd (G.kbd_fd ) +#define less_gets_pos (G.less_gets_pos ) +#define linepos (G.linepos ) +#define max_displayed_line (G.max_displayed_line) +#define max_fline (G.max_fline ) +#define max_lineno (G.max_lineno ) +#define width (G.width ) +#define eof_error (G.eof_error ) +#define readpos (G.readpos ) +#define readeof (G.readeof ) +#define buffer (G.buffer ) +#define flines (G.flines ) +#define empty_line_marker (G.empty_line_marker ) +#define num_files (G.num_files ) +#define current_file (G.current_file ) +#define filename (G.filename ) +#define files (G.files ) +#define num_marks (G.num_marks ) +#define mark_lines (G.mark_lines ) +#if ENABLE_FEATURE_LESS_REGEXP +#define match_lines (G.match_lines ) +#define match_pos (G.match_pos ) +#define num_matches (G.num_matches ) +#define wanted_match (G.wanted_match ) +#define pattern (G.pattern ) +#define pattern_valid (G.pattern_valid ) +#endif +#define terminated (G.terminated ) +#define term_orig (G.term_orig ) +#define term_less (G.term_less ) +#define INIT_G() do { \ + SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ + less_gets_pos = -1; \ + empty_line_marker = "~"; \ + num_files = 1; \ + current_file = 1; \ + eof_error = 1; \ + terminated = 1; \ + USE_FEATURE_LESS_REGEXP(wanted_match = -1;) \ +} while (0) /* Reset terminal input to normal */ -static void set_tty_cooked(void) { +static void set_tty_cooked(void) +{ fflush(stdout); - tcsetattr(0, TCSANOW, &term_orig); -} - -/* Set terminal input to raw mode */ -static void set_tty_raw(void) { - 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); -} - -/* Exit the program gracefully */ -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 - "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. */ -static int tless_getch(void) { - - set_tty_raw(); - char input_key[3]; - - input_key[0] = 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); - 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; - } - } - /* The input is a normal ASCII value */ - else { - set_tty_cooked(); - return input_key[0]; - } - return 0; + tcsetattr(kbd_fd, TCSANOW, &term_orig); } /* 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) { - printf("\033[%i;%iH", x, y); +static void move_cursor(int line, int row) +{ + printf("\033[%u;%uH", line, row); } -static void clear_line(void) { - move_cursor(height, 0); - printf("\033[K"); +static void clear_line(void) +{ + printf("\033[%u;0H" CLEAR_2_EOL, max_displayed_line + 2); } -/* 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] = xrealloc(flines[i], strlen(current_line) + 7 ); - sprintf(flines[i],"%5d %s", i+1, current_line); - } +static void print_hilite(const char *str) +{ + printf(HIGHLIGHT"%s"NORMAL, str); } -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++) { - 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)); - } - num_flines = i - 2; - -/* 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 (ea_inp_stdin) { - fclose(inp); - inp = fopen(CURRENT_TTY, "r"); - } - - if (N_FLAG) - add_linenumbers(); +static void print_statusline(const char *str) +{ + clear_line(); + printf(HIGHLIGHT"%.*s"NORMAL, width - 1, str); } -/* Free the file data */ -static void free_flines(void) { - - int i; - - for (i = 0; i <= num_flines; i++) - free(flines[i]); +/* Exit the program gracefully */ +static void less_exit(int code) +{ + set_tty_cooked(); + clear_line(); + if (code < 0) + kill_myself_with_sig(- code); /* does not return */ + exit(code); } -#ifdef CONFIG_FEATURE_LESS_FLAGS -/* Calculate the percentage the current line position is through the file */ -static int calc_percent(void) { - return ((100 * (line_pos + height - 2) / num_flines) + 1); -} +#if ENABLE_FEATURE_LESS_REGEXP +static void fill_match_lines(unsigned pos); +#else +#define fill_match_lines(pos) ((void)0) #endif -/* Turn a percentage into a line number */ -static int reverse_percent(int percentage) { - double linenum = percentage; - linenum = ((linenum / 100) * num_flines) - 1; - return(linenum); -} - -#ifdef CONFIG_FEATURE_LESS_FLAGS -/* Print a status line if -M was specified */ -static void m_status_print(void) { +/* Devilishly complex routine. + * + * Has to deal with EOF and EPIPE on input, + * with line wrapping, with last line not ending in '\n' + * (possibly not ending YET!), with backspace and tabs. + * It reads input again if last time we got an EOF (thus supporting + * growing files) or EPIPE (watching output of slow process like make). + * + * Variables used: + * flines[] - array of lines already read. Linewrap may cause + * one source file line to occupy several flines[n]. + * flines[max_fline] - last line, possibly incomplete. + * terminated - 1 if flines[max_fline] is 'terminated' + * (if there was '\n' [which isn't stored itself, we just remember + * that it was seen]) + * max_lineno - last line's number, this one doesn't increment + * on line wrap, only on "real" new lines. + * readbuf[0..readeof-1] - small preliminary buffer. + * readbuf[readpos] - next character to add to current line. + * linepos - screen line position of next char to be read + * (takes into account tabs and backspaces) + * eof_error - < 0 error, == 0 EOF, > 0 not EOF/error + */ +static void read_lines(void) +{ +#define readbuf bb_common_bufsiz1 + char *current_line, *p; + int w = width; + char last_terminated = terminated; +#if ENABLE_FEATURE_LESS_REGEXP + unsigned old_max_fline = max_fline; + time_t last_time = 0; + unsigned seconds_p1 = 3; /* seconds_to_loop + 1 */ +#endif - int percentage; + if (option_mask32 & FLAG_N) + w -= 8; + + USE_FEATURE_LESS_REGEXP(again0:) + + p = current_line = xmalloc(w); + max_fline += last_terminated; + if (!last_terminated) { + const char *cp = flines[max_fline]; + if (option_mask32 & FLAG_N) + cp += 8; + strcpy(current_line, cp); + p += strlen(current_line); + free((char*)flines[max_fline]); + /* linepos is still valid from previous read_lines() */ + } else { + linepos = 0; + } - if (!past_eof) { - if (!line_pos) { - if (num_files > 1) - printf("%s%s %s%i%s%i%s%i-%i/%i ", HIGHLIGHT, filename, "(file ", current_file, " of ", num_files, ") lines ", line_pos + 1, line_pos + height - 1, num_flines + 1); - else { - printf("%s%s lines %i-%i/%i ", HIGHLIGHT, filename, line_pos + 1, line_pos + height - 1, num_flines + 1); + while (1) { /* read lines until we reach cur_fline or wanted_match */ + *p = '\0'; + terminated = 0; + while (1) { /* read chars until we have a line */ + char c; + /* if no unprocessed chars left, eat more */ + if (readpos >= readeof) { + ndelay_on(0); + eof_error = safe_read(STDIN_FILENO, readbuf, sizeof(readbuf)); + ndelay_off(0); + readpos = 0; + readeof = eof_error; + if (eof_error <= 0) + goto reached_eof; + } + c = readbuf[readpos]; + /* backspace? [needed for manpages] */ + /* is (a) insane and */ + /* (b) harder to do correctly, so we refuse to do it */ + if (c == '\x8' && linepos && p[-1] != '\t') { + readpos++; /* eat it */ + linepos--; + /* was buggy (p could end up <= current_line)... */ + *--p = '\0'; + continue; } + { + size_t new_linepos = linepos + 1; + if (c == '\t') { + new_linepos += 7; + new_linepos &= (~7); + } + if ((int)new_linepos >= w) + break; + linepos = new_linepos; + } + /* ok, we will eat this char */ + readpos++; + if (c == '\n') { + terminated = 1; + linepos = 0; + break; + } + /* NUL is substituted by '\n'! */ + if (c == '\0') c = '\n'; + *p++ = c; + *p = '\0'; + } /* end of "read chars until we have a line" loop */ + /* Corner case: linewrap with only "" wrapping to next line */ + /* Looks ugly on screen, so we do not store this empty line */ + if (!last_terminated && !current_line[0]) { + last_terminated = 1; + max_lineno++; + continue; } - else { - printf("%s %s lines %i-%i/%i ", HIGHLIGHT, filename, line_pos + 1, line_pos + height - 1, num_flines + 1); + reached_eof: + last_terminated = terminated; + flines = xrealloc_vector(flines, 8, max_fline); + if (option_mask32 & FLAG_N) { + /* Width of 7 preserves tab spacing in the text */ + flines[max_fline] = xasprintf( + (max_lineno <= 9999999) ? "%7u %s" : "%07u %s", + max_lineno % 10000000, current_line); + free(current_line); + if (terminated) + max_lineno++; + } else { + flines[max_fline] = xrealloc(current_line, strlen(current_line) + 1); } - - if (line_pos == num_flines - height + 2) { - printf("(END) %s", NORMAL); - if ((num_files > 1) && (current_file != num_files)) - printf("%s- Next: %s%s", HIGHLIGHT, files[current_file], NORMAL); + if (max_fline >= MAXLINES) { + eof_error = 0; /* Pretend we saw EOF */ + break; } - else { - percentage = calc_percent(); - printf("%i%s %s", percentage, "%", NORMAL); + if (max_fline > cur_fline + max_displayed_line) { +#if !ENABLE_FEATURE_LESS_REGEXP + break; +#else + if (wanted_match >= num_matches) { /* goto_match called us */ + fill_match_lines(old_max_fline); + old_max_fline = max_fline; + } + if (wanted_match < num_matches) + break; +#endif } - } - else { - printf("%s%s lines %i-%i/%i (END) ", HIGHLIGHT, filename, line_pos + 1, num_flines + 1, num_flines + 1); - if ((num_files > 1) && (current_file != num_files)) - printf("- Next: %s", files[current_file]); - printf("%s", NORMAL); - } + if (eof_error <= 0) { + if (eof_error < 0) { + if (errno == EAGAIN) { + /* not yet eof or error, reset flag (or else + * we will hog CPU - select() will return + * immediately */ + eof_error = 1; + } else { + print_statusline("read error"); + } + } +#if !ENABLE_FEATURE_LESS_REGEXP + break; +#else + if (wanted_match < num_matches) { + break; + } else { /* goto_match called us */ + time_t t = time(NULL); + if (t != last_time) { + last_time = t; + if (--seconds_p1 == 0) + break; + } + sched_yield(); + goto again0; /* go loop again (max 2 seconds) */ + } +#endif + } + max_fline++; + current_line = xmalloc(w); + p = current_line; + linepos = 0; + } /* end of "read lines until we reach cur_fline" loop */ + fill_match_lines(old_max_fline); +#if ENABLE_FEATURE_LESS_REGEXP + /* prevent us from being stuck in search for a match */ + wanted_match = -1; +#endif +#undef readbuf } -/* Print a status line if -m was specified */ -static void medium_status_print(void) { +#if ENABLE_FEATURE_LESS_FLAGS +/* Interestingly, writing calc_percent as a function saves around 32 bytes + * on my build. */ +static int calc_percent(void) +{ + unsigned p = (100 * (cur_fline+max_displayed_line+1) + max_fline/2) / (max_fline+1); + return p <= 100 ? p : 100; +} +/* Print a status line if -M was specified */ +static void m_status_print(void) +{ int percentage; - percentage = calc_percent(); - if (!line_pos) - printf("%s%s %i%s%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); + if (less_gets_pos >= 0) /* don't touch statusline while input is done! */ + return; + + clear_line(); + printf(HIGHLIGHT"%s", filename); + if (num_files > 1) + printf(" (file %i of %i)", current_file, num_files); + printf(" lines %i-%i/%i ", + cur_fline + 1, cur_fline + max_displayed_line + 1, + max_fline + 1); + if (cur_fline >= (int)(max_fline - max_displayed_line)) { + printf("(END)"NORMAL); + if (num_files > 1 && current_file != num_files) + printf(HIGHLIGHT" - next: %s"NORMAL, files[current_file]); + return; + } + percentage = calc_percent(); + printf("%i%%"NORMAL, percentage); } #endif /* Print the status line */ -static void status_print(void) { +static void status_print(void) +{ + const char *p; + + if (less_gets_pos >= 0) /* don't touch statusline while input is done! */ + return; /* Change the status if flags have been set */ -#ifdef CONFIG_FEATURE_LESS_FLAGS - if (M_FLAG) +#if ENABLE_FEATURE_LESS_FLAGS + if (option_mask32 & (FLAG_M|FLAG_m)) { m_status_print(); - else if (m_FLAG) - medium_status_print(); + return; + } /* No flags set */ - else { #endif - if (!line_pos) { - printf("%s%s %s", HIGHLIGHT, filename, NORMAL); - if (num_files > 1) - printf("%s%s%i%s%i%s%s", HIGHLIGHT, "(file ", current_file, " of ", num_files, ")", NORMAL); - } - else if (line_pos == num_flines - height + 2) { - printf("%s%s %s", HIGHLIGHT, "(END)", NORMAL); - if ((num_files > 1) && (current_file != num_files)) - printf("%s%s%s%s", HIGHLIGHT, "- Next: ", files[current_file], NORMAL); - } - else { - printf("%c", ':'); - } -#ifdef CONFIG_FEATURE_LESS_FLAGS + + clear_line(); + if (cur_fline && cur_fline < (int)(max_fline - max_displayed_line)) { + bb_putchar(':'); + return; } -#endif + p = "(END)"; + if (!cur_fline) + p = filename; + if (num_files > 1) { + printf(HIGHLIGHT"%s (file %i of %i)"NORMAL, + p, current_file, num_files); + return; + } + print_hilite(p); } -/* Print the buffer */ -static void buffer_print(void) { - - int i; - - 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(); +static void cap_cur_fline(int nlines) +{ + int diff; + if (cur_fline < 0) + cur_fline = 0; + if (cur_fline + max_displayed_line > max_fline + TILDES) { + cur_fline -= nlines; + if (cur_fline < 0) + cur_fline = 0; + diff = max_fline - (cur_fline + max_displayed_line) + TILDES; + /* As the number of lines requested was too large, we just move + to the end of the file */ + if (diff > 0) + cur_fline += diff; } } -/* Initialise the buffer */ -static void buffer_init(void) { +static const char controls[] ALIGN1 = + /* NUL: never encountered; TAB: not converted */ + /**/"\x01\x02\x03\x04\x05\x06\x07\x08" "\x0a\x0b\x0c\x0d\x0e\x0f" + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + "\x7f\x9b"; /* DEL and infamous Meta-ESC :( */ +static const char ctrlconv[] ALIGN1 = + /* '\n': it's a former NUL - subst with '@', not 'J' */ + "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x40\x4b\x4c\x4d\x4e\x4f" + "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"; + +#if ENABLE_FEATURE_LESS_REGEXP +static void print_found(const char *line) +{ + int match_status; + int eflags; + char *growline; + regmatch_t match_structs; - int i; + char buf[width]; + const char *str = line; + char *p = buf; + size_t n; + + while (*str) { + n = strcspn(str, controls); + if (n) { + if (!str[n]) break; + memcpy(p, str, n); + p += n; + str += n; + } + n = strspn(str, controls); + memset(p, '.', n); + p += n; + str += n; + } + strcpy(p, str); - for (i = 0; i < (height - 1); i++) - memset(buffer[i], '\0', 256); + /* buf[] holds quarantined version of str */ - /* 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]); + /* Each part of the line that matches has the HIGHLIGHT + and NORMAL escape sequences placed around it. + NB: we regex against line, but insert text + from quarantined copy (buf[]) */ + str = buf; + growline = NULL; + eflags = 0; + goto start; + + while (match_status == 0) { + char *new = xasprintf("%s%.*s"HIGHLIGHT"%.*s"NORMAL, + growline ? : "", + match_structs.rm_so, str, + match_structs.rm_eo - match_structs.rm_so, + str + match_structs.rm_so); + free(growline); growline = new; + str += match_structs.rm_eo; + line += match_structs.rm_eo; + eflags = REG_NOTBOL; + start: + /* Most of the time doesn't find the regex, optimize for that */ + match_status = regexec(&pattern, line, 1, &match_structs, eflags); + /* if even "" matches, treat it as "not a match" */ + if (match_structs.rm_so >= match_structs.rm_eo) + match_status = 1; } - /* If the buffer still isn't full, fill it with blank lines */ - for (; i < (height - 1); i++) { - strcpy(buffer[i], ""); + if (!growline) { + printf(CLEAR_2_EOL"%s\n", str); + return; } + printf(CLEAR_2_EOL"%s%s\n", growline, str); + free(growline); } +#else +void print_found(const char *line); +#endif -/* Move the buffer up and down in the file in order to scroll */ -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]); +static void print_ascii(const char *str) +{ + char buf[width]; + char *p; + size_t n; + + printf(CLEAR_2_EOL); + while (*str) { + n = strcspn(str, controls); + if (n) { + if (!str[n]) break; + printf("%.*s", (int) n, str); + str += n; } - 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) { - line_pos += 1; - for (i = 0; i < (height - 1); i++) - strcpy(buffer[i], flines[line_pos + i]); - } - } - - /* We exit if the -E flag has been set */ - if (E_FLAG && (line_pos + (height - 2) == num_flines)) - tless_exit(0); + n = strspn(str, controls); + p = buf; + do { + if (*str == 0x7f) + *p++ = '?'; + else if (*str == (char)0x9b) + /* VT100's CSI, aka Meta-ESC. Who's inventor? */ + /* I want to know who committed this sin */ + *p++ = '{'; + else + *p++ = ctrlconv[(unsigned char)*str]; + str++; + } while (--n); + *p = '\0'; + print_hilite(buf); } + puts(str); } -static void buffer_up(int nlines) { - - int i; - int tilde_line; +/* Print the buffer */ +static void buffer_print(void) +{ + unsigned i; + + move_cursor(0, 0); + for (i = 0; i <= max_displayed_line; i++) + if (pattern_valid) + print_found(buffer[i]); + else + print_ascii(buffer[i]); + status_print(); +} - 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]); - } - 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]); - } - } +static void buffer_fill_and_print(void) +{ + unsigned i; + for (i = 0; i <= max_displayed_line && cur_fline + i <= max_fline; i++) { + buffer[i] = flines[cur_fline + 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. */ - if (line_pos < num_flines - height + 3) { - past_eof = 0; - buffer_up(nlines); - } - else { - /* We only move part of the buffer, as the rest - is past the EOF */ - for (i = 0; i < (height - 1); i++) { - if (i < tilde_line - nlines + 1) - strcpy(buffer[i], flines[line_pos + i]); - else { - if (line_pos >= num_flines - height + 2) - strcpy(buffer[i], "~\n"); - } - } - } + for (; i <= max_displayed_line; i++) { + buffer[i] = empty_line_marker; } + buffer_print(); } -static void buffer_line(int linenum) { +/* Move the buffer up and down in the file in order to scroll */ +static void buffer_down(int nlines) +{ + cur_fline += nlines; + read_lines(); + cap_cur_fline(nlines); + buffer_fill_and_print(); +} - int i; +static void buffer_up(int nlines) +{ + cur_fline -= nlines; + if (cur_fline < 0) cur_fline = 0; + read_lines(); + buffer_fill_and_print(); +} - past_eof = 0; +static void buffer_line(int linenum) +{ + if (linenum < 0) + linenum = 0; + cur_fline = linenum; + read_lines(); + if (linenum + max_displayed_line > max_fline) + linenum = max_fline - max_displayed_line + TILDES; + if (linenum < 0) + linenum = 0; + cur_fline = linenum; + buffer_fill_and_print(); +} - if (linenum < 1 || linenum > num_flines) { - clear_line(); - printf("%s%s%i%s", HIGHLIGHT, "Cannot seek to line number ", linenum, NORMAL); - } - else if (linenum < (num_flines - height - 2)) { - for (i = 0; i < (height - 1); i++) - strcpy(buffer[i], flines[linenum + i]); - line_pos = linenum; - } - else { - for (i = 0; i < (height - 1); i++) { - if (linenum + i < num_flines + 2) - strcpy(buffer[i], flines[linenum + i]); - else - strcpy(buffer[i], (TILDE_FLAG) ? "\n" : "~\n"); - } - line_pos = linenum; - /* Set past_eof so buffer_down and buffer_up act differently */ - past_eof = 1; +static void open_file_and_read_lines(void) +{ + if (filename) { + int fd = xopen(filename, O_RDONLY); + dup2(fd, 0); + if (fd) close(fd); + } else { + /* "less" with no arguments in argv[] */ + /* For status line only */ + filename = xstrdup(bb_msg_standard_input); } + readpos = 0; + readeof = 0; + linepos = 0; + terminated = 1; + read_lines(); } -static void examine_file(void) { +/* Reinitialize everything for a new file - free the memory and start over */ +static void reinitialize(void) +{ + unsigned i; - int newline_offset; + if (flines) { + for (i = 0; i <= max_fline; i++) + free((void*)(flines[i])); + free(flines); + flines = NULL; + } - clear_line(); - printf("Examine: "); - fgets(filename, 256, inp); + max_fline = -1; + cur_fline = 0; + max_lineno = 0; + open_file_and_read_lines(); + buffer_fill_and_print(); +} - /* 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'; +static ssize_t getch_nowait(char* input, int sz) +{ + ssize_t rd; + struct pollfd pfd[2]; + + pfd[0].fd = STDIN_FILENO; + pfd[0].events = POLLIN; + pfd[1].fd = kbd_fd; + pfd[1].events = POLLIN; + again: + tcsetattr(kbd_fd, TCSANOW, &term_less); + /* NB: select/poll returns whenever read will not block. Therefore: + * if eof is reached, select/poll will return immediately + * because read will immediately return 0 bytes. + * Even if select/poll says that input is available, read CAN block + * (switch fd into O_NONBLOCK'ed mode to avoid it) + */ + rd = 1; + if (max_fline <= cur_fline + max_displayed_line + && eof_error > 0 /* did NOT reach eof yet */ + ) { + /* We are interested in stdin */ + rd = 0; + } + /* position cursor if line input is done */ + if (less_gets_pos >= 0) + move_cursor(max_displayed_line + 2, less_gets_pos + 1); + fflush(stdout); + safe_poll(pfd + rd, 2 - rd, -1); + + input[0] = '\0'; + rd = safe_read(kbd_fd, input, sz); /* NB: kbd_fd is in O_NONBLOCK mode */ + if (rd < 0 && errno == EAGAIN) { + /* No keyboard input -> we have input on stdin! */ + read_lines(); + buffer_fill_and_print(); + goto again; + } + set_tty_cooked(); + return rd; +} - files[num_files] = bb_xstrndup(filename, (strlen(filename) + 1) * sizeof(char)); - current_file = num_files + 1; - num_files++; +/* 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. */ +static int less_getch(int pos) +{ + unsigned char input[16]; + unsigned i; + + again: + less_gets_pos = pos; + memset(input, 0, sizeof(input)); + getch_nowait((char *)input, sizeof(input)); + less_gets_pos = -1; - inp_stdin = 0; - ea_inp_stdin = 1; - free_flines(); - data_readlines(); - buffer_init(); - buffer_print(); + /* Detect escape sequences (i.e. arrow keys) and handle + * them accordingly */ + if (input[0] == '\033' && input[1] == '[') { + i = input[2] - REAL_KEY_UP; + if (i < 4) + return 20 + i; + i = input[2] - REAL_PAGE_UP; + if (i < 4) + return 24 + i; + if (input[2] == REAL_KEY_HOME_XTERM) + return KEY_HOME; + if (input[2] == REAL_KEY_HOME_ALT) + return KEY_HOME; + if (input[2] == REAL_KEY_END_XTERM) + return KEY_END; + if (input[2] == REAL_KEY_END_ALT) + return KEY_END; + return 0; + } + /* Reject almost all control chars */ + i = input[0]; + if (i < ' ' && i != 0x0d && i != 8) + goto again; + return i; } +static char* less_gets(int sz) +{ + char c; + unsigned i = 0; + char *result = xzalloc(1); -static void next_file(void) { - 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); + while (1) { + c = '\0'; + less_gets_pos = sz + i; + getch_nowait(&c, 1); + if (c == 0x0d) { + result[i] = '\0'; + less_gets_pos = -1; + return result; + } + if (c == 0x7f) + c = 8; + if (c == 8 && i) { + printf("\x8 \x8"); + i--; + } + if (c < ' ') + continue; + if (i >= width - sz - 1) + continue; /* len limit */ + bb_putchar(c); + result[i++] = c; + result = xrealloc(result, i+1); } } -static void previous_file(void) { - if (current_file != 1) { - current_file--; - strcpy(filename, files[current_file - 1]); +static void examine_file(void) +{ + char *new_fname; - free_flines(); - data_readlines(); - buffer_init(); - buffer_print(); + print_statusline("Examine: "); + new_fname = less_gets(sizeof("Examine: ") - 1); + if (!new_fname[0]) { + status_print(); + err: + free(new_fname); + return; } - else { - clear_line(); - printf("%s%s%s", HIGHLIGHT, "No previous file", NORMAL); + if (access(new_fname, R_OK) != 0) { + print_statusline("Cannot read this file"); + goto err; } + free(filename); + filename = new_fname; + /* files start by = argv. why we assume that argv is infinitely long?? + files[num_files] = filename; + current_file = num_files + 1; + num_files++; */ + files[0] = filename; + num_files = current_file = 1; + reinitialize(); } -static void first_file(void) { - if (current_file != 1) { - current_file = 1; - strcpy(filename, files[current_file - 1]); - free_flines(); - data_readlines(); - buffer_init(); - buffer_print(); +/* 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; + free(filename); + filename = xstrdup(files[current_file - 1]); + reinitialize(); + } else { + print_statusline(direction > 0 ? "No next file" : "No previous file"); } } -static void remove_current_file(void) { +static void remove_current_file(void) +{ + unsigned i; - int i; + if (num_files < 2) + return; if (current_file != 1) { - previous_file(); + change_file(-1); for (i = 3; i <= num_files; i++) files[i - 2] = files[i - 1]; num_files--; - buffer_print(); - } - else { - next_file(); + } 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 colon_process(void) { - +static void colon_process(void) +{ int keypress; /* Clear the current line and print a prompt */ - clear_line(); - printf(" :"); + print_statusline(" :"); - keypress = tless_getch(); + keypress = less_getch(2); 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; + case 'd': + remove_current_file(); + break; + case 'e': + examine_file(); + break; +#if ENABLE_FEATURE_LESS_FLAGS + case 'f': + 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; + case 'n': + change_file(1); + break; + case 'p': + change_file(-1); + break; + case 'q': + less_exit(EXIT_SUCCESS); + break; + case 'x': + change_file(0); + break; } } -#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 *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; +#if ENABLE_FEATURE_LESS_REGEXP +static void normalize_match_pos(int match) +{ + if (match >= num_matches) + match = num_matches - 1; + if (match < 0) + match = 0; + match_pos = match; } -static 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); +static void goto_match(int match) +{ + if (!pattern_valid) + return; + if (match < 0) + match = 0; + /* Try to find next match if eof isn't reached yet */ + if (match >= num_matches && eof_error > 0) { + wanted_match = match; /* "I want to read until I see N'th match" */ + read_lines(); + } + if (num_matches) { + normalize_match_pos(match); + buffer_line(match_lines[match_pos]); + } else { + print_statusline("No matches found"); } - - return line2; } -static void regex_process(void) { +static void fill_match_lines(unsigned pos) +{ + if (!pattern_valid) + return; + /* Run the regex on each line of the current file */ + while (pos <= max_fline) { + /* If this line matches */ + if (regexec(&pattern, flines[pos], 0, NULL, 0) == 0 + /* and we didn't match it last time */ + && !(num_matches && match_lines[num_matches-1] == pos) + ) { + match_lines = xrealloc_vector(match_lines, 4, num_matches); + match_lines[num_matches++] = pos; + } + pos++; + } +} - char uncomp_regex[100]; - char current_line[256]; - int i; - int j = 0; - regex_t *pattern; +static void regex_process(void) +{ + char *uncomp_regex, *err; /* Reset variables */ - match_lines[0] = -1; + free(match_lines); + match_lines = NULL; match_pos = 0; num_matches = 0; - match_found = 0; - - pattern = (regex_t *) malloc(sizeof(regex_t)); - memset(pattern, 0, sizeof(regex_t)); + if (pattern_valid) { + regfree(&pattern); + pattern_valid = 0; + } /* 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++; - } + bb_putchar((option_mask32 & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/'); + uncomp_regex = less_gets(1); + if (!uncomp_regex[0]) { + free(uncomp_regex); + buffer_print(); + return; } - num_matches = j; - - if ((match_lines[0] != -1) && (num_flines > height - 2)) - buffer_line(match_lines[0]); - else - buffer_init(); -} - -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; + /* Compile the regex and check for errors */ + err = regcomp_or_errmsg(&pattern, uncomp_regex, 0); + free(uncomp_regex); + if (err) { + print_statusline(err); + free(err); + return; } -} - -static void search_backwards(void) { - - 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]); + pattern_valid = 1; + match_pos = 0; + fill_match_lines(0); + while (match_pos < num_matches) { + if ((int)match_lines[match_pos] > cur_fline) break; - } + match_pos++; } + if (option_mask32 & LESS_STATE_MATCH_BACKWARDS) + match_pos--; - /* Reset variables */ - match_backwards = 0; - num_back_match = 1; - + /* It's possible that no matches are found yet. + * goto_match() will read input looking for match, + * if needed */ + goto_match(match_pos); } #endif -static void number_process(int first_digit) { - - int i = 1; +static void number_process(int first_digit) +{ + unsigned i; int num; - char num_input[80]; + char num_input[sizeof(int)*4]; /* more than enough */ char keypress; + num_input[0] = first_digit; /* Clear the current line, print a prompt, and then print the digit */ @@ -787,430 +1005,408 @@ static void number_process(int first_digit) { 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]); + i = 1; + while (i < sizeof(num_input)-1) { + num_input[i] = less_getch(i + 1); + if (!num_input[i] || !isdigit(num_input[i])) + break; + bb_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 = bb_strtou(num_input, NULL, 10); + /* on format error, num == -1 */ + if (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(); - break; -#ifdef CONFIG_FEATURE_LESS_REGEXP - case 'n': - goto_match(match_pos + num - 1); - buffer_print(); - break; - case '/': - regex_process(); - goto_match(num - 1); - buffer_print(); - break; - case '?': - num_back_match = num; - search_backwards(); - buffer_print(); - break; + case KEY_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015': + buffer_down(num); + break; + case KEY_UP: case 'b': case 'w': case 'y': case 'u': + buffer_up(num); + break; + case 'g': case '<': case 'G': case '>': + cur_fline = num + max_displayed_line; + read_lines(); + buffer_line(num - 1); + break; + case 'p': case '%': + num = num * (max_fline / 100); /* + max_fline / 2; */ + cur_fline = num + max_displayed_line; + read_lines(); + buffer_line(num); + break; +#if ENABLE_FEATURE_LESS_REGEXP + case 'n': + goto_match(match_pos + num); + break; + case '/': + option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS; + regex_process(); + break; + case '?': + option_mask32 |= LESS_STATE_MATCH_BACKWARDS; + regex_process(); + break; #endif - default: - break; } } -#ifdef CONFIG_FEATURE_LESS_FLAGCS -static void flag_change(void) { - +#if ENABLE_FEATURE_LESS_FLAGCS +static void flag_change(void) +{ int keypress; clear_line(); - printf("-"); - keypress = tless_getch(); + bb_putchar('-'); + keypress = less_getch(1); switch (keypress) { - case 'M': - M_FLAG = !M_FLAG; - break; - case 'm': - m_FLAG = !m_FLAG; - break; - case 'E': - E_FLAG = !E_FLAG; - break; - case '~': - TILDE_FLAG = !TILDE_FLAG; - break; - default: - break; + case 'M': + option_mask32 ^= FLAG_M; + break; + case 'm': + option_mask32 ^= FLAG_m; + break; + case 'E': + option_mask32 ^= FLAG_E; + break; + case '~': + option_mask32 ^= FLAG_TILDE; + break; } } -static void show_flag_status(void) { - +static void show_flag_status(void) +{ int keypress; int flag_val; clear_line(); - printf("_"); - keypress = tless_getch(); + bb_putchar('_'); + keypress = less_getch(1); switch (keypress) { - case 'M': - flag_val = M_FLAG; - break; - case 'm': - flag_val = m_FLAG; - break; - case '~': - flag_val = TILDE_FLAG; - break; - case 'N': - flag_val = N_FLAG; - break; - case 'E': - flag_val = E_FLAG; - break; - default: - flag_val = 0; - break; + case 'M': + flag_val = option_mask32 & FLAG_M; + break; + case 'm': + flag_val = option_mask32 & FLAG_m; + break; + case '~': + flag_val = option_mask32 & FLAG_TILDE; + break; + case 'N': + flag_val = option_mask32 & FLAG_N; + break; + case 'E': + flag_val = option_mask32 & FLAG_E; + break; + default: + flag_val = 0; + break; } clear_line(); - printf("%s%s%i%s", HIGHLIGHT, "The status of the flag is: ", flag_val != 0, NORMAL); + printf(HIGHLIGHT"The status of the flag is: %u"NORMAL, flag_val != 0); } #endif -static void full_repaint(void) { - - int temp_line_pos = line_pos; - data_readlines(); - buffer_init(); - buffer_line(temp_line_pos); - buffer_print(); -} - - -static void save_input_to_file(void) { - - char current_line[256]; - int i; +static void save_input_to_file(void) +{ + const char *msg = ""; + char *current_line; + unsigned i; FILE *fp; - clear_line(); - printf("Log file: "); - fgets(current_line, 256, inp); - current_line[strlen(current_line) - 1] = '\0'; - if (strlen(current_line)) { - fp = bb_xfopen(current_line, "w"); - for (i = 0; i < num_flines; i++) - fprintf(fp, "%s", flines[i]); + print_statusline("Log file: "); + current_line = less_gets(sizeof("Log file: ")-1); + if (current_line[0]) { + fp = fopen_for_write(current_line); + if (!fp) { + msg = "Error opening log file"; + goto ret; + } + for (i = 0; i <= max_fline; i++) + fprintf(fp, "%s\n", flines[i]); fclose(fp); - buffer_print(); + msg = "Done"; } - else - printf("%sNo log file%s", HIGHLIGHT, NORMAL); + ret: + print_statusline(msg); + free(current_line); } -#ifdef CONFIG_FEATURE_LESS_MARKS -static void add_mark(void) { - +#if ENABLE_FEATURE_LESS_MARKS +static void add_mark(void) +{ int letter; - int mark_line; - clear_line(); - printf("Mark: "); - letter = tless_getch(); + print_statusline("Mark: "); + letter = less_getch(sizeof("Mark: ") - 1); if (isalpha(letter)) { - mark_line = line_pos; - /* If we exceed 15 marks, start overwriting previous ones */ if (num_marks == 14) num_marks = 0; mark_lines[num_marks][0] = letter; - mark_lines[num_marks][1] = line_pos; + mark_lines[num_marks][1] = cur_fline; num_marks++; - } - else { - clear_line(); - printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL); + } else { + print_statusline("Invalid mark letter"); } } -static void goto_mark(void) { - +static void goto_mark(void) +{ int letter; int i; + print_statusline("Go to mark: "); + letter = less_getch(sizeof("Go to mark: ") - 1); clear_line(); - printf("Go to mark: "); - letter = tless_getch(); + 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(); - printf("%s%s%s", HIGHLIGHT, "Mark not set", NORMAL); - } - } - else { - clear_line(); - printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL); - } + if (num_marks == 14 && letter != mark_lines[14][0]) + print_statusline("Mark not set"); + } else + print_statusline("Invalid mark letter"); } #endif - -#ifdef CONFIG_FEATURE_LESS_BRACKETS - -static char opp_bracket (char bracket) { - +#if ENABLE_FEATURE_LESS_BRACKETS +static char opp_bracket(char bracket) +{ switch (bracket) { - case '{': case '[': - return bracket + 2; - break; - case '(': - return ')'; + case '{': case '[': /* '}' == '{' + 2. Same for '[' */ + bracket++; + case '(': /* ')' == '(' + 1 */ + bracket++; break; case '}': case ']': - return bracket - 2; - break; + bracket--; case ')': - return '('; + bracket--; break; - default: - return 0; - break; - } + }; + return bracket; } -static void match_right_bracket(char bracket) { +static void match_right_bracket(char bracket) +{ + unsigned i; - int bracket_line = -1; - int i; - - if (strchr(flines[line_pos], bracket) == NULL) { - clear_line(); - printf("%s%s%s", HIGHLIGHT, "No bracket in top line", NORMAL); + if (strchr(flines[cur_fline], bracket) == NULL) { + print_statusline("No bracket in top line"); + return; } - else { - for (i = line_pos + 1; i < num_flines; i++) { - if (strchr(flines[i], opp_bracket(bracket)) != NULL) { - bracket_line = i; - break; - } - } - - if (bracket_line == -1) { - clear_line(); - printf("%s%s%s", HIGHLIGHT, "No matching bracket found", NORMAL); + bracket = opp_bracket(bracket); + for (i = cur_fline + 1; i < max_fline; i++) { + if (strchr(flines[i], bracket) != NULL) { + buffer_line(i); + return; } - - buffer_line(bracket_line - height + 2); - buffer_print(); } + print_statusline("No matching bracket found"); } -static void match_left_bracket (char bracket) { - - int bracket_line = -1; +static void match_left_bracket(char bracket) +{ int i; - 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); + if (strchr(flines[cur_fline + max_displayed_line], bracket) == NULL) { + print_statusline("No bracket in bottom line"); + return; } - else { - for (i = line_pos + height - 2; i >= 0; i--) { - if (strchr(flines[i], opp_bracket(bracket)) != NULL) { - bracket_line = i; - break; - } - } - if (bracket_line == -1) { - clear_line(); - printf("%s%s%s", HIGHLIGHT, "No matching bracket found", NORMAL); + bracket = opp_bracket(bracket); + for (i = cur_fline + max_displayed_line; i >= 0; i--) { + if (strchr(flines[i], bracket) != NULL) { + buffer_line(i); + return; } - - buffer_line(bracket_line); - buffer_print(); } + print_statusline("No matching bracket found"); } +#endif /* FEATURE_LESS_BRACKETS */ -#endif /* CONFIG_FEATURE_LESS_BRACKETS */ - -static void keypress_process(int keypress) { +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_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; + case KEY_DOWN: case 'e': case 'j': case 0x0d: + buffer_down(1); + break; + case KEY_UP: case 'y': case 'k': + buffer_up(1); + break; + case PAGE_DOWN: case ' ': case 'z': case 'f': + buffer_down(max_displayed_line + 1); + break; + case PAGE_UP: case 'w': case 'b': + buffer_up(max_displayed_line + 1); + break; + case 'd': + buffer_down((max_displayed_line + 1) / 2); + break; + case 'u': + buffer_up((max_displayed_line + 1) / 2); + break; + case KEY_HOME: case 'g': case 'p': case '<': case '%': + buffer_line(0); + break; + case KEY_END: case 'G': case '>': + cur_fline = MAXLINES; + read_lines(); + buffer_line(cur_fline); + break; + case 'q': case 'Q': + less_exit(EXIT_SUCCESS); + break; +#if ENABLE_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; + case 'r': case 'R': + buffer_print(); + break; + /*case 'R': + full_repaint(); + break;*/ + case 's': + save_input_to_file(); + break; + case 'E': + examine_file(); + break; +#if ENABLE_FEATURE_LESS_FLAGS + case '=': + 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(); - break; - case '?': - search_backwards(); - buffer_print(); - break; +#if ENABLE_FEATURE_LESS_REGEXP + case '/': + option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS; + regex_process(); + break; + case 'n': + goto_match(match_pos + 1); + break; + case 'N': + goto_match(match_pos - 1); + break; + case '?': + option_mask32 |= LESS_STATE_MATCH_BACKWARDS; + regex_process(); + break; #endif -#ifdef CONFIG_FEATURE_LESS_FLAGCS - case '-': - flag_change(); - buffer_print(); - break; - case '_': - show_flag_status(); - break; +#if ENABLE_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; +#if ENABLE_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; + case ':': + colon_process(); + break; } + if (isdigit(keypress)) number_process(keypress); } -int less_main(int argc, char **argv) { +static void sig_catcher(int sig) +{ + less_exit(- sig); +} - unsigned long flags; +int less_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int less_main(int argc, char **argv) +{ 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); + INIT_G(); + /* TODO: -x: do not interpret backspace, -xx: tab also */ + /* -xxx: newline also */ + /* -w N: assume width N (-xxx -w 32: hex viewer of sorts) */ + getopt32(argv, "EMmN~"); argc -= optind; argv += optind; - files = argv; num_files = argc; + files = argv; + + /* Another popular pager, most, detects when stdout + * is not a tty and turns into cat. This makes sense. */ + if (!isatty(STDOUT_FILENO)) + return bb_cat(argv); + kbd_fd = open(CURRENT_TTY, O_RDONLY); + if (kbd_fd < 0) + return bb_cat(argv); + ndelay_on(kbd_fd); if (!num_files) { - if (ttyname(STDIN_FILENO) == NULL) - inp_stdin = 1; - else { - bb_error_msg("Missing filename"); + if (isatty(STDIN_FILENO)) { + /* Just "less"? No args and no redirection? */ + bb_error_msg("missing filename"); bb_show_usage(); } - } - - strcpy(filename, (inp_stdin) ? "stdin" : files[0]); - tty_width_height(); - data_readlines(); - buffer_init(); - buffer_print(); - + } else + filename = xstrdup(files[0]); + + get_terminal_width_height(kbd_fd, &width, &max_displayed_line); + /* 20: two tabstops + 4 */ + if (width < 20 || max_displayed_line < 3) + return bb_cat(argv); + max_displayed_line -= 2; + + buffer = xmalloc((max_displayed_line+1) * sizeof(char *)); + if (option_mask32 & FLAG_TILDE) + empty_line_marker = ""; + + tcgetattr(kbd_fd, &term_orig); + term_less = term_orig; + term_less.c_lflag &= ~(ICANON | ECHO); + term_less.c_iflag &= ~(IXON | ICRNL); + /*term_less.c_oflag &= ~ONLCR;*/ + term_less.c_cc[VMIN] = 1; + term_less.c_cc[VTIME] = 0; + + /* We want to restore term_orig on exit */ + bb_signals(BB_FATAL_SIGS, sig_catcher); + + reinitialize(); while (1) { - keypress = tless_getch(); + keypress = less_getch(-1); /* -1: do not position cursor */ keypress_process(keypress); } }