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