busybox/miscutils/less.c
2005-09-15 19:26:59 +00:00

1263 lines
28 KiB
C

/* vi: set sw=4 ts=4: */
/*
* Mini less implementation for busybox
*
*
* Copyright (C) 2005 by Rob Sullivan <cogito.ergo.cogito@gmail.com>
*
* 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
*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <regex.h>
#include <ctype.h>
#include "busybox.h"
/* 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'
/* 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
/* 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)
/* Function prototypes */
static void set_tty_cooked(void);
static void set_tty_raw(void);
static void tless_exit(int code);
static int tless_getch(void);
static void move_cursor(int x, int y);
static void clear_line(void);
static void data_readlines(void);
static void free_flines(void);
#ifdef CONFIG_FEATURE_LESS_FLAGS
static int calc_percent(void);
#endif
static int reverse_percent(int percentage);
#ifdef CONFIG_FEATURE_LESS_FLAGS
static void m_status_print(void);
static void medium_status_print(void);
#endif
static void status_print(void);
static void buffer_print(void);
static void buffer_init(void);
static void buffer_down(int nlines);
static void buffer_up(int nlines);
static void buffer_line(int linenum);
static void keypress_process(int keypress);
static void colon_process(void);
static void number_process(int first_digit);
#ifdef CONFIG_FEATURE_LESS_FLAGCS
static void flag_change(void);
static void show_flag_status(void);
#endif
static void examine_file(void);
static void next_file(void);
static void previous_file(void);
static void first_file(void);
static void remove_current_file(void);
static void full_repaint(void);
static void add_linenumbers(void);
static void save_input_to_file(void);
#ifdef CONFIG_FEATURE_LESS_MARKS
static void add_mark(void);
static void goto_mark(void);
#endif
#ifdef CONFIG_FEATURE_LESS_REGEXP
static void regex_process(void);
char *process_regex_on_line(char *line, regex_t *pattern);
char *insert_highlights(char *line, int start, int end);
static void goto_match (int match);
static void search_backwards(void);
#endif
#ifdef CONFIG_FEATURE_LESS_BRACKETS
static char opp_bracket (char bracket);
static void match_right_bracket (char bracket);
static void match_left_bracket (char bracket);
#endif
int less_main(int argc, char *argv[]);
static int height;
static int width;
static char **files;
static char filename[256];
static char buffer[100][256];
static char *flines[MAXLINES];
static int current_file = 1;
static int line_pos = 0;
static int num_flines;
static int num_files = 1;
static int past_eof = 0;
/* Command line options */
static int E_FLAG = 0;
static int M_FLAG = 0;
static int N_FLAG = 0;
static int m_FLAG = 0;
static int TILDE_FLAG = 0;
/* This is needed so that program behaviour changes when input comes from
stdin */
static int inp_stdin = 0;
/* This is required so that when a file is requested to be examined after
input has come from stdin (e.g. dmesg | less), the input stream from
the keyboard still stays the same. If it switched back to stdin, keyboard
input wouldn't work. */
static int ea_inp_stdin = 0;
#ifdef CONFIG_FEATURE_LESS_MARKS
static int mark_lines[15][2];
static int num_marks = 0;
#endif
#ifdef CONFIG_FEATURE_LESS_REGEXP
static int match_found = 0;
static int match_lines[100];
static int match_pos = 0;
static int num_matches = 0;
static int match_backwards = 0;
static int num_back_match = 1;
#endif
/* Needed termios structures */
static struct termios term_orig, term_vi;
/* File pointer to get input from */
static FILE *inp;
/* Reset terminal input to normal */
static void set_tty_cooked() {
fflush(stdout);
tcsetattr(0, TCSANOW, &term_orig);
}
/* Set terminal input to raw mode */
static void set_tty_raw() {
tcgetattr(0, &term_orig);
term_vi = term_orig;
term_vi.c_lflag &= (~ICANON & ~ECHO);
term_vi.c_iflag &= (~IXON & ~ICRNL);
term_vi.c_oflag &= (~ONLCR);
term_vi.c_cc[VMIN] = 1;
term_vi.c_cc[VTIME] = 0;
tcsetattr(0, TCSANOW, &term_vi);
}
/* 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. */
int tless_getch() {
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;
}
/* 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 clear_line() {
move_cursor(height, 0);
printf("\033[K");
}
static void data_readlines() {
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();
}
/* Free the file data */
static void free_flines() {
int i;
for (i = 0; i <= num_flines; i++)
free(flines[i]);
}
#ifdef CONFIG_FEATURE_LESS_FLAGS
/* Calculate the percentage the current line position is through the file */
int calc_percent() {
return ((100 * (line_pos + height - 2) / num_flines) + 1);
}
#endif
/* Turn a percentage into a line number */
int reverse_percent(int percentage) {
double linenum = percentage;
linenum = ((linenum / 100) * num_flines) - 1;
return(linenum);
}
#ifdef CONFIG_FEATURE_LESS_FLAGS
/* Print a status line if -M was specified */
static void m_status_print() {
int percentage;
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);
}
}
else {
printf("%s %s lines %i-%i/%i ", HIGHLIGHT, filename, line_pos + 1, line_pos + height - 1, num_flines + 1);
}
if (line_pos == num_flines - height + 2) {
printf("(END) %s", NORMAL);
if ((num_files > 1) && (current_file != num_files))
printf("%s- Next: %s%s", HIGHLIGHT, files[current_file], NORMAL);
}
else {
percentage = calc_percent();
printf("%i%s %s", percentage, "%", NORMAL);
}
}
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);
}
}
/* Print a status line if -m was specified */
static void medium_status_print() {
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);
}
#endif
/* Print the status line */
static void status_print() {
/* Change the status if flags have been set */
#ifdef CONFIG_FEATURE_LESS_FLAGS
if (M_FLAG)
m_status_print();
else if (m_FLAG)
medium_status_print();
/* 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
}
#endif
}
/* Print the buffer */
static void buffer_print() {
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();
}
}
/* Initialise the buffer */
static void buffer_init() {
int i;
for (i = 0; i < (height - 1); i++)
memset(buffer[i], '\0', 256);
/* Fill the buffer until the end of the file or the
end of the buffer is reached */
for (i = 0; (i < (height - 1)) && (i <= num_flines); i++) {
strcpy(buffer[i], flines[i]);
}
/* If the buffer still isn't full, fill it with blank lines */
for (; i < (height - 1); i++) {
strcpy(buffer[i], "");
}
}
/* 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]);
}
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);
}
}
static void buffer_up(int nlines) {
int i;
int tilde_line;
if (!past_eof) {
if (line_pos - nlines >= 0) {
line_pos -= nlines;
for (i = 0; i < (height - 1); i++)
strcpy(buffer[i], flines[line_pos + i]);
}
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]);
}
}
}
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");
}
}
}
}
}
static void buffer_line(int linenum) {
int i;
past_eof = 0;
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 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;
#endif
case 'r':
buffer_print();
break;
case 'R':
full_repaint();
break;
case 's':
if (inp_stdin)
save_input_to_file();
break;
case 'E':
examine_file();
break;
#ifdef CONFIG_FEATURE_LESS_FLAGS
case '=':
clear_line();
m_status_print();
break;
#endif
#ifdef CONFIG_FEATURE_LESS_REGEXP
case '/':
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;
#endif
#ifdef CONFIG_FEATURE_LESS_FLAGCS
case '-':
flag_change();
buffer_print();
break;
case '_':
show_flag_status();
break;
#endif
#ifdef CONFIG_FEATURE_LESS_BRACKETS
case '{': case '(': case '[':
match_right_bracket(keypress);
break;
case '}': case ')': case ']':
match_left_bracket(keypress);
break;
#endif
case ':':
colon_process();
break;
default:
break;
}
if (isdigit(keypress))
number_process(keypress);
}
static void colon_process() {
int keypress;
/* Clear the current line and print a prompt */
clear_line();
printf(" :");
keypress = tless_getch();
switch (keypress) {
case 'd':
remove_current_file();
break;
case 'e':
examine_file();
break;
#ifdef CONFIG_FEATURE_LESS_FLAGS
case 'f':
clear_line();
m_status_print();
break;
#endif
case 'n':
next_file();
break;
case 'p':
previous_file();
break;
case 'q':
tless_exit(0);
break;
case 'x':
first_file();
break;
default:
break;
}
}
static void number_process(int first_digit) {
int i = 1;
int num;
char num_input[80];
char keypress;
num_input[0] = first_digit;
/* Clear the current line, print a prompt, and then print the digit */
clear_line();
printf(":%c", first_digit);
/* Receive input until a letter is given */
while((num_input[i] = tless_getch()) && isdigit(num_input[i])) {
printf("%c",num_input[i]);
i++;
}
/* Take the final letter out of the digits string */
keypress = num_input[i];
num_input[i] = '\0';
i--;
num = atoi(num_input);
/* 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;
#endif
default:
break;
}
}
#ifdef CONFIG_FEATURE_LESS_FLAGCS
static void flag_change() {
int keypress;
clear_line();
printf("-");
keypress = tless_getch();
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;
}
}
static void show_flag_status() {
int keypress;
int flag_val;
clear_line();
printf("_");
keypress = tless_getch();
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;
}
clear_line();
printf("%s%s%i%s", HIGHLIGHT, "The status of the flag is: ", flag_val, NORMAL);
}
#endif
static void examine_file() {
int newline_offset;
clear_line();
printf("Examine: ");
fgets(filename, 256, inp);
/* As fgets adds a newline to the end of an input string, we
need to remove it */
newline_offset = strlen(filename) - 1;
filename[newline_offset] = '\0';
files[num_files] = bb_xstrndup(filename, (strlen(filename) + 1) * sizeof(char));
current_file = num_files + 1;
num_files++;
inp_stdin = 0;
ea_inp_stdin = 1;
free_flines();
data_readlines();
buffer_init();
buffer_print();
}
static void next_file() {
if (current_file != num_files) {
current_file++;
strcpy(filename, files[current_file - 1]);
free_flines();
data_readlines();
buffer_init();
buffer_print();
}
else {
clear_line();
printf("%s%s%s", HIGHLIGHT, "No next file", NORMAL);
}
}
static void previous_file() {
if (current_file != 1) {
current_file--;
strcpy(filename, files[current_file - 1]);
free_flines();
data_readlines();
buffer_init();
buffer_print();
}
else {
clear_line();
printf("%s%s%s", HIGHLIGHT, "No previous file", NORMAL);
}
}
static void first_file() {
if (current_file != 1) {
current_file = 1;
strcpy(filename, files[current_file - 1]);
free_flines();
data_readlines();
buffer_init();
buffer_print();
}
}
static void remove_current_file() {
int i;
if (current_file != 1) {
previous_file();
for (i = 3; i <= num_files; i++)
files[i - 2] = files[i - 1];
num_files--;
buffer_print();
}
else {
next_file();
for (i = 2; i <= num_files; i++)
files[i - 2] = files[i - 1];
num_files--;
current_file--;
buffer_print();
}
}
static void full_repaint() {
int temp_line_pos = line_pos;
data_readlines();
buffer_init();
buffer_line(temp_line_pos);
buffer_print();
}
/* This adds line numbers to every line, as the -N flag necessitates */
static void add_linenumbers() {
char current_line[256];
int i;
for (i = 0; i <= num_flines; i++) {
safe_strncpy(current_line, flines[i], 256);
flines[i] = xrealloc(flines[i], strlen(current_line) + 7 );
sprintf(flines[i],"%5d %s", i+1, current_line);
}
}
static void save_input_to_file() {
char current_line[256];
int i;
FILE *fp;
clear_line();
printf("Log file: ");
fgets(current_line, 256, inp);
current_line[strlen(current_line) - 1] = '\0';
if (strlen(current_line)) {
fp = bb_xfopen(current_line, "w");
for (i = 0; i < num_flines; i++)
fprintf(fp, "%s", flines[i]);
fclose(fp);
buffer_print();
}
else
printf("%sNo log file%s", HIGHLIGHT, NORMAL);
}
#ifdef CONFIG_FEATURE_LESS_MARKS
static void add_mark() {
int letter;
int mark_line;
clear_line();
printf("Mark: ");
letter = tless_getch();
if (isalpha(letter)) {
mark_line = line_pos;
/* If we exceed 15 marks, start overwriting previous ones */
if (num_marks == 14)
num_marks = 0;
mark_lines[num_marks][0] = letter;
mark_lines[num_marks][1] = line_pos;
num_marks++;
}
else {
clear_line();
printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL);
}
}
static void goto_mark() {
int letter;
int i;
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);
}
}
#endif
#ifdef CONFIG_FEATURE_LESS_REGEXP
/* The below two regular expression handler functions NEED development. */
/* Get a regular expression from the user, and then go through the current
file line by line, running a processing regex function on each one. */
static void regex_process() {
char uncomp_regex[100];
char current_line[256];
int i;
int j = 0;
regex_t *pattern;
/* Reset variables */
match_lines[0] = -1;
match_pos = 0;
num_matches = 0;
match_found = 0;
pattern = (regex_t *) malloc(sizeof(regex_t));
memset(pattern, 0, sizeof(regex_t));
/* Get the uncompiled regular expression from the user */
clear_line();
if (match_backwards)
printf("?");
else
printf("/");
scanf("%s", uncomp_regex);
/* Compile the regex and check for errors */
xregcomp(pattern, uncomp_regex, 0);
/* Run the regex on each line of the current file here */
for (i = 0; i <= num_flines; i++) {
strcpy(current_line, process_regex_on_line(flines[i], pattern));
flines[i] = (char *) bb_xstrndup(current_line, sizeof(char) * (strlen(current_line)+1));
if (match_found) {
match_lines[j] = i;
j++;
}
}
num_matches = j;
if ((match_lines[0] != -1) && (num_flines > height - 2))
buffer_line(match_lines[0]);
else
buffer_init();
}
char *process_regex_on_line(char *line, regex_t *pattern) {
/* This function takes the regex and applies it to the line.
Each part of the line that matches has the HIGHLIGHT
and NORMAL escape sequences placed around it by
insert_highlights, and then the line is returned. */
int match_status;
char *line2 = (char *) malloc((sizeof(char) * (strlen(line) + 1)) + 64);
char sub_line[256];
int prev_eo = 0;
memset(sub_line, 0, 256);
strcpy(line2, line);
regmatch_t match_structs;
match_found = 0;
match_status = regexec(pattern, line2, 1, &match_structs, 0);
while (match_status == 0) {
memset(sub_line, 0, 256);
if (match_found == 0)
match_found = 1;
line2 = insert_highlights(line2, match_structs.rm_so + prev_eo, match_structs.rm_eo + prev_eo);
if (match_structs.rm_eo + 11 + prev_eo < strlen(line2))
strcat(sub_line, line2 + match_structs.rm_eo + 11 + prev_eo);
prev_eo += match_structs.rm_eo + 11;
match_status = regexec(pattern, sub_line, 1, &match_structs, REG_NOTBOL);
}
return line2;
}
char *insert_highlights (char *line, int start, int end) {
char *new_line = (char *) malloc((sizeof(char) * (strlen(line) + 1)) + 10);
memset(new_line, 0, ((sizeof(char) * (strlen(line) + 1)) + 10));
strncat(new_line, line, start);
strcat(new_line, HIGHLIGHT);
strncat(new_line, line + start, end - start);
strcat(new_line, NORMAL);
strncat(new_line, line + end, strlen(line) - end);
return new_line;
}
static void goto_match(int match) {
/* This goes to a specific match - all line positions of matches are
stored within the match_lines[] array. */
if ((match < num_matches) && (match >= 0)) {
buffer_line(match_lines[match]);
match_pos = match;
}
}
static void search_backwards() {
int current_linepos = line_pos;
int i;
match_backwards = 1;
regex_process();
for (i = 0; i < num_matches; i++) {
if (match_lines[i] > current_linepos) {
buffer_line(match_lines[i - num_back_match]);
break;
}
}
/* Reset variables */
match_backwards = 0;
num_back_match = 1;
}
#endif
#ifdef CONFIG_FEATURE_LESS_BRACKETS
static char opp_bracket (char bracket) {
switch (bracket) {
case '{': case '[':
return bracket + 2;
break;
case '(':
return ')';
break;
case '}': case ']':
return bracket - 2;
break;
case ')':
return '(';
break;
default:
return 0;
break;
}
}
static void match_right_bracket(char bracket) {
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);
}
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);
}
buffer_line(bracket_line - height + 2);
buffer_print();
}
}
static void match_left_bracket (char bracket) {
int bracket_line = -1;
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);
}
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);
}
buffer_line(bracket_line);
buffer_print();
}
}
#endif
int less_main(int argc, char **argv) {
unsigned long flags;
int keypress;
flags = bb_getopt_ulflags(argc, argv, "EMNm~");
E_FLAG = (flags & 1);
M_FLAG = (flags & 2);
N_FLAG = (flags & 4);
m_FLAG = (flags & 8);
TILDE_FLAG = (flags & 16);
argc -= optind;
argv += optind;
files = argv;
num_files = argc;
if (!num_files) {
if (ttyname(STDIN_FILENO) == NULL)
inp_stdin = 1;
else {
bb_error_msg("Missing filename");
bb_show_usage();
}
}
strcpy(filename, (inp_stdin) ? "stdin" : files[0]);
tty_width_height();
data_readlines();
buffer_init();
buffer_print();
while (1) {
keypress = tless_getch();
keypress_process(keypress);
}
}