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