1377 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1377 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* vi: set sw=4 ts=4: */
 | |
| /*
 | |
|  * Mini less implementation for busybox
 | |
|  *
 | |
|  * Copyright (C) 2005 by Rob Sullivan <cogito.ergo.cogito@gmail.com>
 | |
|  *
 | |
|  * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
 | |
|  */
 | |
| 
 | |
| /*
 | |
|  * TODO:
 | |
|  * - Add more regular expression support - search modifiers, certain matches, etc.
 | |
|  * - Add more complex bracket searching - currently, nested brackets are
 | |
|  *   not considered.
 | |
|  * - Add support for "F" as an input. This causes less to act in
 | |
|  *   a similar way to tail -f.
 | |
|  * - Allow horizontal scrolling.
 | |
|  *
 | |
|  * Notes:
 | |
|  * - the inp file pointer is used so that keyboard input works after
 | |
|  *   redirected input has been read from stdin
 | |
|  */
 | |
| 
 | |
| #include <sched.h>	/* sched_yield() */
 | |
| 
 | |
| #include "libbb.h"
 | |
| #if ENABLE_FEATURE_LESS_REGEXP
 | |
| #include "xregex.h"
 | |
| #endif
 | |
| 
 | |
| /* FIXME: currently doesn't work right */
 | |
| #undef ENABLE_FEATURE_LESS_FLAGCS
 | |
| #define ENABLE_FEATURE_LESS_FLAGCS 0
 | |
| 
 | |
| /* The escape codes for highlighted and normal text */
 | |
| #define HIGHLIGHT "\033[7m"
 | |
| #define NORMAL "\033[0m"
 | |
| /* The escape code to clear the screen */
 | |
| #define CLEAR "\033[H\033[J"
 | |
| /* The escape code to clear to end of line */
 | |
| #define CLEAR_2_EOL "\033[K"
 | |
| 
 | |
| /* These are the escape sequences corresponding to special keys */
 | |
| enum {
 | |
| 	REAL_KEY_UP = 'A',
 | |
| 	REAL_KEY_DOWN = 'B',
 | |
| 	REAL_KEY_RIGHT = 'C',
 | |
| 	REAL_KEY_LEFT = 'D',
 | |
| 	REAL_PAGE_UP = '5',
 | |
| 	REAL_PAGE_DOWN = '6',
 | |
| 	REAL_KEY_HOME = '7', // vt100? linux vt? or what?
 | |
| 	REAL_KEY_END = '8',
 | |
| 	REAL_KEY_HOME_ALT = '1', // ESC [1~ (vt100? linux vt? or what?)
 | |
| 	REAL_KEY_END_ALT = '4', // ESC [4~
 | |
| 	REAL_KEY_HOME_XTERM = 'H',
 | |
| 	REAL_KEY_END_XTERM = 'F',
 | |
| 
 | |
| /* These are the special codes assigned by this program to the special keys */
 | |
| 	KEY_UP = 20,
 | |
| 	KEY_DOWN = 21,
 | |
| 	KEY_RIGHT = 22,
 | |
| 	KEY_LEFT = 23,
 | |
| 	PAGE_UP = 24,
 | |
| 	PAGE_DOWN = 25,
 | |
| 	KEY_HOME = 26,
 | |
| 	KEY_END = 27,
 | |
| 
 | |
| /* Absolute max of lines eaten */
 | |
| 	MAXLINES = CONFIG_FEATURE_LESS_MAXLINES,
 | |
| 
 | |
| /* This many "after the end" lines we will show (at max) */
 | |
| 	TILDES = 1,
 | |
| };
 | |
| 
 | |
| /* Command line options */
 | |
| enum {
 | |
| 	FLAG_E = 1,
 | |
| 	FLAG_M = 1 << 1,
 | |
| 	FLAG_m = 1 << 2,
 | |
| 	FLAG_N = 1 << 3,
 | |
| 	FLAG_TILDE = 1 << 4,
 | |
| /* hijack command line options variable for internal state vars */
 | |
| 	LESS_STATE_MATCH_BACKWARDS = 1 << 15,
 | |
| };
 | |
| 
 | |
| #if !ENABLE_FEATURE_LESS_REGEXP
 | |
| enum { pattern_valid = 0 };
 | |
| #endif
 | |
| 
 | |
| struct globals {
 | |
| 	int cur_fline; /* signed */
 | |
| 	int kbd_fd;  /* fd to get input from */
 | |
| /* last position in last line, taking into account tabs */
 | |
| 	size_t linepos;
 | |
| 	unsigned max_displayed_line;
 | |
| 	unsigned max_fline;
 | |
| 	unsigned max_lineno; /* this one tracks linewrap */
 | |
| 	unsigned width;
 | |
| 	ssize_t eof_error; /* eof if 0, error if < 0 */
 | |
| 	size_t readpos;
 | |
| 	size_t readeof;
 | |
| 	const char **buffer;
 | |
| 	const char **flines;
 | |
| 	const char *empty_line_marker;
 | |
| 	unsigned num_files;
 | |
| 	unsigned current_file;
 | |
| 	char *filename;
 | |
| 	char **files;
 | |
| #if ENABLE_FEATURE_LESS_MARKS
 | |
| 	unsigned num_marks;
 | |
| 	unsigned mark_lines[15][2];
 | |
| #endif
 | |
| #if ENABLE_FEATURE_LESS_REGEXP
 | |
| 	unsigned *match_lines;
 | |
| 	int match_pos; /* signed! */
 | |
| 	unsigned num_matches;
 | |
| 	regex_t pattern;
 | |
| 	smallint pattern_valid;
 | |
| #endif
 | |
| 	smallint terminated;
 | |
| 	struct termios term_orig, term_less;
 | |
| };
 | |
| #define G (*ptr_to_globals)
 | |
| #define cur_fline           (G.cur_fline         )
 | |
| #define kbd_fd              (G.kbd_fd            )
 | |
| #define linepos             (G.linepos           )
 | |
| #define max_displayed_line  (G.max_displayed_line)
 | |
| #define max_fline           (G.max_fline         )
 | |
| #define max_lineno          (G.max_lineno        )
 | |
| #define width               (G.width             )
 | |
| #define eof_error           (G.eof_error         )
 | |
| #define readpos             (G.readpos           )
 | |
| #define readeof             (G.readeof           )
 | |
| #define buffer              (G.buffer            )
 | |
| #define flines              (G.flines            )
 | |
| #define empty_line_marker   (G.empty_line_marker )
 | |
| #define num_files           (G.num_files         )
 | |
| #define current_file        (G.current_file      )
 | |
| #define filename            (G.filename          )
 | |
| #define files               (G.files             )
 | |
| #define num_marks           (G.num_marks         )
 | |
| #define mark_lines          (G.mark_lines        )
 | |
| #if ENABLE_FEATURE_LESS_REGEXP
 | |
| #define match_lines         (G.match_lines       )
 | |
| #define match_pos           (G.match_pos         )
 | |
| #define num_matches         (G.num_matches       )
 | |
| #define pattern             (G.pattern           )
 | |
| #define pattern_valid       (G.pattern_valid     )
 | |
| #endif
 | |
| #define terminated          (G.terminated        )
 | |
| #define term_orig           (G.term_orig         )
 | |
| #define term_less           (G.term_less         )
 | |
| #define INIT_G() do { \
 | |
| 		PTR_TO_GLOBALS = xzalloc(sizeof(G)); \
 | |
| 		empty_line_marker = "~"; \
 | |
| 		num_files = 1; \
 | |
| 		current_file = 1; \
 | |
| 		eof_error = 1; \
 | |
| 		terminated = 1; \
 | |
| 	} while (0)
 | |
| 
 | |
| /* Reset terminal input to normal */
 | |
| static void set_tty_cooked(void)
 | |
| {
 | |
| 	fflush(stdout);
 | |
| 	tcsetattr(kbd_fd, TCSANOW, &term_orig);
 | |
| }
 | |
| 
 | |
| /* Exit the program gracefully */
 | |
| static void less_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? */
 | |
| 	bb_putchar('\n');
 | |
| 	fflush_stdout_and_exit(code);
 | |
| }
 | |
| 
 | |
| /* Move the cursor to a position (x,y), where (0,0) is the
 | |
|    top-left corner of the console */
 | |
| static void move_cursor(int line, int row)
 | |
| {
 | |
| 	printf("\033[%u;%uH", line, row);
 | |
| }
 | |
| 
 | |
| static void clear_line(void)
 | |
| {
 | |
| 	printf("\033[%u;0H" CLEAR_2_EOL, max_displayed_line + 2);
 | |
| }
 | |
| 
 | |
| static void print_hilite(const char *str)
 | |
| {
 | |
| 	printf(HIGHLIGHT"%s"NORMAL, str);
 | |
| }
 | |
| 
 | |
| static void print_statusline(const char *str)
 | |
| {
 | |
| 	clear_line();
 | |
| 	printf(HIGHLIGHT"%.*s"NORMAL, width - 1, str);
 | |
| }
 | |
| 
 | |
| #if ENABLE_FEATURE_LESS_REGEXP
 | |
| static void fill_match_lines(unsigned pos);
 | |
| #else
 | |
| #define fill_match_lines(pos) ((void)0)
 | |
| #endif
 | |
| 
 | |
| /* Devilishly complex routine.
 | |
|  *
 | |
|  * Has to deal with EOF and EPIPE on input,
 | |
|  * with line wrapping, with last line not ending in '\n'
 | |
|  * (possibly not ending YET!), with backspace and tabs.
 | |
|  * It reads input again if last time we got an EOF (thus supporting
 | |
|  * growing files) or EPIPE (watching output of slow process like make).
 | |
|  *
 | |
|  * Variables used:
 | |
|  * flines[] - array of lines already read. Linewrap may cause
 | |
|  *      one source file line to occupy several flines[n].
 | |
|  * flines[max_fline] - last line, possibly incomplete.
 | |
|  * terminated - 1 if flines[max_fline] is 'terminated'
 | |
|  *      (if there was '\n' [which isn't stored itself, we just remember
 | |
|  *      that it was seen])
 | |
|  * max_lineno - last line's number, this one doesn't increment
 | |
|  *      on line wrap, only on "real" new lines.
 | |
|  * readbuf[0..readeof-1] - small preliminary buffer.
 | |
|  * readbuf[readpos] - next character to add to current line.
 | |
|  * linepos - screen line position of next char to be read
 | |
|  *      (takes into account tabs and backspaces)
 | |
|  * eof_error - < 0 error, == 0 EOF, > 0 not EOF/error
 | |
|  */
 | |
| static void read_lines(void)
 | |
| {
 | |
| #define readbuf bb_common_bufsiz1
 | |
| 	char *current_line, *p;
 | |
| 	USE_FEATURE_LESS_REGEXP(unsigned old_max_fline = max_fline;)
 | |
| 	int w = width;
 | |
| 	char last_terminated = terminated;
 | |
| 
 | |
| 	if (option_mask32 & FLAG_N)
 | |
| 		w -= 8;
 | |
| 
 | |
| 	current_line = xmalloc(w);
 | |
| 	p = current_line;
 | |
| 	max_fline += last_terminated;
 | |
| 	if (!last_terminated) {
 | |
| 		const char *cp = flines[max_fline];
 | |
| 		if (option_mask32 & FLAG_N)
 | |
| 			cp += 8;
 | |
| 		strcpy(current_line, cp);
 | |
| 		p += strlen(current_line);
 | |
| 		/* linepos is still valid from previous read_lines() */
 | |
| 	} else {
 | |
| 		linepos = 0;
 | |
| 	}
 | |
| 
 | |
| 	while (1) {
 | |
|  again:
 | |
| 		*p = '\0';
 | |
| 		terminated = 0;
 | |
| 		while (1) {
 | |
| 			char c;
 | |
| 			/* if no unprocessed chars left, eat more */
 | |
| 			if (readpos >= readeof) {
 | |
| 				smallint yielded = 0;
 | |
| 
 | |
| 				ndelay_on(0);
 | |
|  read_again:
 | |
| 				eof_error = safe_read(0, readbuf, sizeof(readbuf));
 | |
| 				readpos = 0;
 | |
| 				readeof = eof_error;
 | |
| 				if (eof_error < 0) {
 | |
| 					if (errno == EAGAIN && !yielded) {
 | |
| 			/* We can hit EAGAIN while searching for regexp match.
 | |
| 			 * Yield is not 100% reliable solution in general,
 | |
| 			 * but for less it should be good enough -
 | |
| 			 * we give stdin supplier some CPU time to produce
 | |
| 			 * more input. We do it just once.
 | |
| 			 * Currently, we do not stop when we found the Nth
 | |
| 			 * occurrence we were looking for. We read till end
 | |
| 			 * (or double EAGAIN). TODO? */
 | |
| 						sched_yield();
 | |
| 						yielded = 1;
 | |
| 						goto read_again;
 | |
| 					}
 | |
| 					readeof = 0;
 | |
| 					if (errno != EAGAIN)
 | |
| 						print_statusline("read error");
 | |
| 				}
 | |
| 				ndelay_off(0);
 | |
| 
 | |
| 				if (eof_error <= 0) {
 | |
| 					goto reached_eof;
 | |
| 				}
 | |
| 			}
 | |
| 			c = readbuf[readpos];
 | |
| 			/* backspace? [needed for manpages] */
 | |
| 			/* <tab><bs> is (a) insane and */
 | |
| 			/* (b) harder to do correctly, so we refuse to do it */
 | |
| 			if (c == '\x8' && linepos && p[-1] != '\t') {
 | |
| 				readpos++; /* eat it */
 | |
| 				linepos--;
 | |
| 			/* was buggy (p could end up <= current_line)... */
 | |
| 				*--p = '\0';
 | |
| 				continue;
 | |
| 			}
 | |
| 			{
 | |
| 				size_t new_linepos = linepos + 1;
 | |
| 				if (c == '\t') {
 | |
| 					new_linepos += 7;
 | |
| 					new_linepos &= (~7);
 | |
| 				}
 | |
| 				if (new_linepos >= w)
 | |
| 					break;
 | |
| 				linepos = new_linepos;
 | |
| 			}
 | |
| 			/* ok, we will eat this char */
 | |
| 			readpos++;
 | |
| 			if (c == '\n') {
 | |
| 				terminated = 1;
 | |
| 				linepos = 0;
 | |
| 				break;
 | |
| 			}
 | |
| 			/* NUL is substituted by '\n'! */
 | |
| 			if (c == '\0') c = '\n';
 | |
| 			*p++ = c;
 | |
| 			*p = '\0';
 | |
| 		}
 | |
| 		/* Corner case: linewrap with only "" wrapping to next line */
 | |
| 		/* Looks ugly on screen, so we do not store this empty line */
 | |
| 		if (!last_terminated && !current_line[0]) {
 | |
| 			last_terminated = 1;
 | |
| 			max_lineno++;
 | |
| 			goto again;
 | |
| 		}
 | |
|  reached_eof:
 | |
| 		last_terminated = terminated;
 | |
| 		flines = xrealloc(flines, (max_fline+1) * sizeof(char *));
 | |
| 		if (option_mask32 & FLAG_N) {
 | |
| 			/* Width of 7 preserves tab spacing in the text */
 | |
| 			flines[max_fline] = xasprintf(
 | |
| 				(max_lineno <= 9999999) ? "%7u %s" : "%07u %s",
 | |
| 				max_lineno % 10000000, current_line);
 | |
| 			free(current_line);
 | |
| 			if (terminated)
 | |
| 				max_lineno++;
 | |
| 		} else {
 | |
| 			flines[max_fline] = xrealloc(current_line, strlen(current_line)+1);
 | |
| 		}
 | |
| 		if (max_fline >= MAXLINES) {
 | |
| 			eof_error = 0; /* Pretend we saw EOF */
 | |
| 			break;
 | |
| 		}
 | |
| 		if (max_fline > cur_fline + max_displayed_line)
 | |
| 			break;
 | |
| 		if (eof_error <= 0) {
 | |
| 			if (eof_error < 0 && errno == EAGAIN) {
 | |
| 				/* not yet eof or error, reset flag (or else
 | |
| 				 * we will hog CPU - select() will return
 | |
| 				 * immediately */
 | |
| 				eof_error = 1;
 | |
| 			}
 | |
| 			break;
 | |
| 		}
 | |
| 		max_fline++;
 | |
| 		current_line = xmalloc(w);
 | |
| 		p = current_line;
 | |
| 		linepos = 0;
 | |
| 	}
 | |
| 	fill_match_lines(old_max_fline);
 | |
| #undef readbuf
 | |
| }
 | |
| 
 | |
| #if ENABLE_FEATURE_LESS_FLAGS
 | |
| /* Interestingly, writing calc_percent as a function saves around 32 bytes
 | |
|  * on my build. */
 | |
| static int calc_percent(void)
 | |
| {
 | |
| 	unsigned p = (100 * (cur_fline+max_displayed_line+1) + max_fline/2) / (max_fline+1);
 | |
| 	return p <= 100 ? p : 100;
 | |
| }
 | |
| 
 | |
| /* Print a status line if -M was specified */
 | |
| static void m_status_print(void)
 | |
| {
 | |
| 	int percentage;
 | |
| 
 | |
| 	clear_line();
 | |
| 	printf(HIGHLIGHT"%s", filename);
 | |
| 	if (num_files > 1)
 | |
| 		printf(" (file %i of %i)", current_file, num_files);
 | |
| 	printf(" lines %i-%i/%i ",
 | |
| 			cur_fline + 1, cur_fline + max_displayed_line + 1,
 | |
| 			max_fline + 1);
 | |
| 	if (cur_fline >= max_fline - max_displayed_line) {
 | |
| 		printf("(END)"NORMAL);
 | |
| 		if (num_files > 1 && current_file != num_files)
 | |
| 			printf(HIGHLIGHT" - next: %s"NORMAL, files[current_file]);
 | |
| 		return;
 | |
| 	}
 | |
| 	percentage = calc_percent();
 | |
| 	printf("%i%%"NORMAL, percentage);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| /* Print the status line */
 | |
| static void status_print(void)
 | |
| {
 | |
| 	const char *p;
 | |
| 
 | |
| 	/* Change the status if flags have been set */
 | |
| #if ENABLE_FEATURE_LESS_FLAGS
 | |
| 	if (option_mask32 & (FLAG_M|FLAG_m)) {
 | |
| 		m_status_print();
 | |
| 		return;
 | |
| 	}
 | |
| 	/* No flags set */
 | |
| #endif
 | |
| 
 | |
| 	clear_line();
 | |
| 	if (cur_fline && cur_fline < max_fline - max_displayed_line) {
 | |
| 		bb_putchar(':');
 | |
| 		return;
 | |
| 	}
 | |
| 	p = "(END)";
 | |
| 	if (!cur_fline)
 | |
| 		p = filename;
 | |
| 	if (num_files > 1) {
 | |
| 		printf(HIGHLIGHT"%s (file %i of %i)"NORMAL,
 | |
| 				p, current_file, num_files);
 | |
| 		return;
 | |
| 	}
 | |
| 	print_hilite(p);
 | |
| }
 | |
| 
 | |
| 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"
 | |
| 	"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
 | |
| 	"\x7f\x9b"; /* DEL and infamous Meta-ESC :( */
 | |
| static const char ctrlconv[] ALIGN1 =
 | |
| 	/* '\n': it's a former NUL - subst with '@', not 'J' */
 | |
| 	"\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x40\x4b\x4c\x4d\x4e\x4f"
 | |
| 	"\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f";
 | |
| 
 | |
| #if ENABLE_FEATURE_LESS_REGEXP
 | |
| static void print_found(const char *line)
 | |
| {
 | |
| 	int match_status;
 | |
| 	int eflags;
 | |
| 	char *growline;
 | |
| 	regmatch_t match_structs;
 | |
| 
 | |
| 	char buf[width];
 | |
| 	const char *str = line;
 | |
| 	char *p = buf;
 | |
| 	size_t n;
 | |
| 
 | |
| 	while (*str) {
 | |
| 		n = strcspn(str, controls);
 | |
| 		if (n) {
 | |
| 			if (!str[n]) break;
 | |
| 			memcpy(p, str, n);
 | |
| 			p += n;
 | |
| 			str += n;
 | |
| 		}
 | |
| 		n = strspn(str, controls);
 | |
| 		memset(p, '.', n);
 | |
| 		p += n;
 | |
| 		str += n;
 | |
| 	}
 | |
| 	strcpy(p, str);
 | |
| 
 | |
| 	/* buf[] holds quarantined version of str */
 | |
| 
 | |
| 	/* Each part of the line that matches has the HIGHLIGHT
 | |
| 	   and NORMAL escape sequences placed around it.
 | |
| 	   NB: we regex against line, but insert text
 | |
| 	   from quarantined copy (buf[]) */
 | |
| 	str = buf;
 | |
| 	growline = NULL;
 | |
| 	eflags = 0;
 | |
| 	goto start;
 | |
| 
 | |
| 	while (match_status == 0) {
 | |
| 		char *new = xasprintf("%s%.*s"HIGHLIGHT"%.*s"NORMAL,
 | |
| 				growline ? : "",
 | |
| 				match_structs.rm_so, str,
 | |
| 				match_structs.rm_eo - match_structs.rm_so,
 | |
| 						str + match_structs.rm_so);
 | |
| 		free(growline); growline = new;
 | |
| 		str += match_structs.rm_eo;
 | |
| 		line += match_structs.rm_eo;
 | |
| 		eflags = REG_NOTBOL;
 | |
|  start:
 | |
| 		/* Most of the time doesn't find the regex, optimize for that */
 | |
| 		match_status = regexec(&pattern, line, 1, &match_structs, eflags);
 | |
| 	}
 | |
| 
 | |
| 	if (!growline) {
 | |
| 		printf(CLEAR_2_EOL"%s\n", str);
 | |
| 		return;
 | |
| 	}
 | |
| 	printf(CLEAR_2_EOL"%s%s\n", growline, str);
 | |
| 	free(growline);
 | |
| }
 | |
| #else
 | |
| void print_found(const char *line);
 | |
| #endif
 | |
| 
 | |
| static void print_ascii(const char *str)
 | |
| {
 | |
| 	char buf[width];
 | |
| 	char *p;
 | |
| 	size_t n;
 | |
| 
 | |
| 	printf(CLEAR_2_EOL);
 | |
| 	while (*str) {
 | |
| 		n = strcspn(str, controls);
 | |
| 		if (n) {
 | |
| 			if (!str[n]) break;
 | |
| 			printf("%.*s", (int) n, str);
 | |
| 			str += n;
 | |
| 		}
 | |
| 		n = strspn(str, controls);
 | |
| 		p = buf;
 | |
| 		do {
 | |
| 			if (*str == 0x7f)
 | |
| 				*p++ = '?';
 | |
| 			else if (*str == (char)0x9b)
 | |
| 			/* VT100's CSI, aka Meta-ESC. Who's inventor? */
 | |
| 			/* I want to know who committed this sin */
 | |
| 				*p++ = '{';
 | |
| 			else
 | |
| 				*p++ = ctrlconv[(unsigned char)*str];
 | |
| 			str++;
 | |
| 		} while (--n);
 | |
| 		*p = '\0';
 | |
| 		print_hilite(buf);
 | |
| 	}
 | |
| 	puts(str);
 | |
| }
 | |
| 
 | |
| /* Print the buffer */
 | |
| static void buffer_print(void)
 | |
| {
 | |
| 	int i;
 | |
| 
 | |
| 	move_cursor(0, 0);
 | |
| 	for (i = 0; i <= max_displayed_line; i++)
 | |
| 		if (pattern_valid)
 | |
| 			print_found(buffer[i]);
 | |
| 		else
 | |
| 			print_ascii(buffer[i]);
 | |
| 	status_print();
 | |
| }
 | |
| 
 | |
| static void buffer_fill_and_print(void)
 | |
| {
 | |
| 	int i;
 | |
| 	for (i = 0; i <= max_displayed_line && cur_fline + i <= max_fline; i++) {
 | |
| 		buffer[i] = flines[cur_fline + i];
 | |
| 	}
 | |
| 	for (; i <= max_displayed_line; i++) {
 | |
| 		buffer[i] = empty_line_marker;
 | |
| 	}
 | |
| 	buffer_print();
 | |
| }
 | |
| 
 | |
| /* Move the buffer up and down in the file in order to scroll */
 | |
| static void buffer_down(int nlines)
 | |
| {
 | |
| 	cur_fline += nlines;
 | |
| 	read_lines();
 | |
| 	cap_cur_fline(nlines);
 | |
| 	buffer_fill_and_print();
 | |
| }
 | |
| 
 | |
| static void buffer_up(int nlines)
 | |
| {
 | |
| 	cur_fline -= nlines;
 | |
| 	if (cur_fline < 0) cur_fline = 0;
 | |
| 	read_lines();
 | |
| 	buffer_fill_and_print();
 | |
| }
 | |
| 
 | |
| static void buffer_line(int linenum)
 | |
| {
 | |
| 	if (linenum < 0)
 | |
| 		linenum = 0;
 | |
| 	cur_fline = linenum;
 | |
| 	read_lines();
 | |
| 	if (linenum + max_displayed_line > max_fline)
 | |
| 		linenum = max_fline - max_displayed_line + TILDES;
 | |
| 	if (linenum < 0)
 | |
| 		linenum = 0;
 | |
| 	cur_fline = linenum;
 | |
| 	buffer_fill_and_print();
 | |
| }
 | |
| 
 | |
| static void open_file_and_read_lines(void)
 | |
| {
 | |
| 	if (filename) {
 | |
| 		int fd = xopen(filename, O_RDONLY);
 | |
| 		dup2(fd, 0);
 | |
| 		if (fd) close(fd);
 | |
| 	} else {
 | |
| 		/* "less" with no arguments in argv[] */
 | |
| 		/* For status line only */
 | |
| 		filename = xstrdup(bb_msg_standard_input);
 | |
| 	}
 | |
| 	readpos = 0;
 | |
| 	readeof = 0;
 | |
| 	linepos = 0;
 | |
| 	terminated = 1;
 | |
| 	read_lines();
 | |
| }
 | |
| 
 | |
| /* Reinitialize everything for a new file - free the memory and start over */
 | |
| static void reinitialize(void)
 | |
| {
 | |
| 	int i;
 | |
| 
 | |
| 	if (flines) {
 | |
| 		for (i = 0; i <= max_fline; i++)
 | |
| 			free((void*)(flines[i]));
 | |
| 		free(flines);
 | |
| 		flines = NULL;
 | |
| 	}
 | |
| 
 | |
| 	max_fline = -1;
 | |
| 	cur_fline = 0;
 | |
| 	max_lineno = 0;
 | |
| 	open_file_and_read_lines();
 | |
| 	buffer_fill_and_print();
 | |
| }
 | |
| 
 | |
| static void getch_nowait(char* input, int sz)
 | |
| {
 | |
| 	ssize_t rd;
 | |
| 	fd_set readfds;
 | |
|  again:
 | |
| 	fflush(stdout);
 | |
| 
 | |
| 	/* NB: select returns whenever read will not block. Therefore:
 | |
| 	 * (a) with O_NONBLOCK'ed fds select will return immediately
 | |
| 	 * (b) if eof is reached, select will also return
 | |
| 	 *     because read will immediately return 0 bytes.
 | |
| 	 * Even if select says that input is available, read CAN block
 | |
| 	 * (switch fd into O_NONBLOCK'ed mode to avoid it)
 | |
| 	 */
 | |
| 	FD_ZERO(&readfds);
 | |
| 	if (max_fline <= cur_fline + max_displayed_line
 | |
| 	 && eof_error > 0 /* did NOT reach eof yet */
 | |
| 	) {
 | |
| 		/* We are interested in stdin */
 | |
| 		FD_SET(0, &readfds);
 | |
| 	}
 | |
| 	FD_SET(kbd_fd, &readfds);
 | |
| 	tcsetattr(kbd_fd, TCSANOW, &term_less);
 | |
| 	select(kbd_fd + 1, &readfds, NULL, NULL, NULL);
 | |
| 
 | |
| 	input[0] = '\0';
 | |
| 	ndelay_on(kbd_fd);
 | |
| 	rd = read(kbd_fd, input, sz);
 | |
| 	ndelay_off(kbd_fd);
 | |
| 	if (rd < 0) {
 | |
| 		/* No keyboard input, but we have input on stdin! */
 | |
| 		if (errno != EAGAIN) /* Huh?? */
 | |
| 			return;
 | |
| 		read_lines();
 | |
| 		buffer_fill_and_print();
 | |
| 		goto again;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /* Grab a character from input without requiring the return key. If the
 | |
|  * character is ASCII \033, get more characters and assign certain sequences
 | |
|  * special return codes. Note that this function works best with raw input. */
 | |
| static int less_getch(void)
 | |
| {
 | |
| 	char input[16];
 | |
| 	unsigned i;
 | |
|  again:
 | |
| 	memset(input, 0, sizeof(input));
 | |
| 	getch_nowait(input, sizeof(input));
 | |
| 
 | |
| 	/* Detect escape sequences (i.e. arrow keys) and handle
 | |
| 	 * them accordingly */
 | |
| 	if (input[0] == '\033' && input[1] == '[') {
 | |
| 		set_tty_cooked();
 | |
| 		i = input[2] - REAL_KEY_UP;
 | |
| 		if (i < 4)
 | |
| 			return 20 + i;
 | |
| 		i = input[2] - REAL_PAGE_UP;
 | |
| 		if (i < 4)
 | |
| 			return 24 + i;
 | |
| 		if (input[2] == REAL_KEY_HOME_XTERM)
 | |
| 			return KEY_HOME;
 | |
| 		if (input[2] == REAL_KEY_HOME_ALT)
 | |
| 			return KEY_HOME;
 | |
| 		if (input[2] == REAL_KEY_END_XTERM)
 | |
| 			return KEY_END;
 | |
| 		if (input[2] == REAL_KEY_END_ALT)
 | |
| 			return KEY_END;
 | |
| 		return 0;
 | |
| 	}
 | |
| 	/* Reject almost all control chars */
 | |
| 	i = input[0];
 | |
| 	if (i < ' ' && i != 0x0d && i != 8) goto again;
 | |
| 	set_tty_cooked();
 | |
| 	return i;
 | |
| }
 | |
| 
 | |
| static char* less_gets(int sz)
 | |
| {
 | |
| 	char c;
 | |
| 	int i = 0;
 | |
| 	char *result = xzalloc(1);
 | |
| 	while (1) {
 | |
| 		fflush(stdout);
 | |
| 
 | |
| 		/* I be damned if I know why is it needed *repeatedly*,
 | |
| 		 * but it is needed. Is it because of stdio? */
 | |
| 		tcsetattr(kbd_fd, TCSANOW, &term_less);
 | |
| 
 | |
| 		c = '\0';
 | |
| 		read(kbd_fd, &c, 1);
 | |
| 		if (c == 0x0d)
 | |
| 			return result;
 | |
| 		if (c == 0x7f)
 | |
| 			c = 8;
 | |
| 		if (c == 8 && i) {
 | |
| 			printf("\x8 \x8");
 | |
| 			i--;
 | |
| 		}
 | |
| 		if (c < ' ')
 | |
| 			continue;
 | |
| 		if (i >= width - sz - 1)
 | |
| 			continue; /* len limit */
 | |
| 		bb_putchar(c);
 | |
| 		result[i++] = c;
 | |
| 		result = xrealloc(result, i+1);
 | |
| 		result[i] = '\0';
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void examine_file(void)
 | |
| {
 | |
| 	print_statusline("Examine: ");
 | |
| 	free(filename);
 | |
| 	filename = less_gets(sizeof("Examine: ")-1);
 | |
| 	/* files start by = argv. why we assume that argv is infinitely long??
 | |
| 	files[num_files] = filename;
 | |
| 	current_file = num_files + 1;
 | |
| 	num_files++; */
 | |
| 	files[0] = filename;
 | |
| 	num_files = current_file = 1;
 | |
| 	reinitialize();
 | |
| }
 | |
| 
 | |
| /* This function changes the file currently being paged. direction can be one of the following:
 | |
|  * -1: go back one file
 | |
|  *  0: go to the first file
 | |
|  *  1: go forward one file */
 | |
| static void change_file(int direction)
 | |
| {
 | |
| 	if (current_file != ((direction > 0) ? num_files : 1)) {
 | |
| 		current_file = direction ? current_file + direction : 1;
 | |
| 		free(filename);
 | |
| 		filename = xstrdup(files[current_file - 1]);
 | |
| 		reinitialize();
 | |
| 	} else {
 | |
| 		print_statusline(direction > 0 ? "No next file" : "No previous file");
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void remove_current_file(void)
 | |
| {
 | |
| 	int i;
 | |
| 
 | |
| 	if (num_files < 2)
 | |
| 		return;
 | |
| 
 | |
| 	if (current_file != 1) {
 | |
| 		change_file(-1);
 | |
| 		for (i = 3; i <= num_files; i++)
 | |
| 			files[i - 2] = files[i - 1];
 | |
| 		num_files--;
 | |
| 	} else {
 | |
| 		change_file(1);
 | |
| 		for (i = 2; i <= num_files; i++)
 | |
| 			files[i - 2] = files[i - 1];
 | |
| 		num_files--;
 | |
| 		current_file--;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void colon_process(void)
 | |
| {
 | |
| 	int keypress;
 | |
| 
 | |
| 	/* Clear the current line and print a prompt */
 | |
| 	print_statusline(" :");
 | |
| 
 | |
| 	keypress = less_getch();
 | |
| 	switch (keypress) {
 | |
| 	case 'd':
 | |
| 		remove_current_file();
 | |
| 		break;
 | |
| 	case 'e':
 | |
| 		examine_file();
 | |
| 		break;
 | |
| #if ENABLE_FEATURE_LESS_FLAGS
 | |
| 	case 'f':
 | |
| 		m_status_print();
 | |
| 		break;
 | |
| #endif
 | |
| 	case 'n':
 | |
| 		change_file(1);
 | |
| 		break;
 | |
| 	case 'p':
 | |
| 		change_file(-1);
 | |
| 		break;
 | |
| 	case 'q':
 | |
| 		less_exit(0);
 | |
| 		break;
 | |
| 	case 'x':
 | |
| 		change_file(0);
 | |
| 		break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| #if ENABLE_FEATURE_LESS_REGEXP
 | |
| static void normalize_match_pos(int match)
 | |
| {
 | |
| 	if (match >= num_matches)
 | |
| 		match = num_matches - 1;
 | |
| 	if (match < 0)
 | |
| 		match = 0;
 | |
| 	match_pos = match;
 | |
| }
 | |
| 
 | |
| static void goto_match(int match)
 | |
| {
 | |
| 	int sv;
 | |
| 
 | |
| 	if (!pattern_valid)
 | |
| 		return;
 | |
| 	if (match < 0)
 | |
| 		match = 0;
 | |
| 	sv = cur_fline;
 | |
| 	/* Try to find next match if eof isn't reached yet */
 | |
| 	if (match >= num_matches && eof_error > 0) {
 | |
| 		cur_fline = MAXLINES; /* look as far as needed */
 | |
| 		read_lines();
 | |
| 	}
 | |
| 	if (num_matches) {
 | |
| 		cap_cur_fline(cur_fline);
 | |
| 		normalize_match_pos(match);
 | |
| 		buffer_line(match_lines[match_pos]);
 | |
| 	} else {
 | |
| 		cur_fline = sv;
 | |
| 		print_statusline("No matches found");
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void fill_match_lines(unsigned pos)
 | |
| {
 | |
| 	if (!pattern_valid)
 | |
| 		return;
 | |
| 	/* Run the regex on each line of the current file */
 | |
| 	while (pos <= max_fline) {
 | |
| 		/* If this line matches */
 | |
| 		if (regexec(&pattern, flines[pos], 0, NULL, 0) == 0
 | |
| 		/* and we didn't match it last time */
 | |
| 		 && !(num_matches && match_lines[num_matches-1] == pos)
 | |
| 		) {
 | |
| 			match_lines = xrealloc(match_lines, (num_matches+1) * sizeof(int));
 | |
| 			match_lines[num_matches++] = pos;
 | |
| 		}
 | |
| 		pos++;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void regex_process(void)
 | |
| {
 | |
| 	char *uncomp_regex, *err;
 | |
| 
 | |
| 	/* Reset variables */
 | |
| 	free(match_lines);
 | |
| 	match_lines = NULL;
 | |
| 	match_pos = 0;
 | |
| 	num_matches = 0;
 | |
| 	if (pattern_valid) {
 | |
| 		regfree(&pattern);
 | |
| 		pattern_valid = 0;
 | |
| 	}
 | |
| 
 | |
| 	/* Get the uncompiled regular expression from the user */
 | |
| 	clear_line();
 | |
| 	bb_putchar((option_mask32 & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/');
 | |
| 	uncomp_regex = less_gets(1);
 | |
| 	if (!uncomp_regex[0]) {
 | |
| 		free(uncomp_regex);
 | |
| 		buffer_print();
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	/* Compile the regex and check for errors */
 | |
| 	err = regcomp_or_errmsg(&pattern, uncomp_regex, 0);
 | |
| 	free(uncomp_regex);
 | |
| 	if (err) {
 | |
| 		print_statusline(err);
 | |
| 		free(err);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	pattern_valid = 1;
 | |
| 	match_pos = 0;
 | |
| 	fill_match_lines(0);
 | |
| 	while (match_pos < num_matches) {
 | |
| 		if (match_lines[match_pos] > cur_fline)
 | |
| 			break;
 | |
| 		match_pos++;
 | |
| 	}
 | |
| 	if (option_mask32 & LESS_STATE_MATCH_BACKWARDS)
 | |
| 		match_pos--;
 | |
| 
 | |
| 	/* It's possible that no matches are found yet.
 | |
| 	 * goto_match() will read input looking for match,
 | |
| 	 * if needed */
 | |
| 	goto_match(match_pos);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| static void number_process(int first_digit)
 | |
| {
 | |
| 	int i = 1;
 | |
| 	int num;
 | |
| 	char num_input[sizeof(int)*4]; /* more than enough */
 | |
| 	char keypress;
 | |
| 
 | |
| 	num_input[0] = first_digit;
 | |
| 
 | |
| 	/* Clear the current line, print a prompt, and then print the digit */
 | |
| 	clear_line();
 | |
| 	printf(":%c", first_digit);
 | |
| 
 | |
| 	/* Receive input until a letter is given */
 | |
| 	while (i < sizeof(num_input)-1) {
 | |
| 		num_input[i] = less_getch();
 | |
| 		if (!num_input[i] || !isdigit(num_input[i]))
 | |
| 			break;
 | |
| 		bb_putchar(num_input[i]);
 | |
| 		i++;
 | |
| 	}
 | |
| 
 | |
| 	/* Take the final letter out of the digits string */
 | |
| 	keypress = num_input[i];
 | |
| 	num_input[i] = '\0';
 | |
| 	num = bb_strtou(num_input, NULL, 10);
 | |
| 	/* on format error, num == -1 */
 | |
| 	if (num < 1 || num > MAXLINES) {
 | |
| 		buffer_print();
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	/* We now know the number and the letter entered, so we process them */
 | |
| 	switch (keypress) {
 | |
| 	case KEY_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015':
 | |
| 		buffer_down(num);
 | |
| 		break;
 | |
| 	case KEY_UP: case 'b': case 'w': case 'y': case 'u':
 | |
| 		buffer_up(num);
 | |
| 		break;
 | |
| 	case 'g': case '<': case 'G': case '>':
 | |
| 		cur_fline = num + max_displayed_line;
 | |
| 		read_lines();
 | |
| 		buffer_line(num - 1);
 | |
| 		break;
 | |
| 	case 'p': case '%':
 | |
| 		num = num * (max_fline / 100); /* + max_fline / 2; */
 | |
| 		cur_fline = num + max_displayed_line;
 | |
| 		read_lines();
 | |
| 		buffer_line(num);
 | |
| 		break;
 | |
| #if ENABLE_FEATURE_LESS_REGEXP
 | |
| 	case 'n':
 | |
| 		goto_match(match_pos + num);
 | |
| 		break;
 | |
| 	case '/':
 | |
| 		option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
 | |
| 		regex_process();
 | |
| 		break;
 | |
| 	case '?':
 | |
| 		option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
 | |
| 		regex_process();
 | |
| 		break;
 | |
| #endif
 | |
| 	}
 | |
| }
 | |
| 
 | |
| #if ENABLE_FEATURE_LESS_FLAGCS
 | |
| static void flag_change(void)
 | |
| {
 | |
| 	int keypress;
 | |
| 
 | |
| 	clear_line();
 | |
| 	bb_putchar('-');
 | |
| 	keypress = less_getch();
 | |
| 
 | |
| 	switch (keypress) {
 | |
| 	case 'M':
 | |
| 		option_mask32 ^= FLAG_M;
 | |
| 		break;
 | |
| 	case 'm':
 | |
| 		option_mask32 ^= FLAG_m;
 | |
| 		break;
 | |
| 	case 'E':
 | |
| 		option_mask32 ^= FLAG_E;
 | |
| 		break;
 | |
| 	case '~':
 | |
| 		option_mask32 ^= FLAG_TILDE;
 | |
| 		break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void show_flag_status(void)
 | |
| {
 | |
| 	int keypress;
 | |
| 	int flag_val;
 | |
| 
 | |
| 	clear_line();
 | |
| 	bb_putchar('_');
 | |
| 	keypress = less_getch();
 | |
| 
 | |
| 	switch (keypress) {
 | |
| 	case 'M':
 | |
| 		flag_val = option_mask32 & FLAG_M;
 | |
| 		break;
 | |
| 	case 'm':
 | |
| 		flag_val = option_mask32 & FLAG_m;
 | |
| 		break;
 | |
| 	case '~':
 | |
| 		flag_val = option_mask32 & FLAG_TILDE;
 | |
| 		break;
 | |
| 	case 'N':
 | |
| 		flag_val = option_mask32 & FLAG_N;
 | |
| 		break;
 | |
| 	case 'E':
 | |
| 		flag_val = option_mask32 & FLAG_E;
 | |
| 		break;
 | |
| 	default:
 | |
| 		flag_val = 0;
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	clear_line();
 | |
| 	printf(HIGHLIGHT"The status of the flag is: %u"NORMAL, flag_val != 0);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| static void save_input_to_file(void)
 | |
| {
 | |
| 	const char *msg = "";
 | |
| 	char *current_line;
 | |
| 	int i;
 | |
| 	FILE *fp;
 | |
| 
 | |
| 	print_statusline("Log file: ");
 | |
| 	current_line = less_gets(sizeof("Log file: ")-1);
 | |
| 	if (strlen(current_line) > 0) {
 | |
| 		fp = fopen(current_line, "w");
 | |
| 		if (!fp) {
 | |
| 			msg = "Error opening log file";
 | |
| 			goto ret;
 | |
| 		}
 | |
| 		for (i = 0; i <= max_fline; i++)
 | |
| 			fprintf(fp, "%s\n", flines[i]);
 | |
| 		fclose(fp);
 | |
| 		msg = "Done";
 | |
| 	}
 | |
|  ret:
 | |
| 	print_statusline(msg);
 | |
| 	free(current_line);
 | |
| }
 | |
| 
 | |
| #if ENABLE_FEATURE_LESS_MARKS
 | |
| static void add_mark(void)
 | |
| {
 | |
| 	int letter;
 | |
| 
 | |
| 	print_statusline("Mark: ");
 | |
| 	letter = less_getch();
 | |
| 
 | |
| 	if (isalpha(letter)) {
 | |
| 		/* 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] = cur_fline;
 | |
| 		num_marks++;
 | |
| 	} else {
 | |
| 		print_statusline("Invalid mark letter");
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void goto_mark(void)
 | |
| {
 | |
| 	int letter;
 | |
| 	int i;
 | |
| 
 | |
| 	print_statusline("Go to mark: ");
 | |
| 	letter = less_getch();
 | |
| 	clear_line();
 | |
| 
 | |
| 	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])
 | |
| 			print_statusline("Mark not set");
 | |
| 	} else
 | |
| 		print_statusline("Invalid mark letter");
 | |
| }
 | |
| #endif
 | |
| 
 | |
| #if ENABLE_FEATURE_LESS_BRACKETS
 | |
| static char opp_bracket(char bracket)
 | |
| {
 | |
| 	switch (bracket) {
 | |
| 	case '{': case '[':
 | |
| 		return bracket + 2;
 | |
| 	case '(':
 | |
| 		return ')';
 | |
| 	case '}': case ']':
 | |
| 		return bracket - 2;
 | |
| 	case ')':
 | |
| 		return '(';
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void match_right_bracket(char bracket)
 | |
| {
 | |
| 	int bracket_line = -1;
 | |
| 	int i;
 | |
| 
 | |
| 	if (strchr(flines[cur_fline], bracket) == NULL) {
 | |
| 		print_statusline("No bracket in top line");
 | |
| 		return;
 | |
| 	}
 | |
| 	for (i = cur_fline + 1; i < max_fline; i++) {
 | |
| 		if (strchr(flines[i], opp_bracket(bracket)) != NULL) {
 | |
| 			bracket_line = i;
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	if (bracket_line == -1)
 | |
| 		print_statusline("No matching bracket found");
 | |
| 	buffer_line(bracket_line - max_displayed_line);
 | |
| }
 | |
| 
 | |
| static void match_left_bracket(char bracket)
 | |
| {
 | |
| 	int bracket_line = -1;
 | |
| 	int i;
 | |
| 
 | |
| 	if (strchr(flines[cur_fline + max_displayed_line], bracket) == NULL) {
 | |
| 		print_statusline("No bracket in bottom line");
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	for (i = cur_fline + max_displayed_line; i >= 0; i--) {
 | |
| 		if (strchr(flines[i], opp_bracket(bracket)) != NULL) {
 | |
| 			bracket_line = i;
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	if (bracket_line == -1)
 | |
| 		print_statusline("No matching bracket found");
 | |
| 	buffer_line(bracket_line);
 | |
| }
 | |
| #endif  /* FEATURE_LESS_BRACKETS */
 | |
| 
 | |
| static void keypress_process(int keypress)
 | |
| {
 | |
| 	switch (keypress) {
 | |
| 	case KEY_DOWN: case 'e': case 'j': case 0x0d:
 | |
| 		buffer_down(1);
 | |
| 		break;
 | |
| 	case KEY_UP: case 'y': case 'k':
 | |
| 		buffer_up(1);
 | |
| 		break;
 | |
| 	case PAGE_DOWN: case ' ': case 'z': case 'f':
 | |
| 		buffer_down(max_displayed_line + 1);
 | |
| 		break;
 | |
| 	case PAGE_UP: case 'w': case 'b':
 | |
| 		buffer_up(max_displayed_line + 1);
 | |
| 		break;
 | |
| 	case 'd':
 | |
| 		buffer_down((max_displayed_line + 1) / 2);
 | |
| 		break;
 | |
| 	case 'u':
 | |
| 		buffer_up((max_displayed_line + 1) / 2);
 | |
| 		break;
 | |
| 	case KEY_HOME: case 'g': case 'p': case '<': case '%':
 | |
| 		buffer_line(0);
 | |
| 		break;
 | |
| 	case KEY_END: case 'G': case '>':
 | |
| 		cur_fline = MAXLINES;
 | |
| 		read_lines();
 | |
| 		buffer_line(cur_fline);
 | |
| 		break;
 | |
| 	case 'q': case 'Q':
 | |
| 		less_exit(0);
 | |
| 		break;
 | |
| #if ENABLE_FEATURE_LESS_MARKS
 | |
| 	case 'm':
 | |
| 		add_mark();
 | |
| 		buffer_print();
 | |
| 		break;
 | |
| 	case '\'':
 | |
| 		goto_mark();
 | |
| 		buffer_print();
 | |
| 		break;
 | |
| #endif
 | |
| 	case 'r': case 'R':
 | |
| 		buffer_print();
 | |
| 		break;
 | |
| 	/*case 'R':
 | |
| 		full_repaint();
 | |
| 		break;*/
 | |
| 	case 's':
 | |
| 		save_input_to_file();
 | |
| 		break;
 | |
| 	case 'E':
 | |
| 		examine_file();
 | |
| 		break;
 | |
| #if ENABLE_FEATURE_LESS_FLAGS
 | |
| 	case '=':
 | |
| 		m_status_print();
 | |
| 		break;
 | |
| #endif
 | |
| #if ENABLE_FEATURE_LESS_REGEXP
 | |
| 	case '/':
 | |
| 		option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
 | |
| 		regex_process();
 | |
| 		break;
 | |
| 	case 'n':
 | |
| 		goto_match(match_pos + 1);
 | |
| 		break;
 | |
| 	case 'N':
 | |
| 		goto_match(match_pos - 1);
 | |
| 		break;
 | |
| 	case '?':
 | |
| 		option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
 | |
| 		regex_process();
 | |
| 		break;
 | |
| #endif
 | |
| #if ENABLE_FEATURE_LESS_FLAGCS
 | |
| 	case '-':
 | |
| 		flag_change();
 | |
| 		buffer_print();
 | |
| 		break;
 | |
| 	case '_':
 | |
| 		show_flag_status();
 | |
| 		break;
 | |
| #endif
 | |
| #if ENABLE_FEATURE_LESS_BRACKETS
 | |
| 	case '{': case '(': case '[':
 | |
| 		match_right_bracket(keypress);
 | |
| 		break;
 | |
| 	case '}': case ')': case ']':
 | |
| 		match_left_bracket(keypress);
 | |
| 		break;
 | |
| #endif
 | |
| 	case ':':
 | |
| 		colon_process();
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	if (isdigit(keypress))
 | |
| 		number_process(keypress);
 | |
| }
 | |
| 
 | |
| static void sig_catcher(int sig ATTRIBUTE_UNUSED)
 | |
| {
 | |
| 	set_tty_cooked();
 | |
| 	exit(1);
 | |
| }
 | |
| 
 | |
| int less_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 | |
| int less_main(int argc, char **argv)
 | |
| {
 | |
| 	int keypress;
 | |
| 
 | |
| 	INIT_G();
 | |
| 
 | |
| 	/* TODO: -x: do not interpret backspace, -xx: tab also */
 | |
| 	/* -xxx: newline also */
 | |
| 	/* -w N: assume width N (-xxx -w 32: hex viewer of sorts) */
 | |
| 	getopt32(argv, "EMmN~");
 | |
| 	argc -= optind;
 | |
| 	argv += optind;
 | |
| 	num_files = argc;
 | |
| 	files = argv;
 | |
| 
 | |
| 	/* Another popular pager, most, detects when stdout
 | |
| 	 * is not a tty and turns into cat. This makes sense. */
 | |
| 	if (!isatty(STDOUT_FILENO))
 | |
| 		return bb_cat(argv);
 | |
| 	kbd_fd = open(CURRENT_TTY, O_RDONLY);
 | |
| 	if (kbd_fd < 0)
 | |
| 		return bb_cat(argv);
 | |
| 
 | |
| 	if (!num_files) {
 | |
| 		if (isatty(STDIN_FILENO)) {
 | |
| 			/* Just "less"? No args and no redirection? */
 | |
| 			bb_error_msg("missing filename");
 | |
| 			bb_show_usage();
 | |
| 		}
 | |
| 	} else
 | |
| 		filename = xstrdup(files[0]);
 | |
| 
 | |
| 	get_terminal_width_height(kbd_fd, &width, &max_displayed_line);
 | |
| 	/* 20: two tabstops + 4 */
 | |
| 	if (width < 20 || max_displayed_line < 3)
 | |
| 		bb_error_msg_and_die("too narrow here");
 | |
| 	max_displayed_line -= 2;
 | |
| 
 | |
| 	buffer = xmalloc((max_displayed_line+1) * sizeof(char *));
 | |
| 	if (option_mask32 & FLAG_TILDE)
 | |
| 		empty_line_marker = "";
 | |
| 
 | |
| 	tcgetattr(kbd_fd, &term_orig);
 | |
| 	signal(SIGTERM, sig_catcher);
 | |
| 	signal(SIGINT, sig_catcher);
 | |
| 	term_less = term_orig;
 | |
| 	term_less.c_lflag &= ~(ICANON | ECHO);
 | |
| 	term_less.c_iflag &= ~(IXON | ICRNL);
 | |
| 	/*term_less.c_oflag &= ~ONLCR;*/
 | |
| 	term_less.c_cc[VMIN] = 1;
 | |
| 	term_less.c_cc[VTIME] = 0;
 | |
| 
 | |
| 	/* Want to do it just once, but it doesn't work, */
 | |
| 	/* so we are redoing it (see code above). Mystery... */
 | |
| 	/*tcsetattr(kbd_fd, TCSANOW, &term_less);*/
 | |
| 
 | |
| 	reinitialize();
 | |
| 	while (1) {
 | |
| 		keypress = less_getch();
 | |
| 		keypress_process(keypress);
 | |
| 	}
 | |
| }
 |