aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/applets.h3
-rw-r--r--include/usage.h16
-rw-r--r--miscutils/Config.in47
-rw-r--r--miscutils/Makefile.in1
-rw-r--r--miscutils/less.c1262
5 files changed, 1329 insertions, 0 deletions
diff --git a/include/applets.h b/include/applets.h
index 61210bcc3..64e561a88 100644
--- a/include/applets.h
+++ b/include/applets.h
@@ -359,6 +359,9 @@
#ifdef CONFIG_FEATURE_INITRD
APPLET_NOUSAGE("linuxrc", init_main, _BB_DIR_ROOT, _BB_SUID_NEVER)
#endif
+#ifdef CONFIG_LESS
+ APPLET(less, less_main, _BB_DIR_USR_BIN, _BB_SUID_NEVER)
+#endif
#ifdef CONFIG_LN
APPLET(ln, ln_main, _BB_DIR_BIN, _BB_SUID_NEVER)
#endif
diff --git a/include/usage.h b/include/usage.h
index 9ddd55fd8..43b6f7048 100644
--- a/include/usage.h
+++ b/include/usage.h
@@ -1498,6 +1498,22 @@
"$ length Hello\n" \
"5\n"
+#define less_trivial_usage \
+ "[-EMNmh~?] FILE1 FILE2..."
+#define less_full_usage \
+ "View a file or list of files. The position within files can be\n" \
+ "changed, and files can be manipulated in various ways with the\n" \
+ "following options:\n\n" \
+ "\t-E\tQuit once the end of a file is reached\n" \
+ "\t-M\tDisplay a status line containing the current line numbers\n" \
+ "\t\tand the percentage through the file\n" \
+ "\t-N\tPrefix line numbers to each line\n" \
+ "\t-m\tDisplay a status line containing the percentage through the\n" \
+ "\t\tfile\n" \
+ "\t-~\tSuppress ~s displayed when input past the end of the file is\n" \
+ "\t\treached.\n" \
+ "\t-h, -?\tDisplay this help message\n"
+
#define ln_trivial_usage \
"[OPTION] TARGET... LINK_NAME|DIRECTORY"
#define ln_full_usage \
diff --git a/miscutils/Config.in b/miscutils/Config.in
index 1f14d212f..a1048804f 100644
--- a/miscutils/Config.in
+++ b/miscutils/Config.in
@@ -104,6 +104,53 @@ config CONFIG_LAST
help
'last' displays a list of the last users that logged into the system.
+config CONFIG_LESS
+ bool "less"
+ default n
+ help
+ 'less' is a pager, meaning that it displays text files. It possesses
+ a wide array of features, and is an improvement over 'more'.
+
+config CONFIG_FEATURE_LESS_BRACKETS
+ bool " Enable bracket searching"
+ default y
+ depends on CONFIG_LESS
+ help
+ This option adds the capability to search for matching left and right
+ brackets, facilitating programming.
+
+config CONFIG_FEATURE_LESS_FLAGS
+ bool " Enable extra flags"
+ default y
+ depends on CONFIG_LESS
+ help
+ The extra flags provided do the following:
+
+ The -M flag enables a more sophisticated status line.
+ The -m flag enables a simpler status line with a percentage.
+
+config CONFIG_FEATURE_LESS_FLAGCS
+ bool " Enable flag changes"
+ default n
+ depends on CONFIG_LESS
+ help
+ This enables the ability to change command-line flags within
+ less itself.
+
+config CONFIG_FEATURE_LESS_MARKS
+ bool " Enable marks"
+ default n
+ depends on CONFIG_LESS
+ help
+ Marks enable positions in a file to be stored for easy reference.
+
+config CONFIG_FEATURE_LESS_REGEXP
+ bool " Enable regular expressions"
+ default n
+ depends on CONFIG_LESS
+ help
+ Enable regular expressions, allowing complex file searches.
+
config CONFIG_HDPARM
bool "hdparm"
default n
diff --git a/miscutils/Makefile.in b/miscutils/Makefile.in
index ee1cc7519..ba8069c83 100644
--- a/miscutils/Makefile.in
+++ b/miscutils/Makefile.in
@@ -33,6 +33,7 @@ MISCUTILS-$(CONFIG_DEVFSD) += devfsd.o
MISCUTILS-$(CONFIG_EJECT) += eject.o
MISCUTILS-$(CONFIG_HDPARM) += hdparm.o
MISCUTILS-$(CONFIG_LAST) += last.o
+MISCUTILS-${CONFIG_LESS} += less.o
MISCUTILS-$(CONFIG_MAKEDEVS) += makedevs.o
MISCUTILS-$(CONFIG_MOUNTPOINT) += mountpoint.o
MISCUTILS-$(CONFIG_MT) += mt.o
diff --git a/miscutils/less.c b/miscutils/less.c
new file mode 100644
index 000000000..43bc6737e
--- /dev/null
+++ b/miscutils/less.c
@@ -0,0 +1,1262 @@
+/* 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);
+ }
+}