//config: help
//config: The -M/-m flag enables a more sophisticated status line.
//config:
+//config:config FEATURE_LESS_TRUNCATE
+//config: bool "Enable -S"
+//config: default y
+//config: depends on LESS
+//config: help
+//config: The -S flag causes long lines to be truncated rather than
+//config: wrapped.
+//config:
//config:config FEATURE_LESS_MARKS
//config: bool "Enable marks"
//config: default y
//config: this option makes less perform a last-ditch effort to find it:
//config: position cursor to 999,999 and ask terminal to report real
//config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
-//config:
//config: This is not clean but helps a lot on serial lines and such.
//config:
//config:config FEATURE_LESS_DASHCMD
//config: less itself ('-' keyboard command).
//config:
//config:config FEATURE_LESS_LINENUMS
-//config: bool "Enable dynamic switching of line numbers"
+//config: bool "Enable -N (dynamic switching of line numbers)"
//config: default y
//config: depends on FEATURE_LESS_DASHCMD
-//config: help
-//config: Enables "-N" command.
+
+//applet:IF_LESS(APPLET(less, BB_DIR_USR_BIN, BB_SUID_DROP))
+
+//kbuild:lib-$(CONFIG_LESS) += less.o
//usage:#define less_trivial_usage
-//usage: "[-E" IF_FEATURE_LESS_REGEXP("I")IF_FEATURE_LESS_FLAGS("Mm") "Nh~] [FILE]..."
+//usage: "[-E" IF_FEATURE_LESS_REGEXP("I")IF_FEATURE_LESS_FLAGS("Mm")
+//usage: "N" IF_FEATURE_LESS_TRUNCATE("S") "h~] [FILE]..."
//usage:#define less_full_usage "\n\n"
//usage: "View FILE (or stdin) one screenful at a time\n"
//usage: "\n -E Quit once the end of a file is reached"
//usage: "\n and percentage through the file"
//usage: )
//usage: "\n -N Prefix line number to each line"
+//usage: IF_FEATURE_LESS_TRUNCATE(
+//usage: "\n -S Truncate long lines"
+//usage: )
//usage: "\n -~ Suppress ~s displayed past EOF"
#include <sched.h> /* sched_yield() */
#include "libbb.h"
+#include "common_bufsiz.h"
#if ENABLE_FEATURE_LESS_REGEXP
#include "xregex.h"
#endif
FLAG_N = 1 << 3,
FLAG_TILDE = 1 << 4,
FLAG_I = 1 << 5,
- FLAG_S = (1 << 6) * ENABLE_FEATURE_LESS_DASHCMD,
+ FLAG_S = (1 << 6) * ENABLE_FEATURE_LESS_TRUNCATE,
/* hijack command line options variable for internal state vars */
LESS_STATE_MATCH_BACKWARDS = 1 << 15,
};
struct globals {
int cur_fline; /* signed */
int kbd_fd; /* fd to get input from */
+ int kbd_fd_orig_flags;
int less_gets_pos;
/* last position in last line, taking into account tabs */
size_t last_line_pos;
unsigned current_file;
char *filename;
char **files;
+#if ENABLE_FEATURE_LESS_FLAGS
+ int num_lines; /* a flag if < 0, line count if >= 0 */
+# define REOPEN_AND_COUNT (-1)
+# define REOPEN_STDIN (-2)
+# define NOT_REGULAR_FILE (-3)
+#endif
#if ENABLE_FEATURE_LESS_MARKS
unsigned num_marks;
unsigned mark_lines[15][2];
#define current_file (G.current_file )
#define filename (G.filename )
#define files (G.files )
+#define num_lines (G.num_lines )
#define num_marks (G.num_marks )
#define mark_lines (G.mark_lines )
#if ENABLE_FEATURE_LESS_REGEXP
static void less_exit(int code)
{
set_tty_cooked();
+ if (!(G.kbd_fd_orig_flags & O_NONBLOCK))
+ ndelay_off(kbd_fd);
clear_line();
if (code < 0)
kill_myself_with_sig(- code); /* does not return */
*d = *s;
if (*d != '\0') {
new_line_pos++;
- if (*d == '\t') /* tab */
+ if (*d == '\t') { /* tab */
new_line_pos += 7;
+ new_line_pos &= (~7);
+ }
s++;
d++;
if (new_line_pos >= w) {
#define fill_match_lines(pos) ((void)0)
#endif
+static int at_end(void)
+{
+ return (option_mask32 & FLAG_S)
+ ? !(cur_fline <= max_fline &&
+ max_lineno > LINENO(flines[cur_fline]) + max_displayed_line)
+ : !(max_fline > cur_fline + max_displayed_line);
+}
+
/* Devilishly complex routine.
*
* Has to deal with EOF and EPIPE on input,
*/
static void read_lines(void)
{
-#define readbuf bb_common_bufsiz1
char *current_line, *p;
int w = width;
char last_terminated = terminated;
unsigned old_max_fline = max_fline;
#endif
+#define readbuf bb_common_bufsiz1
+ setup_common_bufsiz();
+
/* (careful: max_fline can be -1) */
if (max_fline + 1 > MAXLINES)
return;
if (option_mask32 & FLAG_N)
w -= 8;
- p = current_line = ((char*)xmalloc(w + 4)) + 4;
- max_fline += last_terminated;
+ p = current_line = ((char*)xmalloc(w + 5)) + 4;
if (!last_terminated) {
const char *cp = flines[max_fline];
- strcpy(p, cp);
- p += strlen(current_line);
- free(MEMPTR(flines[max_fline]));
+ p = stpcpy(p, cp);
+ free(MEMPTR(cp));
/* last_line_pos is still valid from previous read_lines() */
} else {
+ max_fline++;
last_line_pos = 0;
}
time_t t;
errno = 0;
- eof_error = safe_read(STDIN_FILENO, readbuf, sizeof(readbuf));
+ eof_error = safe_read(STDIN_FILENO, readbuf, COMMON_BUFSIZE);
if (errno != EAGAIN)
break;
t = time(NULL);
new_last_line_pos += 7;
new_last_line_pos &= (~7);
}
- if ((int)new_last_line_pos >= w)
+ if ((int)new_last_line_pos > w)
break;
last_line_pos = new_last_line_pos;
}
eof_error = 0; /* Pretend we saw EOF */
break;
}
- if (!(option_mask32 & FLAG_S)
- ? (max_fline > cur_fline + max_displayed_line)
- : (max_fline >= cur_fline
- && max_lineno > LINENO(flines[cur_fline]) + max_displayed_line)
- ) {
+ if (!at_end()) {
#if !ENABLE_FEATURE_LESS_REGEXP
break;
#else
break;
}
max_fline++;
- current_line = ((char*)xmalloc(w + 4)) + 4;
+ current_line = ((char*)xmalloc(w + 5)) + 4;
p = current_line;
last_line_pos = 0;
} /* end of "read lines until we reach cur_fline" loop */
print_statusline(bb_msg_read_error);
}
}
+#if ENABLE_FEATURE_LESS_FLAGS
+ else if (eof_error == 0)
+ num_lines = max_lineno;
+#endif
fill_match_lines(old_max_fline);
#if ENABLE_FEATURE_LESS_REGEXP
}
#if ENABLE_FEATURE_LESS_FLAGS
-/* Interestingly, writing calc_percent as a function saves around 32 bytes
- * on my build. */
-static int calc_percent(void)
+static int safe_lineno(int fline)
+{
+ if (fline >= max_fline)
+ fline = max_fline - 1;
+
+ /* also catches empty file (max_fline == 0) */
+ if (fline < 0)
+ return 0;
+
+ return LINENO(flines[fline]) + 1;
+}
+
+/* count number of lines in file */
+static void update_num_lines(void)
{
- unsigned p = (100 * (cur_fline+max_displayed_line+1) + max_fline/2) / (max_fline+1);
- return p <= 100 ? p : 100;
+ int count, fd;
+ struct stat stbuf;
+ ssize_t len, i;
+ char buf[4096];
+
+ /* only do this for regular files */
+ if (num_lines == REOPEN_AND_COUNT || num_lines == REOPEN_STDIN) {
+ count = 0;
+ fd = open("/proc/self/fd/0", O_RDONLY);
+ if (fd < 0 && num_lines == REOPEN_AND_COUNT) {
+ /* "filename" is valid only if REOPEN_AND_COUNT */
+ fd = open(filename, O_RDONLY);
+ }
+ if (fd < 0) {
+ /* somebody stole my file! */
+ num_lines = NOT_REGULAR_FILE;
+ return;
+ }
+ if (fstat(fd, &stbuf) != 0 || !S_ISREG(stbuf.st_mode)) {
+ num_lines = NOT_REGULAR_FILE;
+ goto do_close;
+ }
+ while ((len = safe_read(fd, buf, sizeof(buf))) > 0) {
+ for (i = 0; i < len; ++i) {
+ if (buf[i] == '\n' && ++count == MAXLINES)
+ goto done;
+ }
+ }
+ done:
+ num_lines = count;
+ do_close:
+ close(fd);
+ }
}
/* Print a status line if -M was specified */
static void m_status_print(void)
{
- int percentage;
+ int first, last;
+ unsigned percent;
if (less_gets_pos >= 0) /* don't touch statusline while input is done! */
return;
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);
+
+ first = safe_lineno(cur_fline);
+ last = (option_mask32 & FLAG_S)
+ ? MIN(first + max_displayed_line, max_lineno)
+ : safe_lineno(cur_fline + max_displayed_line);
+ printf(" lines %i-%i", first, last);
+
+ update_num_lines();
+ if (num_lines >= 0)
+ printf("/%i", num_lines);
+
+ if (at_end()) {
+ printf(" (END)");
if (num_files > 1 && current_file != num_files)
- printf(HIGHLIGHT" - next: %s"NORMAL, files[current_file]);
- return;
+ printf(" - next: %s", files[current_file]);
+ } else if (num_lines > 0) {
+ percent = (100 * last + num_lines/2) / num_lines;
+ printf(" %i%%", percent <= 100 ? percent : 100);
}
- percentage = calc_percent();
- printf("%i%%"NORMAL, percentage);
+ printf(NORMAL);
}
#endif
#endif
clear_line();
- if (cur_fline && cur_fline < (int)(max_fline - max_displayed_line)) {
+ if (cur_fline && !at_end()) {
bb_putchar(':');
return;
}
print_hilite(p);
}
-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;
- }
-}
-
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"
"\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";
-static void lineno_str(char *nbuf9, const char *line)
+static void print_lineno(const char *line)
{
- nbuf9[0] = '\0';
- if (option_mask32 & FLAG_N) {
- const char *fmt;
- unsigned n;
-
- if (line == empty_line_marker) {
- memset(nbuf9, ' ', 8);
- nbuf9[8] = '\0';
- return;
- }
+ const char *fmt = " ";
+ unsigned n = n; /* for compiler */
+
+ if (line != empty_line_marker) {
/* Width of 7 preserves tab spacing in the text */
fmt = "%7u ";
n = LINENO(line) + 1;
- if (n > 9999999) {
+ if (n > 9999999 && MAXLINES > 9999999) {
n %= 10000000;
fmt = "%07u ";
}
- sprintf(nbuf9, fmt, n);
}
+ printf(fmt, n);
}
char *growline;
regmatch_t match_structs;
- char buf[width];
- char nbuf9[9];
+ char buf[width+1];
const char *str = line;
char *p = buf;
size_t n;
match_status = 1;
}
- lineno_str(nbuf9, line);
- if (!growline) {
- printf(CLEAR_2_EOL"%s%s\n", nbuf9, str);
- return;
- }
- printf(CLEAR_2_EOL"%s%s%s\n", nbuf9, growline, str);
+ printf("%s%s\n", growline ? growline : "", str);
free(growline);
}
#else
static void print_ascii(const char *str)
{
- char buf[width];
- char nbuf9[9];
+ char buf[width+1];
char *p;
size_t n;
- lineno_str(nbuf9, str);
- printf(CLEAR_2_EOL"%s", nbuf9);
-
while (*str) {
n = strcspn(str, controls);
if (n) {
unsigned i;
move_cursor(0, 0);
- for (i = 0; i <= max_displayed_line; i++)
+ for (i = 0; i <= max_displayed_line; i++) {
+ printf(CLEAR_2_EOL);
+ if (option_mask32 & FLAG_N)
+ print_lineno(buffer[i]);
if (pattern_valid)
print_found(buffer[i]);
else
print_ascii(buffer[i]);
+ }
+ if ((option_mask32 & FLAG_E)
+ && eof_error <= 0
+ && (max_fline - cur_fline) <= max_displayed_line
+ ) {
+ less_exit(EXIT_SUCCESS);
+ }
status_print();
}
static void buffer_fill_and_print(void)
{
unsigned i;
-#if ENABLE_FEATURE_LESS_DASHCMD
+#if ENABLE_FEATURE_LESS_TRUNCATE
int fpos = cur_fline;
if (option_mask32 & FLAG_S) {
buffer_print();
}
+/* move cur_fline to a given line number, reading lines if necessary */
+static void goto_lineno(int target)
+{
+ if (target <= 0 ) {
+ cur_fline = 0;
+ }
+ else if (target > LINENO(flines[cur_fline])) {
+ retry:
+ while (LINENO(flines[cur_fline]) != target && cur_fline < max_fline)
+ ++cur_fline;
+ /* target not reached but more input is available */
+ if (LINENO(flines[cur_fline]) != target && eof_error > 0) {
+ read_lines();
+ goto retry;
+ }
+ }
+ else {
+ /* search backwards through already-read lines */
+ while (LINENO(flines[cur_fline]) != target && cur_fline > 0)
+ --cur_fline;
+ }
+}
+
+static void cap_cur_fline(void)
+{
+ if ((option_mask32 & FLAG_S)) {
+ if (cur_fline > max_fline)
+ cur_fline = max_fline;
+ if (LINENO(flines[cur_fline]) + max_displayed_line > max_lineno + TILDES) {
+ goto_lineno(max_lineno - max_displayed_line + TILDES);
+ read_lines();
+ }
+ }
+ else {
+ if (cur_fline + max_displayed_line > max_fline + TILDES)
+ cur_fline = max_fline - max_displayed_line + TILDES;
+ if (cur_fline < 0)
+ cur_fline = 0;
+ }
+}
+
/* Move the buffer up and down in the file in order to scroll */
static void buffer_down(int nlines)
{
- cur_fline += nlines;
+ if ((option_mask32 & FLAG_S))
+ goto_lineno(LINENO(flines[cur_fline]) + nlines);
+ else
+ cur_fline += nlines;
read_lines();
- cap_cur_fline(nlines);
+ cap_cur_fline();
buffer_fill_and_print();
}
static void buffer_up(int nlines)
{
- cur_fline -= nlines;
- if (cur_fline < 0) cur_fline = 0;
+ if ((option_mask32 & FLAG_S)) {
+ goto_lineno(LINENO(flines[cur_fline]) - nlines);
+ }
+ else {
+ cur_fline -= nlines;
+ if (cur_fline < 0)
+ cur_fline = 0;
+ }
read_lines();
buffer_fill_and_print();
}
-static void buffer_line(int linenum)
+/* display a given line where the argument can be either an index into
+ * the flines array or a line number */
+static void buffer_to_line(int linenum, int is_lineno)
{
- if (linenum < 0)
- linenum = 0;
- cur_fline = linenum;
+ if (linenum <= 0)
+ cur_fline = 0;
+ else if (is_lineno)
+ goto_lineno(linenum);
+ else
+ 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;
+ cap_cur_fline();
buffer_fill_and_print();
}
+static void buffer_line(int linenum)
+{
+ buffer_to_line(linenum, FALSE);
+}
+
+static void buffer_lineno(int lineno)
+{
+ buffer_to_line(lineno, TRUE);
+}
+
static void open_file_and_read_lines(void)
{
if (filename) {
xmove_fd(xopen(filename, O_RDONLY), STDIN_FILENO);
+#if ENABLE_FEATURE_LESS_FLAGS
+ num_lines = REOPEN_AND_COUNT;
+#endif
} else {
/* "less" with no arguments in argv[] */
/* For status line only */
filename = xstrdup(bb_msg_standard_input);
+#if ENABLE_FEATURE_LESS_FLAGS
+ num_lines = REOPEN_STDIN;
+#endif
}
readpos = 0;
readeof = 0;
*/
rd = 1;
/* Are we interested in stdin? */
-//TODO: reuse code for determining this
- if (!(option_mask32 & FLAG_S)
- ? !(max_fline > cur_fline + max_displayed_line)
- : !(max_fline >= cur_fline
- && max_lineno > LINENO(flines[cur_fline]) + max_displayed_line)
- ) {
+ if (at_end()) {
if (eof_error > 0) /* did NOT reach eof yet */
rd = 0; /* yes, we are interested in stdin */
}
i = 1;
while (i < sizeof(num_input)-1) {
keypress = less_getch(i + 1);
- if ((unsigned)keypress > 255 || !isdigit(num_input[i]))
+ if ((unsigned)keypress > 255 || !isdigit(keypress))
break;
num_input[i] = keypress;
bb_putchar(keypress);
buffer_up(num);
break;
case 'g': case '<': case 'G': case '>':
- cur_fline = num + max_displayed_line;
- read_lines();
- buffer_line(num - 1);
+ buffer_lineno(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);
+#if ENABLE_FEATURE_LESS_FLAGS
+ update_num_lines();
+ num = num * (num_lines > 0 ? num_lines : max_lineno) / 100;
+#else
+ num = num * max_lineno / 100;
+#endif
+ buffer_lineno(num);
break;
#if ENABLE_FEATURE_LESS_REGEXP
case 'n':
case '~':
option_mask32 ^= FLAG_TILDE;
break;
+#if ENABLE_FEATURE_LESS_TRUNCATE
case 'S':
option_mask32 ^= FLAG_S;
buffer_fill_and_print();
break;
+#endif
#if ENABLE_FEATURE_LESS_LINENUMS
case 'N':
option_mask32 ^= FLAG_N;
static void match_right_bracket(char bracket)
{
- unsigned i;
+ unsigned i = cur_fline;
- if (strchr(flines[cur_fline], bracket) == NULL) {
+ if (i >= max_fline
+ || strchr(flines[i], bracket) == NULL
+ ) {
print_statusline("No bracket in top line");
return;
}
+
bracket = opp_bracket(bracket);
- for (i = cur_fline + 1; i < max_fline; i++) {
+ for (; i < max_fline; i++) {
if (strchr(flines[i], bracket) != NULL) {
- buffer_line(i);
+ /*
+ * Line with matched right bracket becomes
+ * last visible line
+ */
+ buffer_line(i - max_displayed_line);
return;
}
}
static void match_left_bracket(char bracket)
{
- int i;
+ int i = cur_fline + max_displayed_line;
- if (strchr(flines[cur_fline + max_displayed_line], bracket) == NULL) {
+ if (i >= max_fline
+ || strchr(flines[i], bracket) == NULL
+ ) {
print_statusline("No bracket in bottom line");
return;
}
bracket = opp_bracket(bracket);
- for (i = cur_fline + max_displayed_line; i >= 0; i--) {
+ for (; i >= 0; i--) {
if (strchr(flines[i], bracket) != NULL) {
+ /*
+ * Line with matched left bracket becomes
+ * first visible line
+ */
buffer_line(i);
return;
}
* -s: condense many empty lines to one
* (used by some setups for manpage display)
*/
- getopt32(argv, "EMmN~I" IF_FEATURE_LESS_DASHCMD("S") /*ignored:*/"s");
+ getopt32(argv, "EMmN~I" IF_FEATURE_LESS_TRUNCATE("S") /*ignored:*/"s");
argc -= optind;
argv += optind;
num_files = argc;
/* Some versions of less can survive w/o controlling tty,
* try to do the same. This also allows to specify an alternative
* tty via "less 1<>TTY".
- * We don't try to use STDOUT_FILENO directly,
+ *
+ * We prefer not to use STDOUT_FILENO directly,
* since we want to set this fd to non-blocking mode,
- * and not bother with restoring it on exit.
+ * and not interfere with other processes which share stdout with us.
*/
tty_name = xmalloc_ttyname(STDOUT_FILENO);
if (tty_name) {
/* Try controlling tty */
try_ctty:
tty_fd = open(CURRENT_TTY, O_RDONLY);
- if (tty_fd < 0)
- return bb_cat(argv);
+ if (tty_fd < 0) {
+ /* If all else fails, less 481 uses stdout. Mimic that */
+ tty_fd = STDOUT_FILENO;
+ }
}
- ndelay_on(tty_fd);
+ G.kbd_fd_orig_flags = ndelay_on(tty_fd);
kbd_fd = tty_fd; /* save in a global */
tcgetattr(kbd_fd, &term_orig);
Most options may be changed either on the command line,
or from within less by using the - or -- command.
Options may be given in one of two forms: either a single
- character preceded by a -, or a name preceeded by --.
+ character preceded by a -, or a name preceded by --.
-? ........ --help
Display help (from command line).
-a ........ --search-skip-screen