source: MondoRescue/branches/3.3/mindi-busybox/miscutils/less.c@ 3865

Last change on this file since 3865 was 3621, checked in by Bruno Cornec, 10 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

File size: 54.1 KB
RevLine 
[821]1/* vi: set sw=4 ts=4: */
2/*
3 * Mini less implementation for busybox
4 *
5 * Copyright (C) 2005 by Rob Sullivan <cogito.ergo.cogito@gmail.com>
6 *
[2725]7 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
[821]8 */
9
10/*
[1765]11 * TODO:
12 * - Add more regular expression support - search modifiers, certain matches, etc.
13 * - Add more complex bracket searching - currently, nested brackets are
14 * not considered.
15 * - Add support for "F" as an input. This causes less to act in
16 * a similar way to tail -f.
17 * - Allow horizontal scrolling.
[821]18 *
[1765]19 * Notes:
20 * - the inp file pointer is used so that keyboard input works after
21 * redirected input has been read from stdin
22 */
[821]23
[3232]24//config:config LESS
25//config: bool "less"
26//config: default y
27//config: help
28//config: 'less' is a pager, meaning that it displays text files. It possesses
29//config: a wide array of features, and is an improvement over 'more'.
30//config:
31//config:config FEATURE_LESS_MAXLINES
32//config: int "Max number of input lines less will try to eat"
33//config: default 9999999
34//config: depends on LESS
35//config:
36//config:config FEATURE_LESS_BRACKETS
37//config: bool "Enable bracket searching"
38//config: default y
39//config: depends on LESS
40//config: help
41//config: This option adds the capability to search for matching left and right
42//config: brackets, facilitating programming.
43//config:
44//config:config FEATURE_LESS_FLAGS
45//config: bool "Enable -m/-M"
46//config: default y
47//config: depends on LESS
48//config: help
49//config: The -M/-m flag enables a more sophisticated status line.
50//config:
[3621]51//config:config FEATURE_LESS_TRUNCATE
52//config: bool "Enable -S"
53//config: default y
54//config: depends on LESS
55//config: help
56//config: The -S flag causes long lines to be truncated rather than
57//config: wrapped.
58//config:
[3232]59//config:config FEATURE_LESS_MARKS
60//config: bool "Enable marks"
61//config: default y
62//config: depends on LESS
63//config: help
64//config: Marks enable positions in a file to be stored for easy reference.
65//config:
66//config:config FEATURE_LESS_REGEXP
67//config: bool "Enable regular expressions"
68//config: default y
69//config: depends on LESS
70//config: help
71//config: Enable regular expressions, allowing complex file searches.
72//config:
73//config:config FEATURE_LESS_WINCH
74//config: bool "Enable automatic resizing on window size changes"
75//config: default y
76//config: depends on LESS
77//config: help
78//config: Makes less track window size changes.
79//config:
80//config:config FEATURE_LESS_ASK_TERMINAL
81//config: bool "Use 'tell me cursor position' ESC sequence to measure window"
82//config: default y
83//config: depends on FEATURE_LESS_WINCH
84//config: help
85//config: Makes less track window size changes.
86//config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
87//config: this option makes less perform a last-ditch effort to find it:
88//config: position cursor to 999,999 and ask terminal to report real
89//config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
90//config:
91//config: This is not clean but helps a lot on serial lines and such.
92//config:
93//config:config FEATURE_LESS_DASHCMD
94//config: bool "Enable flag changes ('-' command)"
95//config: default y
96//config: depends on LESS
97//config: help
98//config: This enables the ability to change command-line flags within
99//config: less itself ('-' keyboard command).
100//config:
101//config:config FEATURE_LESS_LINENUMS
102//config: bool "Enable dynamic switching of line numbers"
103//config: default y
104//config: depends on FEATURE_LESS_DASHCMD
105//config: help
106//config: Enables "-N" command.
107
108//usage:#define less_trivial_usage
[3621]109//usage: "[-E" IF_FEATURE_LESS_REGEXP("I")IF_FEATURE_LESS_FLAGS("Mm")
110//usage: "N" IF_FEATURE_LESS_TRUNCATE("S") "h~] [FILE]..."
[3232]111//usage:#define less_full_usage "\n\n"
112//usage: "View FILE (or stdin) one screenful at a time\n"
113//usage: "\n -E Quit once the end of a file is reached"
[3621]114//usage: IF_FEATURE_LESS_REGEXP(
115//usage: "\n -I Ignore case in all searches"
116//usage: )
[3232]117//usage: IF_FEATURE_LESS_FLAGS(
118//usage: "\n -M,-m Display status line with line numbers"
119//usage: "\n and percentage through the file"
120//usage: )
121//usage: "\n -N Prefix line number to each line"
[3621]122//usage: IF_FEATURE_LESS_TRUNCATE(
123//usage: "\n -S Truncate long lines"
124//usage: )
[3232]125//usage: "\n -~ Suppress ~s displayed past EOF"
126
[2725]127#include <sched.h> /* sched_yield() */
[821]128
[1765]129#include "libbb.h"
[3621]130#include "common_bufsiz.h"
[1765]131#if ENABLE_FEATURE_LESS_REGEXP
[821]132#include "xregex.h"
133#endif
134
135
[2725]136#define ESC "\033"
[821]137/* The escape codes for highlighted and normal text */
[2725]138#define HIGHLIGHT ESC"[7m"
139#define NORMAL ESC"[0m"
140/* The escape code to home and clear to the end of screen */
141#define CLEAR ESC"[H\033[J"
142/* The escape code to clear to the end of line */
143#define CLEAR_2_EOL ESC"[K"
[821]144
[1765]145enum {
146/* Absolute max of lines eaten */
147 MAXLINES = CONFIG_FEATURE_LESS_MAXLINES,
148/* This many "after the end" lines we will show (at max) */
149 TILDES = 1,
150};
151
[821]152/* Command line options */
[1765]153enum {
[2725]154 FLAG_E = 1 << 0,
[1765]155 FLAG_M = 1 << 1,
156 FLAG_m = 1 << 2,
157 FLAG_N = 1 << 3,
158 FLAG_TILDE = 1 << 4,
[2725]159 FLAG_I = 1 << 5,
[3621]160 FLAG_S = (1 << 6) * ENABLE_FEATURE_LESS_TRUNCATE,
[1765]161/* hijack command line options variable for internal state vars */
162 LESS_STATE_MATCH_BACKWARDS = 1 << 15,
163};
[821]164
[1765]165#if !ENABLE_FEATURE_LESS_REGEXP
166enum { pattern_valid = 0 };
[821]167#endif
168
[1765]169struct globals {
170 int cur_fline; /* signed */
171 int kbd_fd; /* fd to get input from */
[2725]172 int less_gets_pos;
[1765]173/* last position in last line, taking into account tabs */
[2725]174 size_t last_line_pos;
[1765]175 unsigned max_fline;
176 unsigned max_lineno; /* this one tracks linewrap */
[2725]177 unsigned max_displayed_line;
[1765]178 unsigned width;
[2725]179#if ENABLE_FEATURE_LESS_WINCH
180 unsigned winch_counter;
181#endif
[1765]182 ssize_t eof_error; /* eof if 0, error if < 0 */
[2725]183 ssize_t readpos;
184 ssize_t readeof; /* must be signed */
[1765]185 const char **buffer;
186 const char **flines;
187 const char *empty_line_marker;
188 unsigned num_files;
189 unsigned current_file;
190 char *filename;
191 char **files;
[3621]192#if ENABLE_FEATURE_LESS_FLAGS
193 int num_lines; /* a flag if < 0, line count if >= 0 */
194# define REOPEN_AND_COUNT (-1)
195# define REOPEN_STDIN (-2)
196# define NOT_REGULAR_FILE (-3)
197#endif
[1765]198#if ENABLE_FEATURE_LESS_MARKS
199 unsigned num_marks;
200 unsigned mark_lines[15][2];
[821]201#endif
[1765]202#if ENABLE_FEATURE_LESS_REGEXP
203 unsigned *match_lines;
204 int match_pos; /* signed! */
[2725]205 int wanted_match; /* signed! */
206 int num_matches;
[1765]207 regex_t pattern;
208 smallint pattern_valid;
209#endif
[3232]210#if ENABLE_FEATURE_LESS_ASK_TERMINAL
211 smallint winsize_err;
212#endif
[1765]213 smallint terminated;
214 struct termios term_orig, term_less;
[2725]215 char kbd_input[KEYCODE_BUFFER_SIZE];
[1765]216};
217#define G (*ptr_to_globals)
218#define cur_fline (G.cur_fline )
219#define kbd_fd (G.kbd_fd )
[2725]220#define less_gets_pos (G.less_gets_pos )
221#define last_line_pos (G.last_line_pos )
[1765]222#define max_fline (G.max_fline )
223#define max_lineno (G.max_lineno )
[2725]224#define max_displayed_line (G.max_displayed_line)
[1765]225#define width (G.width )
[2725]226#define winch_counter (G.winch_counter )
227/* This one is 100% not cached by compiler on read access */
228#define WINCH_COUNTER (*(volatile unsigned *)&winch_counter)
[1765]229#define eof_error (G.eof_error )
230#define readpos (G.readpos )
231#define readeof (G.readeof )
232#define buffer (G.buffer )
233#define flines (G.flines )
234#define empty_line_marker (G.empty_line_marker )
235#define num_files (G.num_files )
236#define current_file (G.current_file )
237#define filename (G.filename )
238#define files (G.files )
[3621]239#define num_lines (G.num_lines )
[1765]240#define num_marks (G.num_marks )
241#define mark_lines (G.mark_lines )
242#if ENABLE_FEATURE_LESS_REGEXP
243#define match_lines (G.match_lines )
244#define match_pos (G.match_pos )
245#define num_matches (G.num_matches )
[2725]246#define wanted_match (G.wanted_match )
[1765]247#define pattern (G.pattern )
248#define pattern_valid (G.pattern_valid )
249#endif
250#define terminated (G.terminated )
251#define term_orig (G.term_orig )
252#define term_less (G.term_less )
[2725]253#define kbd_input (G.kbd_input )
[1765]254#define INIT_G() do { \
[2725]255 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
256 less_gets_pos = -1; \
257 empty_line_marker = "~"; \
258 num_files = 1; \
259 current_file = 1; \
260 eof_error = 1; \
261 terminated = 1; \
262 IF_FEATURE_LESS_REGEXP(wanted_match = -1;) \
263} while (0)
[821]264
[2725]265/* flines[] are lines read from stdin, each in malloc'ed buffer.
266 * Line numbers are stored as uint32_t prepended to each line.
267 * Pointer is adjusted so that flines[i] points directly past
268 * line number. Accesor: */
269#define MEMPTR(p) ((char*)(p) - 4)
270#define LINENO(p) (*(uint32_t*)((p) - 4))
271
272
[821]273/* Reset terminal input to normal */
274static void set_tty_cooked(void)
275{
[2725]276 fflush_all();
[1765]277 tcsetattr(kbd_fd, TCSANOW, &term_orig);
[821]278}
279
280/* Move the cursor to a position (x,y), where (0,0) is the
281 top-left corner of the console */
[1765]282static void move_cursor(int line, int row)
[821]283{
[2725]284 printf(ESC"[%u;%uH", line, row);
[821]285}
286
287static void clear_line(void)
288{
[2725]289 printf(ESC"[%u;0H" CLEAR_2_EOL, max_displayed_line + 2);
[821]290}
291
[1765]292static void print_hilite(const char *str)
[821]293{
[1765]294 printf(HIGHLIGHT"%s"NORMAL, str);
295}
[821]296
[1765]297static void print_statusline(const char *str)
298{
299 clear_line();
300 printf(HIGHLIGHT"%.*s"NORMAL, width - 1, str);
[821]301}
302
[2725]303/* Exit the program gracefully */
304static void less_exit(int code)
305{
306 set_tty_cooked();
307 clear_line();
308 if (code < 0)
309 kill_myself_with_sig(- code); /* does not return */
310 exit(code);
311}
312
313#if (ENABLE_FEATURE_LESS_DASHCMD && ENABLE_FEATURE_LESS_LINENUMS) \
314 || ENABLE_FEATURE_LESS_WINCH
315static void re_wrap(void)
316{
317 int w = width;
318 int new_line_pos;
319 int src_idx;
320 int dst_idx;
321 int new_cur_fline = 0;
322 uint32_t lineno;
323 char linebuf[w + 1];
324 const char **old_flines = flines;
325 const char *s;
326 char **new_flines = NULL;
327 char *d;
328
329 if (option_mask32 & FLAG_N)
330 w -= 8;
331
332 src_idx = 0;
333 dst_idx = 0;
334 s = old_flines[0];
335 lineno = LINENO(s);
336 d = linebuf;
337 new_line_pos = 0;
338 while (1) {
339 *d = *s;
340 if (*d != '\0') {
341 new_line_pos++;
[3621]342 if (*d == '\t') { /* tab */
[2725]343 new_line_pos += 7;
[3621]344 new_line_pos &= (~7);
345 }
[2725]346 s++;
347 d++;
348 if (new_line_pos >= w) {
349 int sz;
350 /* new line is full, create next one */
351 *d = '\0';
352 next_new:
353 sz = (d - linebuf) + 1; /* + 1: NUL */
354 d = ((char*)xmalloc(sz + 4)) + 4;
355 LINENO(d) = lineno;
356 memcpy(d, linebuf, sz);
357 new_flines = xrealloc_vector(new_flines, 8, dst_idx);
358 new_flines[dst_idx] = d;
359 dst_idx++;
360 if (new_line_pos < w) {
361 /* if we came here thru "goto next_new" */
362 if (src_idx > max_fline)
363 break;
364 lineno = LINENO(s);
365 }
366 d = linebuf;
367 new_line_pos = 0;
368 }
369 continue;
370 }
371 /* *d == NUL: old line ended, go to next old one */
372 free(MEMPTR(old_flines[src_idx]));
373 /* btw, convert cur_fline... */
374 if (cur_fline == src_idx)
375 new_cur_fline = dst_idx;
376 src_idx++;
377 /* no more lines? finish last new line (and exit the loop) */
378 if (src_idx > max_fline)
379 goto next_new;
380 s = old_flines[src_idx];
381 if (lineno != LINENO(s)) {
382 /* this is not a continuation line!
383 * create next _new_ line too */
384 goto next_new;
385 }
386 }
387
388 free(old_flines);
389 flines = (const char **)new_flines;
390
391 max_fline = dst_idx - 1;
392 last_line_pos = new_line_pos;
393 cur_fline = new_cur_fline;
394 /* max_lineno is screen-size independent */
[1765]395#if ENABLE_FEATURE_LESS_REGEXP
[2725]396 pattern_valid = 0;
397#endif
398}
399#endif
400
401#if ENABLE_FEATURE_LESS_REGEXP
[1765]402static void fill_match_lines(unsigned pos);
403#else
404#define fill_match_lines(pos) ((void)0)
405#endif
406
[3621]407static int at_end(void)
408{
409 return (option_mask32 & FLAG_S)
410 ? !(cur_fline <= max_fline &&
411 max_lineno > LINENO(flines[cur_fline]) + max_displayed_line)
412 : !(max_fline > cur_fline + max_displayed_line);
413}
414
[1765]415/* Devilishly complex routine.
416 *
417 * Has to deal with EOF and EPIPE on input,
418 * with line wrapping, with last line not ending in '\n'
419 * (possibly not ending YET!), with backspace and tabs.
420 * It reads input again if last time we got an EOF (thus supporting
421 * growing files) or EPIPE (watching output of slow process like make).
422 *
423 * Variables used:
424 * flines[] - array of lines already read. Linewrap may cause
425 * one source file line to occupy several flines[n].
426 * flines[max_fline] - last line, possibly incomplete.
427 * terminated - 1 if flines[max_fline] is 'terminated'
428 * (if there was '\n' [which isn't stored itself, we just remember
429 * that it was seen])
430 * max_lineno - last line's number, this one doesn't increment
431 * on line wrap, only on "real" new lines.
432 * readbuf[0..readeof-1] - small preliminary buffer.
433 * readbuf[readpos] - next character to add to current line.
[2725]434 * last_line_pos - screen line position of next char to be read
[1765]435 * (takes into account tabs and backspaces)
436 * eof_error - < 0 error, == 0 EOF, > 0 not EOF/error
[3621]437 *
438 * "git log -p | less -m" on the kernel git tree is a good test for EAGAINs,
439 * "/search on very long input" and "reaching max line count" corner cases.
[1765]440 */
441static void read_lines(void)
[821]442{
[1765]443 char *current_line, *p;
444 int w = width;
445 char last_terminated = terminated;
[3621]446 time_t last_time = 0;
447 int retry_EAGAIN = 2;
[2725]448#if ENABLE_FEATURE_LESS_REGEXP
449 unsigned old_max_fline = max_fline;
450#endif
[821]451
[3621]452#define readbuf bb_common_bufsiz1
453 setup_common_bufsiz();
454
455 /* (careful: max_fline can be -1) */
456 if (max_fline + 1 > MAXLINES)
457 return;
458
[1765]459 if (option_mask32 & FLAG_N)
460 w -= 8;
461
[3621]462 p = current_line = ((char*)xmalloc(w + 5)) + 4;
[1765]463 if (!last_terminated) {
464 const char *cp = flines[max_fline];
[3621]465 p = stpcpy(p, cp);
466 free(MEMPTR(cp));
[2725]467 /* last_line_pos is still valid from previous read_lines() */
[1765]468 } else {
[3621]469 max_fline++;
[2725]470 last_line_pos = 0;
[821]471 }
472
[2725]473 while (1) { /* read lines until we reach cur_fline or wanted_match */
[1765]474 *p = '\0';
475 terminated = 0;
[2725]476 while (1) { /* read chars until we have a line */
[1765]477 char c;
478 /* if no unprocessed chars left, eat more */
479 if (readpos >= readeof) {
[3621]480 int flags = ndelay_on(0);
481
482 while (1) {
483 time_t t;
484
485 errno = 0;
486 eof_error = safe_read(STDIN_FILENO, readbuf, COMMON_BUFSIZE);
487 if (errno != EAGAIN)
488 break;
489 t = time(NULL);
490 if (t != last_time) {
491 last_time = t;
492 if (--retry_EAGAIN < 0)
493 break;
494 }
495 sched_yield();
496 }
497 fcntl(0, F_SETFL, flags); /* ndelay_off(0) */
[1765]498 readpos = 0;
499 readeof = eof_error;
[2725]500 if (eof_error <= 0)
[1765]501 goto reached_eof;
[3621]502 retry_EAGAIN = 1;
[1765]503 }
504 c = readbuf[readpos];
505 /* backspace? [needed for manpages] */
506 /* <tab><bs> is (a) insane and */
507 /* (b) harder to do correctly, so we refuse to do it */
[2725]508 if (c == '\x8' && last_line_pos && p[-1] != '\t') {
[1765]509 readpos++; /* eat it */
[2725]510 last_line_pos--;
[1765]511 /* was buggy (p could end up <= current_line)... */
512 *--p = '\0';
513 continue;
514 }
515 {
[2725]516 size_t new_last_line_pos = last_line_pos + 1;
[1765]517 if (c == '\t') {
[2725]518 new_last_line_pos += 7;
519 new_last_line_pos &= (~7);
[1765]520 }
[3621]521 if ((int)new_last_line_pos > w)
[1765]522 break;
[2725]523 last_line_pos = new_last_line_pos;
[1765]524 }
525 /* ok, we will eat this char */
526 readpos++;
527 if (c == '\n') {
528 terminated = 1;
[2725]529 last_line_pos = 0;
[1765]530 break;
531 }
532 /* NUL is substituted by '\n'! */
533 if (c == '\0') c = '\n';
534 *p++ = c;
535 *p = '\0';
[2725]536 } /* end of "read chars until we have a line" loop */
[3621]537#if 0
538//BUG: also triggers on this:
539// { printf "\nfoo\n"; sleep 1; printf "\nbar\n"; } | less
540// (resulting in lost empty line between "foo" and "bar" lines)
541// the "terminated" logic needs fixing (or explaining)
[1765]542 /* Corner case: linewrap with only "" wrapping to next line */
543 /* Looks ugly on screen, so we do not store this empty line */
544 if (!last_terminated && !current_line[0]) {
545 last_terminated = 1;
546 max_lineno++;
[2725]547 continue;
[1765]548 }
[3621]549#endif
[1765]550 reached_eof:
551 last_terminated = terminated;
[2725]552 flines = xrealloc_vector(flines, 8, max_fline);
553
554 flines[max_fline] = (char*)xrealloc(MEMPTR(current_line), strlen(current_line) + 1 + 4) + 4;
555 LINENO(flines[max_fline]) = max_lineno;
556 if (terminated)
557 max_lineno++;
558
[1765]559 if (max_fline >= MAXLINES) {
560 eof_error = 0; /* Pretend we saw EOF */
561 break;
562 }
[3621]563 if (!at_end()) {
[2725]564#if !ENABLE_FEATURE_LESS_REGEXP
[1765]565 break;
[2725]566#else
567 if (wanted_match >= num_matches) { /* goto_match called us */
568 fill_match_lines(old_max_fline);
569 old_max_fline = max_fline;
570 }
571 if (wanted_match < num_matches)
572 break;
573#endif
574 }
[1765]575 if (eof_error <= 0) {
576 break;
577 }
578 max_fline++;
[3621]579 current_line = ((char*)xmalloc(w + 5)) + 4;
[1765]580 p = current_line;
[2725]581 last_line_pos = 0;
582 } /* end of "read lines until we reach cur_fline" loop */
[3621]583
584 if (eof_error < 0) {
585 if (errno == EAGAIN) {
586 eof_error = 1;
587 } else {
588 print_statusline(bb_msg_read_error);
589 }
590 }
591#if ENABLE_FEATURE_LESS_FLAGS
592 else if (eof_error == 0)
593 num_lines = max_lineno;
594#endif
595
[1765]596 fill_match_lines(old_max_fline);
[2725]597#if ENABLE_FEATURE_LESS_REGEXP
598 /* prevent us from being stuck in search for a match */
599 wanted_match = -1;
600#endif
[1765]601#undef readbuf
[821]602}
603
[1765]604#if ENABLE_FEATURE_LESS_FLAGS
[3621]605static int safe_lineno(int fline)
[821]606{
[3621]607 if (fline >= max_fline)
608 fline = max_fline - 1;
609
610 /* also catches empty file (max_fline == 0) */
611 if (fline < 0)
612 return 0;
613
614 return LINENO(flines[fline]) + 1;
[821]615}
616
[3621]617/* count number of lines in file */
618static void update_num_lines(void)
619{
620 int count, fd;
621 struct stat stbuf;
622 ssize_t len, i;
623 char buf[4096];
624
625 /* only do this for regular files */
626 if (num_lines == REOPEN_AND_COUNT || num_lines == REOPEN_STDIN) {
627 count = 0;
628 fd = open("/proc/self/fd/0", O_RDONLY);
629 if (fd < 0 && num_lines == REOPEN_AND_COUNT) {
630 /* "filename" is valid only if REOPEN_AND_COUNT */
631 fd = open(filename, O_RDONLY);
632 }
633 if (fd < 0) {
634 /* somebody stole my file! */
635 num_lines = NOT_REGULAR_FILE;
636 return;
637 }
638 if (fstat(fd, &stbuf) != 0 || !S_ISREG(stbuf.st_mode)) {
639 num_lines = NOT_REGULAR_FILE;
640 goto do_close;
641 }
642 while ((len = safe_read(fd, buf, sizeof(buf))) > 0) {
643 for (i = 0; i < len; ++i) {
644 if (buf[i] == '\n' && ++count == MAXLINES)
645 goto done;
646 }
647 }
648 done:
649 num_lines = count;
650 do_close:
651 close(fd);
652 }
653}
654
[821]655/* Print a status line if -M was specified */
656static void m_status_print(void)
657{
[3621]658 int first, last;
659 unsigned percent;
[821]660
[2725]661 if (less_gets_pos >= 0) /* don't touch statusline while input is done! */
662 return;
663
[1765]664 clear_line();
665 printf(HIGHLIGHT"%s", filename);
666 if (num_files > 1)
667 printf(" (file %i of %i)", current_file, num_files);
[3621]668
669 first = safe_lineno(cur_fline);
670 last = (option_mask32 & FLAG_S)
671 ? MIN(first + max_displayed_line, max_lineno)
672 : safe_lineno(cur_fline + max_displayed_line);
673 printf(" lines %i-%i", first, last);
674
675 update_num_lines();
676 if (num_lines >= 0)
677 printf("/%i", num_lines);
678
679 if (at_end()) {
680 printf(" (END)");
[1765]681 if (num_files > 1 && current_file != num_files)
[3621]682 printf(" - next: %s", files[current_file]);
683 } else if (num_lines > 0) {
684 percent = (100 * last + num_lines/2) / num_lines;
685 printf(" %i%%", percent <= 100 ? percent : 100);
[821]686 }
[3621]687 printf(NORMAL);
[821]688}
689#endif
690
691/* Print the status line */
692static void status_print(void)
693{
[1765]694 const char *p;
695
[2725]696 if (less_gets_pos >= 0) /* don't touch statusline while input is done! */
697 return;
698
[821]699 /* Change the status if flags have been set */
[1765]700#if ENABLE_FEATURE_LESS_FLAGS
701 if (option_mask32 & (FLAG_M|FLAG_m)) {
[821]702 m_status_print();
[1765]703 return;
704 }
[821]705 /* No flags set */
706#endif
[1765]707
708 clear_line();
[3621]709 if (cur_fline && !at_end()) {
[2725]710 bb_putchar(':');
[1765]711 return;
712 }
713 p = "(END)";
714 if (!cur_fline)
715 p = filename;
716 if (num_files > 1) {
717 printf(HIGHLIGHT"%s (file %i of %i)"NORMAL,
718 p, current_file, num_files);
719 return;
720 }
721 print_hilite(p);
722}
723
724static const char controls[] ALIGN1 =
725 /* NUL: never encountered; TAB: not converted */
726 /**/"\x01\x02\x03\x04\x05\x06\x07\x08" "\x0a\x0b\x0c\x0d\x0e\x0f"
727 "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
728 "\x7f\x9b"; /* DEL and infamous Meta-ESC :( */
729static const char ctrlconv[] ALIGN1 =
[2725]730 /* why 40 instead of 4a below? - it is a replacement for '\n'.
731 * '\n' is a former NUL - we subst it with @, not J */
[1765]732 "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x40\x4b\x4c\x4d\x4e\x4f"
733 "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f";
734
[3621]735static void print_lineno(const char *line)
[2725]736{
[3621]737 const char *fmt = " ";
738 unsigned n = n; /* for compiler */
[2725]739
[3621]740 if (line != empty_line_marker) {
[2725]741 /* Width of 7 preserves tab spacing in the text */
742 fmt = "%7u ";
743 n = LINENO(line) + 1;
[3621]744 if (n > 9999999 && MAXLINES > 9999999) {
[2725]745 n %= 10000000;
746 fmt = "%07u ";
747 }
748 }
[3621]749 printf(fmt, n);
[2725]750}
751
752
[1765]753#if ENABLE_FEATURE_LESS_REGEXP
754static void print_found(const char *line)
755{
756 int match_status;
757 int eflags;
758 char *growline;
759 regmatch_t match_structs;
760
[3621]761 char buf[width+1];
[1765]762 const char *str = line;
763 char *p = buf;
764 size_t n;
765
766 while (*str) {
767 n = strcspn(str, controls);
768 if (n) {
769 if (!str[n]) break;
770 memcpy(p, str, n);
771 p += n;
772 str += n;
[821]773 }
[1765]774 n = strspn(str, controls);
775 memset(p, '.', n);
776 p += n;
777 str += n;
778 }
779 strcpy(p, str);
780
781 /* buf[] holds quarantined version of str */
782
783 /* Each part of the line that matches has the HIGHLIGHT
[3232]784 * and NORMAL escape sequences placed around it.
785 * NB: we regex against line, but insert text
786 * from quarantined copy (buf[]) */
[1765]787 str = buf;
788 growline = NULL;
789 eflags = 0;
790 goto start;
791
792 while (match_status == 0) {
793 char *new = xasprintf("%s%.*s"HIGHLIGHT"%.*s"NORMAL,
[2725]794 growline ? growline : "",
[3232]795 (int)match_structs.rm_so, str,
796 (int)(match_structs.rm_eo - match_structs.rm_so),
[1765]797 str + match_structs.rm_so);
[2725]798 free(growline);
799 growline = new;
[1765]800 str += match_structs.rm_eo;
801 line += match_structs.rm_eo;
802 eflags = REG_NOTBOL;
803 start:
804 /* Most of the time doesn't find the regex, optimize for that */
805 match_status = regexec(&pattern, line, 1, &match_structs, eflags);
[2725]806 /* if even "" matches, treat it as "not a match" */
807 if (match_structs.rm_so >= match_structs.rm_eo)
808 match_status = 1;
[1765]809 }
810
[3621]811 printf("%s%s\n", growline ? growline : "", str);
[1765]812 free(growline);
813}
814#else
815void print_found(const char *line);
816#endif
817
818static void print_ascii(const char *str)
819{
[3621]820 char buf[width+1];
[1765]821 char *p;
822 size_t n;
823
824 while (*str) {
825 n = strcspn(str, controls);
826 if (n) {
827 if (!str[n]) break;
828 printf("%.*s", (int) n, str);
829 str += n;
[821]830 }
[1765]831 n = strspn(str, controls);
832 p = buf;
833 do {
834 if (*str == 0x7f)
835 *p++ = '?';
836 else if (*str == (char)0x9b)
837 /* VT100's CSI, aka Meta-ESC. Who's inventor? */
838 /* I want to know who committed this sin */
839 *p++ = '{';
840 else
841 *p++ = ctrlconv[(unsigned char)*str];
842 str++;
843 } while (--n);
844 *p = '\0';
845 print_hilite(buf);
[821]846 }
[1765]847 puts(str);
[821]848}
849
850/* Print the buffer */
851static void buffer_print(void)
852{
[2725]853 unsigned i;
[821]854
[1765]855 move_cursor(0, 0);
[3621]856 for (i = 0; i <= max_displayed_line; i++) {
857 printf(CLEAR_2_EOL);
858 if (option_mask32 & FLAG_N)
859 print_lineno(buffer[i]);
[1765]860 if (pattern_valid)
861 print_found(buffer[i]);
862 else
863 print_ascii(buffer[i]);
[3621]864 }
865 if ((option_mask32 & FLAG_E)
866 && eof_error <= 0
867 && (max_fline - cur_fline) <= max_displayed_line
868 ) {
869 less_exit(EXIT_SUCCESS);
870 }
[821]871 status_print();
872}
873
[1765]874static void buffer_fill_and_print(void)
[821]875{
[2725]876 unsigned i;
[3621]877#if ENABLE_FEATURE_LESS_TRUNCATE
[2725]878 int fpos = cur_fline;
879
880 if (option_mask32 & FLAG_S) {
881 /* Go back to the beginning of this line */
882 while (fpos && LINENO(flines[fpos]) == LINENO(flines[fpos-1]))
883 fpos--;
884 }
885
886 i = 0;
887 while (i <= max_displayed_line && fpos <= max_fline) {
888 int lineno = LINENO(flines[fpos]);
889 buffer[i] = flines[fpos];
890 i++;
891 do {
892 fpos++;
893 } while ((fpos <= max_fline)
894 && (option_mask32 & FLAG_S)
895 && lineno == LINENO(flines[fpos])
896 );
897 }
898#else
[1765]899 for (i = 0; i <= max_displayed_line && cur_fline + i <= max_fline; i++) {
900 buffer[i] = flines[cur_fline + i];
[821]901 }
[2725]902#endif
[1765]903 for (; i <= max_displayed_line; i++) {
904 buffer[i] = empty_line_marker;
[821]905 }
[1765]906 buffer_print();
[821]907}
908
[3621]909/* move cur_fline to a given line number, reading lines if necessary */
910static void goto_lineno(int target)
911{
912 if (target <= 0 ) {
913 cur_fline = 0;
914 }
915 else if (target > LINENO(flines[cur_fline])) {
916 retry:
917 while (LINENO(flines[cur_fline]) != target && cur_fline < max_fline)
918 ++cur_fline;
919 /* target not reached but more input is available */
920 if (LINENO(flines[cur_fline]) != target && eof_error > 0) {
921 read_lines();
922 goto retry;
923 }
924 }
925 else {
926 /* search backwards through already-read lines */
927 while (LINENO(flines[cur_fline]) != target && cur_fline > 0)
928 --cur_fline;
929 }
930}
931
932static void cap_cur_fline(void)
933{
934 if ((option_mask32 & FLAG_S)) {
935 if (cur_fline > max_fline)
936 cur_fline = max_fline;
937 if (LINENO(flines[cur_fline]) + max_displayed_line > max_lineno + TILDES) {
938 goto_lineno(max_lineno - max_displayed_line + TILDES);
939 read_lines();
940 }
941 }
942 else {
943 if (cur_fline + max_displayed_line > max_fline + TILDES)
944 cur_fline = max_fline - max_displayed_line + TILDES;
945 if (cur_fline < 0)
946 cur_fline = 0;
947 }
948}
949
[821]950/* Move the buffer up and down in the file in order to scroll */
951static void buffer_down(int nlines)
952{
[3621]953 if ((option_mask32 & FLAG_S))
954 goto_lineno(LINENO(flines[cur_fline]) + nlines);
955 else
956 cur_fline += nlines;
[1765]957 read_lines();
[3621]958 cap_cur_fline();
[1765]959 buffer_fill_and_print();
960}
[821]961
[1765]962static void buffer_up(int nlines)
963{
[3621]964 if ((option_mask32 & FLAG_S)) {
965 goto_lineno(LINENO(flines[cur_fline]) - nlines);
966 }
967 else {
968 cur_fline -= nlines;
969 if (cur_fline < 0)
970 cur_fline = 0;
971 }
[1765]972 read_lines();
973 buffer_fill_and_print();
974}
[821]975
[3621]976/* display a given line where the argument can be either an index into
977 * the flines array or a line number */
978static void buffer_to_line(int linenum, int is_lineno)
[1765]979{
[3621]980 if (linenum <= 0)
981 cur_fline = 0;
982 else if (is_lineno)
983 goto_lineno(linenum);
984 else
985 cur_fline = linenum;
[1765]986 read_lines();
[3621]987 cap_cur_fline();
[1765]988 buffer_fill_and_print();
989}
990
[3621]991static void buffer_line(int linenum)
992{
993 buffer_to_line(linenum, FALSE);
994}
995
996static void buffer_lineno(int lineno)
997{
998 buffer_to_line(lineno, TRUE);
999}
1000
[1765]1001static void open_file_and_read_lines(void)
1002{
1003 if (filename) {
[2725]1004 xmove_fd(xopen(filename, O_RDONLY), STDIN_FILENO);
[3621]1005#if ENABLE_FEATURE_LESS_FLAGS
1006 num_lines = REOPEN_AND_COUNT;
1007#endif
[1765]1008 } else {
1009 /* "less" with no arguments in argv[] */
1010 /* For status line only */
1011 filename = xstrdup(bb_msg_standard_input);
[3621]1012#if ENABLE_FEATURE_LESS_FLAGS
1013 num_lines = REOPEN_STDIN;
1014#endif
[821]1015 }
[1765]1016 readpos = 0;
1017 readeof = 0;
[2725]1018 last_line_pos = 0;
[1765]1019 terminated = 1;
1020 read_lines();
[821]1021}
1022
[1765]1023/* Reinitialize everything for a new file - free the memory and start over */
1024static void reinitialize(void)
[821]1025{
[2725]1026 unsigned i;
[821]1027
[1765]1028 if (flines) {
1029 for (i = 0; i <= max_fline; i++)
[2725]1030 free(MEMPTR(flines[i]));
[1765]1031 free(flines);
1032 flines = NULL;
[821]1033 }
1034
[1765]1035 max_fline = -1;
1036 cur_fline = 0;
1037 max_lineno = 0;
1038 open_file_and_read_lines();
[3232]1039#if ENABLE_FEATURE_LESS_ASK_TERMINAL
1040 if (G.winsize_err)
1041 printf("\033[999;999H" "\033[6n");
1042#endif
[1765]1043 buffer_fill_and_print();
[821]1044}
1045
[3232]1046static int64_t getch_nowait(void)
[821]1047{
[2725]1048 int rd;
[3232]1049 int64_t key64;
[2725]1050 struct pollfd pfd[2];
1051
1052 pfd[0].fd = STDIN_FILENO;
1053 pfd[0].events = POLLIN;
1054 pfd[1].fd = kbd_fd;
1055 pfd[1].events = POLLIN;
[1765]1056 again:
[2725]1057 tcsetattr(kbd_fd, TCSANOW, &term_less);
1058 /* NB: select/poll returns whenever read will not block. Therefore:
1059 * if eof is reached, select/poll will return immediately
1060 * because read will immediately return 0 bytes.
1061 * Even if select/poll says that input is available, read CAN block
[1765]1062 * (switch fd into O_NONBLOCK'ed mode to avoid it)
1063 */
[2725]1064 rd = 1;
1065 /* Are we interested in stdin? */
[3621]1066 if (at_end()) {
[2725]1067 if (eof_error > 0) /* did NOT reach eof yet */
1068 rd = 0; /* yes, we are interested in stdin */
[821]1069 }
[2725]1070 /* Position cursor if line input is done */
1071 if (less_gets_pos >= 0)
1072 move_cursor(max_displayed_line + 2, less_gets_pos + 1);
1073 fflush_all();
[1765]1074
[2725]1075 if (kbd_input[0] == 0) { /* if nothing is buffered */
1076#if ENABLE_FEATURE_LESS_WINCH
1077 while (1) {
1078 int r;
1079 /* NB: SIGWINCH interrupts poll() */
1080 r = poll(pfd + rd, 2 - rd, -1);
1081 if (/*r < 0 && errno == EINTR &&*/ winch_counter)
1082 return '\\'; /* anything which has no defined function */
1083 if (r) break;
1084 }
1085#else
1086 safe_poll(pfd + rd, 2 - rd, -1);
1087#endif
[821]1088 }
[2725]1089
1090 /* We have kbd_fd in O_NONBLOCK mode, read inside read_key()
1091 * would not block even if there is no input available */
[3232]1092 key64 = read_key(kbd_fd, kbd_input, /*timeout off:*/ -2);
1093 if ((int)key64 == -1) {
[2725]1094 if (errno == EAGAIN) {
1095 /* No keyboard input available. Since poll() did return,
1096 * we should have input on stdin */
1097 read_lines();
1098 buffer_fill_and_print();
1099 goto again;
1100 }
1101 /* EOF/error (ssh session got killed etc) */
1102 less_exit(0);
1103 }
1104 set_tty_cooked();
[3232]1105 return key64;
[1765]1106}
1107
[2725]1108/* Grab a character from input without requiring the return key.
1109 * May return KEYCODE_xxx values.
1110 * Note that this function works best with raw input. */
[3232]1111static int64_t less_getch(int pos)
[1765]1112{
[3232]1113 int64_t key64;
1114 int key;
[2725]1115
[1765]1116 again:
[2725]1117 less_gets_pos = pos;
[3232]1118 key = key64 = getch_nowait();
[2725]1119 less_gets_pos = -1;
[1765]1120
[3232]1121 /* Discard Ctrl-something chars.
1122 * (checking only lower 32 bits is a size optimization:
1123 * upper 32 bits are used only by KEYCODE_CURSOR_POS)
1124 */
1125 if (key >= 0 && key < ' ' && key != 0x0d && key != 8)
[2725]1126 goto again;
[3232]1127
1128 return key64;
[821]1129}
1130
[1765]1131static char* less_gets(int sz)
[821]1132{
[2725]1133 int c;
1134 unsigned i = 0;
[1765]1135 char *result = xzalloc(1);
[2725]1136
[1765]1137 while (1) {
1138 c = '\0';
[2725]1139 less_gets_pos = sz + i;
1140 c = getch_nowait();
1141 if (c == 0x0d) {
1142 result[i] = '\0';
1143 less_gets_pos = -1;
[1765]1144 return result;
[2725]1145 }
[1765]1146 if (c == 0x7f)
1147 c = 8;
1148 if (c == 8 && i) {
1149 printf("\x8 \x8");
1150 i--;
1151 }
[2725]1152 if (c < ' ') /* filters out KEYCODE_xxx too (<0) */
[1765]1153 continue;
1154 if (i >= width - sz - 1)
1155 continue; /* len limit */
[2725]1156 bb_putchar(c);
[1765]1157 result[i++] = c;
1158 result = xrealloc(result, i+1);
1159 }
[821]1160}
1161
1162static void examine_file(void)
1163{
[2725]1164 char *new_fname;
1165
[1765]1166 print_statusline("Examine: ");
[2725]1167 new_fname = less_gets(sizeof("Examine: ") - 1);
1168 if (!new_fname[0]) {
1169 status_print();
1170 err:
1171 free(new_fname);
1172 return;
1173 }
1174 if (access(new_fname, R_OK) != 0) {
1175 print_statusline("Cannot read this file");
1176 goto err;
1177 }
[1765]1178 free(filename);
[2725]1179 filename = new_fname;
[1765]1180 /* files start by = argv. why we assume that argv is infinitely long??
1181 files[num_files] = filename;
[821]1182 current_file = num_files + 1;
[1765]1183 num_files++; */
1184 files[0] = filename;
1185 num_files = current_file = 1;
1186 reinitialize();
[821]1187}
1188
1189/* This function changes the file currently being paged. direction can be one of the following:
1190 * -1: go back one file
1191 * 0: go to the first file
[1765]1192 * 1: go forward one file */
[821]1193static void change_file(int direction)
1194{
1195 if (current_file != ((direction > 0) ? num_files : 1)) {
1196 current_file = direction ? current_file + direction : 1;
[1765]1197 free(filename);
1198 filename = xstrdup(files[current_file - 1]);
1199 reinitialize();
1200 } else {
1201 print_statusline(direction > 0 ? "No next file" : "No previous file");
[821]1202 }
1203}
1204
1205static void remove_current_file(void)
1206{
[2725]1207 unsigned i;
[821]1208
[1765]1209 if (num_files < 2)
1210 return;
1211
[821]1212 if (current_file != 1) {
1213 change_file(-1);
1214 for (i = 3; i <= num_files; i++)
1215 files[i - 2] = files[i - 1];
1216 num_files--;
[1765]1217 } else {
[821]1218 change_file(1);
1219 for (i = 2; i <= num_files; i++)
1220 files[i - 2] = files[i - 1];
1221 num_files--;
1222 current_file--;
1223 }
1224}
1225
1226static void colon_process(void)
1227{
1228 int keypress;
1229
1230 /* Clear the current line and print a prompt */
[1765]1231 print_statusline(" :");
[821]1232
[2725]1233 keypress = less_getch(2);
[821]1234 switch (keypress) {
[1765]1235 case 'd':
1236 remove_current_file();
1237 break;
1238 case 'e':
1239 examine_file();
1240 break;
1241#if ENABLE_FEATURE_LESS_FLAGS
1242 case 'f':
1243 m_status_print();
1244 break;
[821]1245#endif
[1765]1246 case 'n':
1247 change_file(1);
1248 break;
1249 case 'p':
1250 change_file(-1);
1251 break;
1252 case 'q':
[2725]1253 less_exit(EXIT_SUCCESS);
[1765]1254 break;
1255 case 'x':
1256 change_file(0);
1257 break;
[821]1258 }
1259}
1260
[1765]1261#if ENABLE_FEATURE_LESS_REGEXP
1262static void normalize_match_pos(int match)
1263{
1264 if (match >= num_matches)
1265 match = num_matches - 1;
1266 if (match < 0)
1267 match = 0;
1268 match_pos = match;
1269}
[821]1270
[1765]1271static void goto_match(int match)
[821]1272{
[1765]1273 if (!pattern_valid)
1274 return;
1275 if (match < 0)
1276 match = 0;
1277 /* Try to find next match if eof isn't reached yet */
1278 if (match >= num_matches && eof_error > 0) {
[2725]1279 wanted_match = match; /* "I want to read until I see N'th match" */
[1765]1280 read_lines();
[821]1281 }
[1765]1282 if (num_matches) {
1283 normalize_match_pos(match);
1284 buffer_line(match_lines[match_pos]);
1285 } else {
1286 print_statusline("No matches found");
1287 }
[821]1288}
1289
[1765]1290static void fill_match_lines(unsigned pos)
[821]1291{
[1765]1292 if (!pattern_valid)
1293 return;
1294 /* Run the regex on each line of the current file */
1295 while (pos <= max_fline) {
1296 /* If this line matches */
1297 if (regexec(&pattern, flines[pos], 0, NULL, 0) == 0
1298 /* and we didn't match it last time */
1299 && !(num_matches && match_lines[num_matches-1] == pos)
1300 ) {
[2725]1301 match_lines = xrealloc_vector(match_lines, 4, num_matches);
[1765]1302 match_lines[num_matches++] = pos;
1303 }
1304 pos++;
[821]1305 }
1306}
1307
1308static void regex_process(void)
1309{
[1765]1310 char *uncomp_regex, *err;
1311
1312 /* Reset variables */
1313 free(match_lines);
1314 match_lines = NULL;
1315 match_pos = 0;
1316 num_matches = 0;
1317 if (pattern_valid) {
1318 regfree(&pattern);
1319 pattern_valid = 0;
1320 }
1321
[821]1322 /* Get the uncompiled regular expression from the user */
1323 clear_line();
[2725]1324 bb_putchar((option_mask32 & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/');
[1765]1325 uncomp_regex = less_gets(1);
1326 if (!uncomp_regex[0]) {
1327 free(uncomp_regex);
1328 buffer_print();
[821]1329 return;
1330 }
[1765]1331
[821]1332 /* Compile the regex and check for errors */
[2725]1333 err = regcomp_or_errmsg(&pattern, uncomp_regex,
1334 (option_mask32 & FLAG_I) ? REG_ICASE : 0);
[1765]1335 free(uncomp_regex);
1336 if (err) {
1337 print_statusline(err);
1338 free(err);
1339 return;
1340 }
[821]1341
[1765]1342 pattern_valid = 1;
[821]1343 match_pos = 0;
[1765]1344 fill_match_lines(0);
1345 while (match_pos < num_matches) {
[2725]1346 if ((int)match_lines[match_pos] > cur_fline)
[1765]1347 break;
1348 match_pos++;
[821]1349 }
[1765]1350 if (option_mask32 & LESS_STATE_MATCH_BACKWARDS)
1351 match_pos--;
1352
1353 /* It's possible that no matches are found yet.
1354 * goto_match() will read input looking for match,
1355 * if needed */
1356 goto_match(match_pos);
[821]1357}
1358#endif
1359
1360static void number_process(int first_digit)
1361{
[2725]1362 unsigned i;
[821]1363 int num;
[2725]1364 int keypress;
[1765]1365 char num_input[sizeof(int)*4]; /* more than enough */
[821]1366
1367 num_input[0] = first_digit;
1368
1369 /* Clear the current line, print a prompt, and then print the digit */
1370 clear_line();
1371 printf(":%c", first_digit);
1372
[1765]1373 /* Receive input until a letter is given */
[2725]1374 i = 1;
[1765]1375 while (i < sizeof(num_input)-1) {
[2725]1376 keypress = less_getch(i + 1);
[3621]1377 if ((unsigned)keypress > 255 || !isdigit(keypress))
[1765]1378 break;
[2725]1379 num_input[i] = keypress;
1380 bb_putchar(keypress);
[821]1381 i++;
1382 }
1383
1384 num_input[i] = '\0';
[1765]1385 num = bb_strtou(num_input, NULL, 10);
1386 /* on format error, num == -1 */
1387 if (num < 1 || num > MAXLINES) {
[821]1388 buffer_print();
1389 return;
1390 }
1391
1392 /* We now know the number and the letter entered, so we process them */
1393 switch (keypress) {
[2725]1394 case KEYCODE_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015':
[1765]1395 buffer_down(num);
1396 break;
[2725]1397 case KEYCODE_UP: case 'b': case 'w': case 'y': case 'u':
[1765]1398 buffer_up(num);
1399 break;
1400 case 'g': case '<': case 'G': case '>':
[3621]1401 buffer_lineno(num - 1);
[1765]1402 break;
1403 case 'p': case '%':
[3621]1404#if ENABLE_FEATURE_LESS_FLAGS
1405 update_num_lines();
1406 num = num * (num_lines > 0 ? num_lines : max_lineno) / 100;
1407#else
1408 num = num * max_lineno / 100;
1409#endif
1410 buffer_lineno(num);
[1765]1411 break;
1412#if ENABLE_FEATURE_LESS_REGEXP
1413 case 'n':
1414 goto_match(match_pos + num);
1415 break;
1416 case '/':
1417 option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
1418 regex_process();
1419 break;
1420 case '?':
1421 option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
1422 regex_process();
1423 break;
[821]1424#endif
1425 }
1426}
1427
[2725]1428#if ENABLE_FEATURE_LESS_DASHCMD
[821]1429static void flag_change(void)
1430{
1431 int keypress;
1432
1433 clear_line();
[2725]1434 bb_putchar('-');
1435 keypress = less_getch(1);
[821]1436
1437 switch (keypress) {
[1765]1438 case 'M':
1439 option_mask32 ^= FLAG_M;
1440 break;
1441 case 'm':
1442 option_mask32 ^= FLAG_m;
1443 break;
1444 case 'E':
1445 option_mask32 ^= FLAG_E;
1446 break;
1447 case '~':
1448 option_mask32 ^= FLAG_TILDE;
1449 break;
[3621]1450#if ENABLE_FEATURE_LESS_TRUNCATE
[2725]1451 case 'S':
1452 option_mask32 ^= FLAG_S;
1453 buffer_fill_and_print();
1454 break;
[3621]1455#endif
[2725]1456#if ENABLE_FEATURE_LESS_LINENUMS
1457 case 'N':
1458 option_mask32 ^= FLAG_N;
1459 re_wrap();
1460 buffer_fill_and_print();
1461 break;
1462#endif
[821]1463 }
1464}
1465
[2725]1466#ifdef BLOAT
[821]1467static void show_flag_status(void)
1468{
1469 int keypress;
1470 int flag_val;
1471
1472 clear_line();
[2725]1473 bb_putchar('_');
1474 keypress = less_getch(1);
[821]1475
1476 switch (keypress) {
[1765]1477 case 'M':
1478 flag_val = option_mask32 & FLAG_M;
1479 break;
1480 case 'm':
1481 flag_val = option_mask32 & FLAG_m;
1482 break;
1483 case '~':
1484 flag_val = option_mask32 & FLAG_TILDE;
1485 break;
1486 case 'N':
1487 flag_val = option_mask32 & FLAG_N;
1488 break;
1489 case 'E':
1490 flag_val = option_mask32 & FLAG_E;
1491 break;
1492 default:
1493 flag_val = 0;
1494 break;
[821]1495 }
1496
1497 clear_line();
[1765]1498 printf(HIGHLIGHT"The status of the flag is: %u"NORMAL, flag_val != 0);
[821]1499}
1500#endif
1501
[2725]1502#endif /* ENABLE_FEATURE_LESS_DASHCMD */
1503
[821]1504static void save_input_to_file(void)
1505{
[1765]1506 const char *msg = "";
1507 char *current_line;
[2725]1508 unsigned i;
[821]1509 FILE *fp;
1510
[1765]1511 print_statusline("Log file: ");
1512 current_line = less_gets(sizeof("Log file: ")-1);
[2725]1513 if (current_line[0]) {
1514 fp = fopen_for_write(current_line);
[1765]1515 if (!fp) {
1516 msg = "Error opening log file";
1517 goto ret;
1518 }
1519 for (i = 0; i <= max_fline; i++)
1520 fprintf(fp, "%s\n", flines[i]);
[821]1521 fclose(fp);
[1765]1522 msg = "Done";
[821]1523 }
[1765]1524 ret:
1525 print_statusline(msg);
1526 free(current_line);
[821]1527}
1528
[1765]1529#if ENABLE_FEATURE_LESS_MARKS
[821]1530static void add_mark(void)
1531{
1532 int letter;
1533
[1765]1534 print_statusline("Mark: ");
[2725]1535 letter = less_getch(sizeof("Mark: ") - 1);
[821]1536
1537 if (isalpha(letter)) {
1538 /* If we exceed 15 marks, start overwriting previous ones */
1539 if (num_marks == 14)
1540 num_marks = 0;
1541
1542 mark_lines[num_marks][0] = letter;
[1765]1543 mark_lines[num_marks][1] = cur_fline;
[821]1544 num_marks++;
[1765]1545 } else {
1546 print_statusline("Invalid mark letter");
[821]1547 }
1548}
1549
1550static void goto_mark(void)
1551{
1552 int letter;
1553 int i;
1554
[1765]1555 print_statusline("Go to mark: ");
[2725]1556 letter = less_getch(sizeof("Go to mark: ") - 1);
[821]1557 clear_line();
1558
1559 if (isalpha(letter)) {
1560 for (i = 0; i <= num_marks; i++)
1561 if (letter == mark_lines[i][0]) {
1562 buffer_line(mark_lines[i][1]);
1563 break;
1564 }
[1765]1565 if (num_marks == 14 && letter != mark_lines[14][0])
1566 print_statusline("Mark not set");
1567 } else
1568 print_statusline("Invalid mark letter");
[821]1569}
1570#endif
1571
[1765]1572#if ENABLE_FEATURE_LESS_BRACKETS
[821]1573static char opp_bracket(char bracket)
1574{
1575 switch (bracket) {
[2725]1576 case '{': case '[': /* '}' == '{' + 2. Same for '[' */
1577 bracket++;
1578 case '(': /* ')' == '(' + 1 */
1579 bracket++;
1580 break;
1581 case '}': case ']':
1582 bracket--;
1583 case ')':
1584 bracket--;
1585 break;
1586 };
1587 return bracket;
[821]1588}
1589
1590static void match_right_bracket(char bracket)
1591{
[2725]1592 unsigned i;
[821]1593
[1765]1594 if (strchr(flines[cur_fline], bracket) == NULL) {
1595 print_statusline("No bracket in top line");
1596 return;
1597 }
[2725]1598 bracket = opp_bracket(bracket);
[1765]1599 for (i = cur_fline + 1; i < max_fline; i++) {
[2725]1600 if (strchr(flines[i], bracket) != NULL) {
1601 buffer_line(i);
1602 return;
[821]1603 }
1604 }
[2725]1605 print_statusline("No matching bracket found");
[821]1606}
1607
1608static void match_left_bracket(char bracket)
1609{
1610 int i;
1611
[1765]1612 if (strchr(flines[cur_fline + max_displayed_line], bracket) == NULL) {
1613 print_statusline("No bracket in bottom line");
1614 return;
1615 }
[821]1616
[2725]1617 bracket = opp_bracket(bracket);
[1765]1618 for (i = cur_fline + max_displayed_line; i >= 0; i--) {
[2725]1619 if (strchr(flines[i], bracket) != NULL) {
1620 buffer_line(i);
1621 return;
[821]1622 }
1623 }
[2725]1624 print_statusline("No matching bracket found");
[821]1625}
[1765]1626#endif /* FEATURE_LESS_BRACKETS */
[821]1627
1628static void keypress_process(int keypress)
1629{
1630 switch (keypress) {
[2725]1631 case KEYCODE_DOWN: case 'e': case 'j': case 0x0d:
[1765]1632 buffer_down(1);
1633 break;
[2725]1634 case KEYCODE_UP: case 'y': case 'k':
[1765]1635 buffer_up(1);
1636 break;
[2725]1637 case KEYCODE_PAGEDOWN: case ' ': case 'z': case 'f':
[1765]1638 buffer_down(max_displayed_line + 1);
1639 break;
[2725]1640 case KEYCODE_PAGEUP: case 'w': case 'b':
[1765]1641 buffer_up(max_displayed_line + 1);
1642 break;
1643 case 'd':
1644 buffer_down((max_displayed_line + 1) / 2);
1645 break;
1646 case 'u':
1647 buffer_up((max_displayed_line + 1) / 2);
1648 break;
[2725]1649 case KEYCODE_HOME: case 'g': case 'p': case '<': case '%':
[1765]1650 buffer_line(0);
1651 break;
[2725]1652 case KEYCODE_END: case 'G': case '>':
[1765]1653 cur_fline = MAXLINES;
1654 read_lines();
1655 buffer_line(cur_fline);
1656 break;
1657 case 'q': case 'Q':
[2725]1658 less_exit(EXIT_SUCCESS);
[1765]1659 break;
1660#if ENABLE_FEATURE_LESS_MARKS
1661 case 'm':
1662 add_mark();
1663 buffer_print();
1664 break;
1665 case '\'':
1666 goto_mark();
1667 buffer_print();
1668 break;
[821]1669#endif
[1765]1670 case 'r': case 'R':
[3232]1671 /* TODO: (1) also bind ^R, ^L to this?
1672 * (2) re-measure window size?
1673 */
[1765]1674 buffer_print();
1675 break;
1676 /*case 'R':
1677 full_repaint();
1678 break;*/
1679 case 's':
1680 save_input_to_file();
1681 break;
1682 case 'E':
1683 examine_file();
1684 break;
1685#if ENABLE_FEATURE_LESS_FLAGS
1686 case '=':
1687 m_status_print();
1688 break;
[821]1689#endif
[1765]1690#if ENABLE_FEATURE_LESS_REGEXP
1691 case '/':
1692 option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
1693 regex_process();
1694 break;
1695 case 'n':
1696 goto_match(match_pos + 1);
1697 break;
1698 case 'N':
1699 goto_match(match_pos - 1);
1700 break;
1701 case '?':
1702 option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
1703 regex_process();
1704 break;
[821]1705#endif
[2725]1706#if ENABLE_FEATURE_LESS_DASHCMD
[1765]1707 case '-':
1708 flag_change();
1709 buffer_print();
1710 break;
[2725]1711#ifdef BLOAT
[1765]1712 case '_':
1713 show_flag_status();
1714 break;
[821]1715#endif
[2725]1716#endif
[1765]1717#if ENABLE_FEATURE_LESS_BRACKETS
1718 case '{': case '(': case '[':
1719 match_right_bracket(keypress);
1720 break;
1721 case '}': case ')': case ']':
1722 match_left_bracket(keypress);
1723 break;
[821]1724#endif
[1765]1725 case ':':
1726 colon_process();
1727 break;
[821]1728 }
1729
1730 if (isdigit(keypress))
1731 number_process(keypress);
1732}
1733
[2725]1734static void sig_catcher(int sig)
[1765]1735{
[2725]1736 less_exit(- sig);
[1765]1737}
[821]1738
[2725]1739#if ENABLE_FEATURE_LESS_WINCH
1740static void sigwinch_handler(int sig UNUSED_PARAM)
1741{
1742 winch_counter++;
1743}
1744#endif
1745
1746int less_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
[1765]1747int less_main(int argc, char **argv)
1748{
[3621]1749 char *tty_name;
1750 int tty_fd;
1751
[1765]1752 INIT_G();
[821]1753
[3621]1754 /* TODO: -x: do not interpret backspace, -xx: tab also
1755 * -xxx: newline also
1756 * -w N: assume width N (-xxx -w 32: hex viewer of sorts)
1757 * -s: condense many empty lines to one
1758 * (used by some setups for manpage display)
1759 */
1760 getopt32(argv, "EMmN~I" IF_FEATURE_LESS_TRUNCATE("S") /*ignored:*/"s");
[821]1761 argc -= optind;
1762 argv += optind;
[1765]1763 num_files = argc;
[821]1764 files = argv;
1765
[1765]1766 /* Another popular pager, most, detects when stdout
1767 * is not a tty and turns into cat. This makes sense. */
1768 if (!isatty(STDOUT_FILENO))
1769 return bb_cat(argv);
1770
[821]1771 if (!num_files) {
[1765]1772 if (isatty(STDIN_FILENO)) {
1773 /* Just "less"? No args and no redirection? */
1774 bb_error_msg("missing filename");
[821]1775 bb_show_usage();
1776 }
[2725]1777 } else {
[1765]1778 filename = xstrdup(files[0]);
[2725]1779 }
[821]1780
[1765]1781 if (option_mask32 & FLAG_TILDE)
1782 empty_line_marker = "";
1783
[3621]1784 /* Some versions of less can survive w/o controlling tty,
1785 * try to do the same. This also allows to specify an alternative
1786 * tty via "less 1<>TTY".
1787 * We don't try to use STDOUT_FILENO directly,
1788 * since we want to set this fd to non-blocking mode,
1789 * and not bother with restoring it on exit.
1790 */
1791 tty_name = xmalloc_ttyname(STDOUT_FILENO);
1792 if (tty_name) {
1793 tty_fd = open(tty_name, O_RDONLY);
1794 free(tty_name);
1795 if (tty_fd < 0)
1796 goto try_ctty;
1797 } else {
1798 /* Try controlling tty */
1799 try_ctty:
1800 tty_fd = open(CURRENT_TTY, O_RDONLY);
1801 if (tty_fd < 0)
1802 return bb_cat(argv);
1803 }
1804 ndelay_on(tty_fd);
1805 kbd_fd = tty_fd; /* save in a global */
[2725]1806
[1765]1807 tcgetattr(kbd_fd, &term_orig);
1808 term_less = term_orig;
1809 term_less.c_lflag &= ~(ICANON | ECHO);
1810 term_less.c_iflag &= ~(IXON | ICRNL);
1811 /*term_less.c_oflag &= ~ONLCR;*/
1812 term_less.c_cc[VMIN] = 1;
1813 term_less.c_cc[VTIME] = 0;
1814
[3232]1815 IF_FEATURE_LESS_ASK_TERMINAL(G.winsize_err =) get_terminal_width_height(kbd_fd, &width, &max_displayed_line);
[2725]1816 /* 20: two tabstops + 4 */
1817 if (width < 20 || max_displayed_line < 3)
1818 return bb_cat(argv);
1819 max_displayed_line -= 2;
[1765]1820
[2725]1821 /* We want to restore term_orig on exit */
1822 bb_signals(BB_FATAL_SIGS, sig_catcher);
1823#if ENABLE_FEATURE_LESS_WINCH
1824 signal(SIGWINCH, sigwinch_handler);
1825#endif
1826
1827 buffer = xmalloc((max_displayed_line+1) * sizeof(char *));
[1765]1828 reinitialize();
[821]1829 while (1) {
[3232]1830 int64_t keypress;
1831
[2725]1832#if ENABLE_FEATURE_LESS_WINCH
1833 while (WINCH_COUNTER) {
1834 again:
1835 winch_counter--;
[3232]1836 IF_FEATURE_LESS_ASK_TERMINAL(G.winsize_err =) get_terminal_width_height(kbd_fd, &width, &max_displayed_line);
1837 IF_FEATURE_LESS_ASK_TERMINAL(got_size:)
[2725]1838 /* 20: two tabstops + 4 */
1839 if (width < 20)
1840 width = 20;
1841 if (max_displayed_line < 3)
1842 max_displayed_line = 3;
1843 max_displayed_line -= 2;
1844 free(buffer);
1845 buffer = xmalloc((max_displayed_line+1) * sizeof(char *));
1846 /* Avoid re-wrap and/or redraw if we already know
1847 * we need to do it again. These ops are expensive */
1848 if (WINCH_COUNTER)
1849 goto again;
1850 re_wrap();
1851 if (WINCH_COUNTER)
1852 goto again;
1853 buffer_fill_and_print();
1854 /* This took some time. Loop back and check,
1855 * were there another SIGWINCH? */
1856 }
[3232]1857 keypress = less_getch(-1); /* -1: do not position cursor */
1858# if ENABLE_FEATURE_LESS_ASK_TERMINAL
1859 if ((int32_t)keypress == KEYCODE_CURSOR_POS) {
1860 uint32_t rc = (keypress >> 32);
1861 width = (rc & 0x7fff);
1862 max_displayed_line = ((rc >> 16) & 0x7fff);
1863 goto got_size;
1864 }
1865# endif
1866#else
1867 keypress = less_getch(-1); /* -1: do not position cursor */
[2725]1868#endif
[821]1869 keypress_process(keypress);
1870 }
1871}
[2725]1872
1873/*
1874Help text of less version 418 is below.
1875If you are implementing something, keeping
1876key and/or command line switch compatibility is a good idea:
1877
1878
1879 SUMMARY OF LESS COMMANDS
1880
1881 Commands marked with * may be preceded by a number, N.
1882 Notes in parentheses indicate the behavior if N is given.
1883 h H Display this help.
1884 q :q Q :Q ZZ Exit.
1885 ---------------------------------------------------------------------------
1886 MOVING
1887 e ^E j ^N CR * Forward one line (or N lines).
1888 y ^Y k ^K ^P * Backward one line (or N lines).
1889 f ^F ^V SPACE * Forward one window (or N lines).
1890 b ^B ESC-v * Backward one window (or N lines).
1891 z * Forward one window (and set window to N).
1892 w * Backward one window (and set window to N).
1893 ESC-SPACE * Forward one window, but don't stop at end-of-file.
1894 d ^D * Forward one half-window (and set half-window to N).
1895 u ^U * Backward one half-window (and set half-window to N).
1896 ESC-) RightArrow * Left one half screen width (or N positions).
1897 ESC-( LeftArrow * Right one half screen width (or N positions).
1898 F Forward forever; like "tail -f".
1899 r ^R ^L Repaint screen.
1900 R Repaint screen, discarding buffered input.
1901 ---------------------------------------------------
1902 Default "window" is the screen height.
1903 Default "half-window" is half of the screen height.
1904 ---------------------------------------------------------------------------
1905 SEARCHING
1906 /pattern * Search forward for (N-th) matching line.
1907 ?pattern * Search backward for (N-th) matching line.
1908 n * Repeat previous search (for N-th occurrence).
1909 N * Repeat previous search in reverse direction.
1910 ESC-n * Repeat previous search, spanning files.
1911 ESC-N * Repeat previous search, reverse dir. & spanning files.
1912 ESC-u Undo (toggle) search highlighting.
1913 ---------------------------------------------------
1914 Search patterns may be modified by one or more of:
1915 ^N or ! Search for NON-matching lines.
1916 ^E or * Search multiple files (pass thru END OF FILE).
1917 ^F or @ Start search at FIRST file (for /) or last file (for ?).
1918 ^K Highlight matches, but don't move (KEEP position).
1919 ^R Don't use REGULAR EXPRESSIONS.
1920 ---------------------------------------------------------------------------
1921 JUMPING
1922 g < ESC-< * Go to first line in file (or line N).
1923 G > ESC-> * Go to last line in file (or line N).
1924 p % * Go to beginning of file (or N percent into file).
1925 t * Go to the (N-th) next tag.
1926 T * Go to the (N-th) previous tag.
1927 { ( [ * Find close bracket } ) ].
1928 } ) ] * Find open bracket { ( [.
1929 ESC-^F <c1> <c2> * Find close bracket <c2>.
1930 ESC-^B <c1> <c2> * Find open bracket <c1>
1931 ---------------------------------------------------
1932 Each "find close bracket" command goes forward to the close bracket
1933 matching the (N-th) open bracket in the top line.
1934 Each "find open bracket" command goes backward to the open bracket
1935 matching the (N-th) close bracket in the bottom line.
1936 m<letter> Mark the current position with <letter>.
1937 '<letter> Go to a previously marked position.
1938 '' Go to the previous position.
1939 ^X^X Same as '.
1940 ---------------------------------------------------
1941 A mark is any upper-case or lower-case letter.
1942 Certain marks are predefined:
1943 ^ means beginning of the file
1944 $ means end of the file
1945 ---------------------------------------------------------------------------
1946 CHANGING FILES
1947 :e [file] Examine a new file.
1948 ^X^V Same as :e.
1949 :n * Examine the (N-th) next file from the command line.
1950 :p * Examine the (N-th) previous file from the command line.
1951 :x * Examine the first (or N-th) file from the command line.
1952 :d Delete the current file from the command line list.
1953 = ^G :f Print current file name.
1954 ---------------------------------------------------------------------------
1955 MISCELLANEOUS COMMANDS
1956 -<flag> Toggle a command line option [see OPTIONS below].
1957 --<name> Toggle a command line option, by name.
1958 _<flag> Display the setting of a command line option.
1959 __<name> Display the setting of an option, by name.
1960 +cmd Execute the less cmd each time a new file is examined.
1961 !command Execute the shell command with $SHELL.
1962 |Xcommand Pipe file between current pos & mark X to shell command.
1963 v Edit the current file with $VISUAL or $EDITOR.
1964 V Print version number of "less".
1965 ---------------------------------------------------------------------------
1966 OPTIONS
1967 Most options may be changed either on the command line,
1968 or from within less by using the - or -- command.
1969 Options may be given in one of two forms: either a single
[3621]1970 character preceded by a -, or a name preceded by --.
[2725]1971 -? ........ --help
1972 Display help (from command line).
1973 -a ........ --search-skip-screen
1974 Forward search skips current screen.
1975 -b [N] .... --buffers=[N]
1976 Number of buffers.
1977 -B ........ --auto-buffers
1978 Don't automatically allocate buffers for pipes.
1979 -c ........ --clear-screen
1980 Repaint by clearing rather than scrolling.
1981 -d ........ --dumb
1982 Dumb terminal.
1983 -D [xn.n] . --color=xn.n
1984 Set screen colors. (MS-DOS only)
1985 -e -E .... --quit-at-eof --QUIT-AT-EOF
1986 Quit at end of file.
1987 -f ........ --force
1988 Force open non-regular files.
1989 -F ........ --quit-if-one-screen
1990 Quit if entire file fits on first screen.
1991 -g ........ --hilite-search
1992 Highlight only last match for searches.
1993 -G ........ --HILITE-SEARCH
1994 Don't highlight any matches for searches.
1995 -h [N] .... --max-back-scroll=[N]
1996 Backward scroll limit.
1997 -i ........ --ignore-case
1998 Ignore case in searches that do not contain uppercase.
1999 -I ........ --IGNORE-CASE
2000 Ignore case in all searches.
2001 -j [N] .... --jump-target=[N]
2002 Screen position of target lines.
2003 -J ........ --status-column
2004 Display a status column at left edge of screen.
2005 -k [file] . --lesskey-file=[file]
2006 Use a lesskey file.
2007 -L ........ --no-lessopen
2008 Ignore the LESSOPEN environment variable.
2009 -m -M .... --long-prompt --LONG-PROMPT
2010 Set prompt style.
2011 -n -N .... --line-numbers --LINE-NUMBERS
2012 Don't use line numbers.
2013 -o [file] . --log-file=[file]
2014 Copy to log file (standard input only).
2015 -O [file] . --LOG-FILE=[file]
2016 Copy to log file (unconditionally overwrite).
2017 -p [pattern] --pattern=[pattern]
2018 Start at pattern (from command line).
2019 -P [prompt] --prompt=[prompt]
2020 Define new prompt.
2021 -q -Q .... --quiet --QUIET --silent --SILENT
2022 Quiet the terminal bell.
2023 -r -R .... --raw-control-chars --RAW-CONTROL-CHARS
2024 Output "raw" control characters.
2025 -s ........ --squeeze-blank-lines
2026 Squeeze multiple blank lines.
2027 -S ........ --chop-long-lines
2028 Chop long lines.
2029 -t [tag] .. --tag=[tag]
2030 Find a tag.
2031 -T [tagsfile] --tag-file=[tagsfile]
2032 Use an alternate tags file.
2033 -u -U .... --underline-special --UNDERLINE-SPECIAL
2034 Change handling of backspaces.
2035 -V ........ --version
2036 Display the version number of "less".
2037 -w ........ --hilite-unread
2038 Highlight first new line after forward-screen.
2039 -W ........ --HILITE-UNREAD
2040 Highlight first new line after any forward movement.
2041 -x [N[,...]] --tabs=[N[,...]]
2042 Set tab stops.
2043 -X ........ --no-init
2044 Don't use termcap init/deinit strings.
2045 --no-keypad
2046 Don't use termcap keypad init/deinit strings.
2047 -y [N] .... --max-forw-scroll=[N]
2048 Forward scroll limit.
2049 -z [N] .... --window=[N]
2050 Set size of window.
2051 -" [c[c]] . --quotes=[c[c]]
2052 Set shell quote characters.
2053 -~ ........ --tilde
2054 Don't display tildes after end of file.
2055 -# [N] .... --shift=[N]
2056 Horizontal scroll amount (0 = one half screen width)
2057
2058 ---------------------------------------------------------------------------
2059 LINE EDITING
2060 These keys can be used to edit text being entered
2061 on the "command line" at the bottom of the screen.
2062 RightArrow ESC-l Move cursor right one character.
2063 LeftArrow ESC-h Move cursor left one character.
2064 CNTL-RightArrow ESC-RightArrow ESC-w Move cursor right one word.
2065 CNTL-LeftArrow ESC-LeftArrow ESC-b Move cursor left one word.
2066 HOME ESC-0 Move cursor to start of line.
2067 END ESC-$ Move cursor to end of line.
2068 BACKSPACE Delete char to left of cursor.
2069 DELETE ESC-x Delete char under cursor.
2070 CNTL-BACKSPACE ESC-BACKSPACE Delete word to left of cursor.
2071 CNTL-DELETE ESC-DELETE ESC-X Delete word under cursor.
2072 CNTL-U ESC (MS-DOS only) Delete entire line.
2073 UpArrow ESC-k Retrieve previous command line.
2074 DownArrow ESC-j Retrieve next command line.
2075 TAB Complete filename & cycle.
2076 SHIFT-TAB ESC-TAB Complete filename & reverse cycle.
2077 CNTL-L Complete filename, list all.
2078*/
Note: See TracBrowser for help on using the repository browser.