less: stop dying on bad regexps, quietly pipe data w/o

user interaction if stdout is not a tty.
size optimizations
This commit is contained in:
Denis Vlasenko 2006-12-21 13:24:58 +00:00
parent bf66fbc8e2
commit e865e81d34
4 changed files with 164 additions and 120 deletions

View File

@ -14,9 +14,12 @@
int bb_cat(char **argv) int bb_cat(char **argv)
{ {
static char *const argv_dash[] = { "-", NULL };
FILE *f; FILE *f;
int retval = EXIT_SUCCESS; int retval = EXIT_SUCCESS;
if (!*argv) argv = (char**) &argv_dash;
do { do {
f = fopen_or_warn_stdin(*argv); f = fopen_or_warn_stdin(*argv);
if (f) { if (f) {
@ -35,11 +38,6 @@ int bb_cat(char **argv)
int cat_main(int argc, char **argv) int cat_main(int argc, char **argv)
{ {
getopt32(argc, argv, "u"); getopt32(argc, argv, "u");
argv += optind; argv += optind;
if (!*argv) {
*--argv = "-";
}
return bb_cat(argv); return bb_cat(argv);
} }

View File

@ -12,6 +12,7 @@
#define __BB_REGEX__ #define __BB_REGEX__
#include <regex.h> #include <regex.h>
extern void xregcomp(regex_t *preg, const char *regex, int cflags); char* regcomp_or_errmsg(regex_t *preg, const char *regex, int cflags);
void xregcomp(regex_t *preg, const char *regex, int cflags);
#endif #endif

View File

@ -11,13 +11,22 @@
#include "libbb.h" #include "libbb.h"
#include "xregex.h" #include "xregex.h"
void xregcomp(regex_t *preg, const char *regex, int cflags) char* regcomp_or_errmsg(regex_t *preg, const char *regex, int cflags)
{ {
int ret = regcomp(preg, regex, cflags); int ret = regcomp(preg, regex, cflags);
if (ret) { if (ret) {
int errmsgsz = regerror(ret, preg, NULL, 0); int errmsgsz = regerror(ret, preg, NULL, 0);
char *errmsg = xmalloc(errmsgsz); char *errmsg = xmalloc(errmsgsz);
regerror(ret, preg, errmsg, errmsgsz); regerror(ret, preg, errmsg, errmsgsz);
return errmsg;
}
return NULL;
}
void xregcomp(regex_t *preg, const char *regex, int cflags)
{
char *errmsg = regcomp_or_errmsg(preg, regex, cflags);
if (errmsg) {
bb_error_msg_and_die("xregcomp: %s", errmsg); bb_error_msg_and_die("xregcomp: %s", errmsg);
} }
} }

View File

@ -50,9 +50,10 @@
/* The escape codes for highlighted and normal text */ /* The escape codes for highlighted and normal text */
#define HIGHLIGHT "\033[7m" #define HIGHLIGHT "\033[7m"
#define NORMAL "\033[0m" #define NORMAL "\033[0m"
/* The escape code to clear the screen */ /* The escape code to clear the screen */
#define CLEAR "\033[H\033[J" #define CLEAR "\033[H\033[J"
/* The escape code to clear to end of line */
#define CLEAR_2_EOL "\033[K"
#define MAXLINES CONFIG_FEATURE_LESS_MAXLINES #define MAXLINES CONFIG_FEATURE_LESS_MAXLINES
@ -121,9 +122,9 @@ static void tless_exit(int code)
static int tless_getch(void) static int tless_getch(void)
{ {
int input; int input;
/* Set terminal input to raw mode (taken from vi.c) */ /* Set terminal input to raw mode (taken from vi.c) */
tcsetattr(fileno(inp), TCSANOW, &term_vi); tcsetattr(fileno(inp), TCSANOW, &term_vi);
again:
input = getc(inp); input = getc(inp);
/* Detect escape sequences (i.e. arrow keys) and handle /* Detect escape sequences (i.e. arrow keys) and handle
them accordingly */ them accordingly */
@ -136,27 +137,56 @@ static int tless_getch(void)
i = input - REAL_KEY_UP; i = input - REAL_KEY_UP;
if (i < 4) if (i < 4)
return 20 + i; return 20 + i;
else if ((i = input - REAL_PAGE_UP) < 4) i = input - REAL_PAGE_UP;
if (i < 4)
return 24 + i; return 24 + i;
else return 0; /* ?? */
return 0; /* ?? */
} }
/* The input is a normal ASCII value */ /* Reject almost all control chars */
if (input < ' ' && input != 0x0d && input != 8) goto again;
set_tty_cooked(); set_tty_cooked();
return input; return input;
} }
static char* tless_gets(void)
{
int c;
int i = 0;
char *result = xzalloc(1);
while (1) {
c = tless_getch();
if (c == 0x0d)
return result;
if (c == 0x7f) c = 8;
if (c == 8 && i) {
printf("\x8 \x8");
i--;
}
if (c < ' ')
continue;
putchar(c);
result[i++] = c;
result = xrealloc(result, i+1);
result[i] = '\0';
if (i >= width-1) return result;
}
}
/* 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 */ top-left corner of the console */
static void move_cursor(int x, int y) static void move_cursor(int line, int row)
{ {
printf("\033[%i;%iH", x, y); printf("\033[%u;%uH", line, row);
} }
static void clear_line(void) static void clear_line(void)
{ {
move_cursor(height, 0); printf("\033[%u;0H" CLEAR_2_EOL, height);
printf("\033[K"); }
static void print_hilite(const char *str)
{
printf(HIGHLIGHT"%s"NORMAL, str);
} }
static void data_readlines(void) static void data_readlines(void)
@ -235,26 +265,26 @@ static void m_status_print(void)
if (!line_pos) { if (!line_pos) {
if (num_files > 1) { if (num_files > 1) {
printf("%s%s %s%i%s%i%s%i-%i/%i ", HIGHLIGHT, printf(HIGHLIGHT"%s (file %i of %i) lines %i-%i/%i ",
filename, "(file ", current_file, " of ", num_files, ") lines ", filename, current_file, num_files,
line_pos + 1, line_pos + height - 1, num_flines + 1); line_pos + 1, line_pos + height - 1, num_flines + 1);
} else { } else {
printf("%s%s lines %i-%i/%i ", HIGHLIGHT, printf(HIGHLIGHT"%s lines %i-%i/%i ",
filename, line_pos + 1, line_pos + height - 1, filename, line_pos + 1, line_pos + height - 1,
num_flines + 1); num_flines + 1);
} }
} else { } else {
printf("%s %s lines %i-%i/%i ", HIGHLIGHT, filename, printf(HIGHLIGHT" %s lines %i-%i/%i ", filename,
line_pos + 1, line_pos + height - 1, num_flines + 1); line_pos + 1, line_pos + height - 1, num_flines + 1);
} }
if (line_pos >= num_flines - height + 2) { if (line_pos >= num_flines - height + 2) {
printf("(END) %s", NORMAL); printf("(END) "NORMAL);
if ((num_files > 1) && (current_file != num_files)) if (num_files > 1 && current_file != num_files)
printf("%s- Next: %s%s", HIGHLIGHT, files[current_file], NORMAL); printf(HIGHLIGHT"- Next: %s"NORMAL, files[current_file]);
} else { } else {
percentage = calc_percent(); percentage = calc_percent();
printf("%i%% %s", percentage, NORMAL); printf("%i%% "NORMAL, percentage);
} }
} }
@ -265,11 +295,11 @@ static void medium_status_print(void)
percentage = calc_percent(); percentage = calc_percent();
if (!line_pos) if (!line_pos)
printf("%s%s %i%%%s", HIGHLIGHT, filename, percentage, NORMAL); printf(HIGHLIGHT"%s %i%%"NORMAL, filename, percentage);
else if (line_pos == num_flines - height + 2) else if (line_pos == num_flines - height + 2)
printf("%s(END)%s", HIGHLIGHT, NORMAL); print_hilite("(END)");
else else
printf("%s%i%%%s", HIGHLIGHT, percentage, NORMAL); printf(HIGHLIGHT"%i%%"NORMAL, percentage);
} }
#endif #endif
@ -286,14 +316,14 @@ static void status_print(void)
else { else {
#endif #endif
if (!line_pos) { if (!line_pos) {
printf("%s%s %s", HIGHLIGHT, filename, NORMAL); print_hilite(filename);
if (num_files > 1) if (num_files > 1)
printf("%s%s%i%s%i%s%s", HIGHLIGHT, "(file ", printf(HIGHLIGHT"(file %i of %i)"NORMAL,
current_file, " of ", num_files, ")", NORMAL); current_file, num_files);
} else if (line_pos == num_flines - height + 2) { } else if (line_pos == num_flines - height + 2) {
printf("%s%s %s", HIGHLIGHT, "(END)", NORMAL); print_hilite("(END) ");
if ((num_files > 1) && (current_file != num_files)) if (num_files > 1 && current_file != num_files)
printf("%s%s%s%s", HIGHLIGHT, "- Next: ", files[current_file], NORMAL); printf(HIGHLIGHT"- Next: %s"NORMAL, files[current_file]);
} else { } else {
putchar(':'); putchar(':');
} }
@ -336,13 +366,6 @@ static void print_found(const char *line)
memset(p, '.', n); memset(p, '.', n);
p += n; p += n;
str += n; str += n;
/*
do {
if (*str == '\x7f') { *p++ = '?'; str++; }
else if (*str == '\x9b') { *p++ = '{'; str++; }
else *p++ = ctrlconv[(unsigned char)*str++];
} while (--n);
*/
} }
strcpy(p, str); strcpy(p, str);
@ -358,13 +381,11 @@ static void print_found(const char *line)
goto start; goto start;
while (match_status == 0) { while (match_status == 0) {
char *new = xasprintf("%s" "%.*s" "%s" "%.*s" "%s", char *new = xasprintf("%s%.*s"HIGHLIGHT"%.*s"NORMAL,
growline ? : "", growline ? : "",
match_structs.rm_so, str, match_structs.rm_so, str,
HIGHLIGHT,
match_structs.rm_eo - match_structs.rm_so, match_structs.rm_eo - match_structs.rm_so,
str + match_structs.rm_so, str + match_structs.rm_so);
NORMAL);
free(growline); growline = new; free(growline); growline = new;
str += match_structs.rm_eo; str += match_structs.rm_eo;
line += match_structs.rm_eo; line += match_structs.rm_eo;
@ -375,10 +396,10 @@ static void print_found(const char *line)
} }
if (!growline) { if (!growline) {
puts(str); printf("%s"CLEAR_2_EOL"\n", str);
return; return;
} }
printf("%s%s\n", growline, str); printf("%s%s"CLEAR_2_EOL"\n", growline, str);
free(growline); free(growline);
} }
@ -398,14 +419,20 @@ static void print_ascii(const char *str)
n = strspn(str, controls); n = strspn(str, controls);
p = buf; p = buf;
do { do {
if (*str == '\x7f') { *p++ = '?'; str++; } if (*str == 0x7f)
else if (*str == '\x9b') { *p++ = '{'; str++; } *p++ = '?';
else *p++ = ctrlconv[(unsigned char)*str++]; else if (*str == 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); } while (--n);
*p = '\0'; *p = '\0';
printf("%s%s%s", HIGHLIGHT, buf, NORMAL); print_hilite(buf);
} }
puts(str); printf("%s"CLEAR_2_EOL"\n", str);
} }
/* Print the buffer */ /* Print the buffer */
@ -413,12 +440,13 @@ static void buffer_print(void)
{ {
int i; int i;
printf("%s", CLEAR); move_cursor(0, 0);
for (i = 0; i < height - 1; i++) for (i = 0; i < height - 1; i++)
if (pattern_valid) if (pattern_valid)
print_found(buffer[i]); print_found(buffer[i]);
else else
print_ascii(buffer[i]); print_ascii(buffer[i]);
fputs(CLEAR_2_EOL, stdout); /* clears status line */
status_print(); status_print();
} }
@ -427,11 +455,6 @@ static void buffer_init(void)
{ {
int i; int i;
if (!buffer) {
/* malloc the number of lines needed for the buffer */
buffer = xmalloc(height * sizeof(char *));
}
/* Fill the buffer until the end of the file or the /* Fill the buffer until the end of the file or the
end of the buffer is reached */ end of the buffer is reached */
for (i = 0; i < height - 1 && i <= num_flines; i++) { for (i = 0; i < height - 1 && i <= num_flines; i++) {
@ -491,18 +514,19 @@ static void buffer_line(int linenum)
if (linenum < 0 || linenum > num_flines) { if (linenum < 0 || linenum > num_flines) {
clear_line(); clear_line();
printf("%s%s%i%s", HIGHLIGHT, "Cannot seek to line number ", linenum + 1, NORMAL); printf(HIGHLIGHT"%s%u"NORMAL, "Cannot seek to line ", linenum + 1);
} else { return;
for (i = 0; i < height - 1; i++) {
if (linenum + i <= num_flines)
buffer[i] = flines[linenum + i];
else {
buffer[i] = empty_line_marker;
}
}
line_pos = linenum;
buffer_print();
} }
for (i = 0; i < height - 1; i++) {
if (linenum + i <= num_flines)
buffer[i] = flines[linenum + i];
else {
buffer[i] = empty_line_marker;
}
}
line_pos = linenum;
buffer_print();
} }
/* Reinitialise everything for a new file - free the memory and start over */ /* Reinitialise everything for a new file - free the memory and start over */
@ -524,10 +548,13 @@ static void examine_file(void)
clear_line(); clear_line();
printf("Examine: "); printf("Examine: ");
free(filename); free(filename);
filename = xmalloc_getline(inp); filename = tless_gets();
/* files start by = argv. why we assume that argv is infinitely long??
files[num_files] = filename; files[num_files] = filename;
current_file = num_files + 1; current_file = num_files + 1;
num_files++; num_files++; */
files[0] = filename;
current_file = 1;
reinitialise(); reinitialise();
} }
@ -545,7 +572,7 @@ static void change_file(int direction)
reinitialise(); reinitialise();
} else { } else {
clear_line(); clear_line();
printf("%s%s%s", HIGHLIGHT, (direction > 0) ? "No next file" : "No previous file", NORMAL); print_hilite((direction > 0) ? "No next file" : "No previous file");
} }
} }
@ -628,7 +655,7 @@ static void goto_match(int match)
static void regex_process(void) static void regex_process(void)
{ {
char *uncomp_regex; char *uncomp_regex, *err;
/* Reset variables */ /* Reset variables */
match_lines = xrealloc(match_lines, sizeof(int)); match_lines = xrealloc(match_lines, sizeof(int));
@ -643,16 +670,22 @@ static void regex_process(void)
/* Get the uncompiled regular expression from the user */ /* Get the uncompiled regular expression from the user */
clear_line(); clear_line();
putchar((option_mask32 & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/'); putchar((option_mask32 & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/');
uncomp_regex = xmalloc_getline(inp); uncomp_regex = tless_gets();
if (!uncomp_regex || !uncomp_regex[0]) { if (/*!uncomp_regex ||*/ !uncomp_regex[0]) {
free(uncomp_regex); free(uncomp_regex);
buffer_print(); buffer_print();
return; return;
} }
/* Compile the regex and check for errors */ /* Compile the regex and check for errors */
xregcomp(&pattern, uncomp_regex, 0); err = regcomp_or_errmsg(&pattern, uncomp_regex, 0);
free(uncomp_regex); free(uncomp_regex);
if (err) {
clear_line();
fputs(err, stdout);
free(err);
return;
}
pattern_valid = 1; pattern_valid = 1;
/* Run the regex on each line of the current file */ /* Run the regex on each line of the current file */
@ -799,7 +832,7 @@ static void show_flag_status(void)
} }
clear_line(); clear_line();
printf("%s%s%i%s", HIGHLIGHT, "The status of the flag is: ", flag_val != 0, NORMAL); printf(HIGHLIGHT"%s%u"NORMAL, "The status of the flag is: ", flag_val != 0);
} }
#endif #endif
@ -820,12 +853,12 @@ static void save_input_to_file(void)
clear_line(); clear_line();
printf("Log file: "); printf("Log file: ");
current_line = xmalloc_getline(inp); current_line = tless_gets();
if (strlen(current_line) > 0) { if (strlen(current_line) > 0) {
fp = fopen(current_line, "w"); fp = fopen(current_line, "w");
free(current_line); free(current_line);
if (!fp) { if (!fp) {
printf("%s%s%s", HIGHLIGHT, "Error opening log file", NORMAL); print_hilite("Error opening log file");
return; return;
} }
for (i = 0; i < num_flines; i++) for (i = 0; i < num_flines; i++)
@ -835,7 +868,7 @@ static void save_input_to_file(void)
return; return;
} }
free(current_line); free(current_line);
printf("%s%s%s", HIGHLIGHT, "No log file", NORMAL); print_hilite("No log file");
} }
#if ENABLE_FEATURE_LESS_MARKS #if ENABLE_FEATURE_LESS_MARKS
@ -858,7 +891,7 @@ static void add_mark(void)
num_marks++; num_marks++;
} else { } else {
clear_line(); clear_line();
printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL); print_hilite("Invalid mark letter");
} }
} }
@ -878,10 +911,10 @@ static void goto_mark(void)
buffer_line(mark_lines[i][1]); buffer_line(mark_lines[i][1]);
break; break;
} }
if ((num_marks == 14) && (letter != mark_lines[14][0])) if (num_marks == 14 && letter != mark_lines[14][0])
printf("%s%s%s", HIGHLIGHT, "Mark not set", NORMAL); print_hilite("Mark not set");
} else } else
printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL); print_hilite("Invalid mark letter");
} }
#endif #endif
@ -911,21 +944,19 @@ static void match_right_bracket(char bracket)
clear_line(); clear_line();
if (strchr(flines[line_pos], bracket) == NULL) if (strchr(flines[line_pos], bracket) == NULL) {
printf("%s%s%s", HIGHLIGHT, "No bracket in top line", NORMAL); print_hilite("No bracket in top line");
else { return;
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)
printf("%s%s%s", HIGHLIGHT, "No matching bracket found", NORMAL);
buffer_line(bracket_line - height + 2);
} }
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)
print_hilite("No matching bracket found");
buffer_line(bracket_line - height + 2);
} }
static void match_left_bracket(char bracket) static void match_left_bracket(char bracket)
@ -936,22 +967,22 @@ static void match_left_bracket(char bracket)
clear_line(); clear_line();
if (strchr(flines[line_pos + height - 2], bracket) == NULL) { if (strchr(flines[line_pos + height - 2], bracket) == NULL) {
printf("%s%s%s", HIGHLIGHT, "No bracket in bottom line", NORMAL); print_hilite("No bracket in bottom line");
printf("%s", flines[line_pos + height]); /* ?? */
sleep(4); /*printf("%s", flines[line_pos + height]);*/
} else { /*sleep(4);*/
for (i = line_pos + height - 2; i >= 0; i--) { return;
if (strchr(flines[i], opp_bracket(bracket)) != NULL) {
bracket_line = i;
break;
}
}
if (bracket_line == -1)
printf("%s%s%s", HIGHLIGHT, "No matching bracket found", NORMAL);
buffer_line(bracket_line);
} }
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)
print_hilite("No matching bracket found");
buffer_line(bracket_line);
} }
#endif /* FEATURE_LESS_BRACKETS */ #endif /* FEATURE_LESS_BRACKETS */
@ -959,7 +990,7 @@ static void match_left_bracket(char bracket)
static void keypress_process(int keypress) static void keypress_process(int keypress)
{ {
switch (keypress) { switch (keypress) {
case KEY_DOWN: case 'e': case 'j': case '\015': case KEY_DOWN: case 'e': case 'j': case 0x0d:
buffer_down(1); buffer_down(1);
buffer_print(); buffer_print();
break; break;
@ -1080,37 +1111,42 @@ int less_main(int argc, char **argv)
files = argv; files = argv;
num_files = argc; num_files = argc;
/* 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);
if (!num_files) { if (!num_files) {
if (isatty(STDIN_FILENO)) { if (isatty(STDIN_FILENO)) {
/* Just "less"? No file and no redirection? */ /* Just "less"? No args and no redirection? */
bb_error_msg("missing filename"); bb_error_msg("missing filename");
bb_show_usage(); bb_show_usage();
} }
} else } else
filename = xstrdup(files[0]); filename = xstrdup(files[0]);
/* FIXME: another popular pager, most, detects when stdout
* is not a tty and turns into cat */
inp = xfopen(CURRENT_TTY, "r"); inp = xfopen(CURRENT_TTY, "r");
get_terminal_width_height(fileno(inp), &width, &height); get_terminal_width_height(fileno(inp), &width, &height);
if (width < 10 || height < 3) if (width < 10 || height < 3)
bb_error_msg_and_die("too narrow here"); bb_error_msg_and_die("too narrow here");
if (option_mask32 & FLAG_TILDE) empty_line_marker = ""; buffer = xmalloc(height * sizeof(char *));
if (option_mask32 & FLAG_TILDE)
empty_line_marker = "";
data_readlines(); data_readlines();
tcgetattr(fileno(inp), &term_orig); tcgetattr(fileno(inp), &term_orig);
signal(SIGTERM, sig_catcher); signal(SIGTERM, sig_catcher);
signal(SIGINT, sig_catcher); signal(SIGINT, sig_catcher);
term_vi = term_orig; term_vi = term_orig;
term_vi.c_lflag &= (~ICANON & ~ECHO); term_vi.c_lflag &= (~ICANON & ~ECHO);
term_vi.c_iflag &= (~IXON & ~ICRNL); term_vi.c_iflag &= (~IXON & ~ICRNL);
term_vi.c_oflag &= (~ONLCR); term_vi.c_oflag &= (~ONLCR);
term_vi.c_cc[VMIN] = 1; term_vi.c_cc[VMIN] = 1;
term_vi.c_cc[VTIME] = 0; term_vi.c_cc[VTIME] = 0;
buffer_init(); buffer_init();
buffer_print(); buffer_print();