busybox/miscutils/less.c
<<
>>
Prefs
   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
 164enum {
 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 */
 172enum {
 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
 187enum { pattern_valid = 0 };
 188#endif
 189
 190struct 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 */
 298static 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 */
 306static void move_cursor(int line, int col)
 307{
 308        printf(ESC"[%u;%uH", line, col);
 309}
 310
 311static void clear_line(void)
 312{
 313        printf(ESC"[%u;0H" CLEAR_2_EOL, max_displayed_line + 2);
 314}
 315
 316static void print_hilite(const char *str)
 317{
 318        printf(HIGHLIGHT"%s"NORMAL, str);
 319}
 320
 321static 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 */
 328static 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
 341static 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
 428static void fill_match_lines(unsigned pos);
 429#else
 430#define fill_match_lines(pos) ((void)0)
 431#endif
 432
 433static 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 */
 467static 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
 651static 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 */
 664static 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 */
 702static 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 */
 738static 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
 770static 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 :( */
 775static 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
 781static 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
 800static 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
 861void print_found(const char *line);
 862#endif
 863
 864static 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 */
 897static 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
 921static 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 */
 957static 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
 979static 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 */
 998static 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
1009static 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 */
1025static 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
1038static void buffer_line(int linenum)
1039{
1040        buffer_to_line(linenum, FALSE);
1041}
1042
1043static void buffer_lineno(int lineno)
1044{
1045        buffer_to_line(lineno, TRUE);
1046}
1047
1048static 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 */
1071static 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
1093static 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. */
1158static 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
1178static 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
1209static 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 */
1240static 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
1252static 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
1273static 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
1309static 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
1318static 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
1337static 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
1355static 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
1407static 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
1476static 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
1514static 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
1551static 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
1577static 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
1597static 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
1620static 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
1637static 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
1662static 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
1688static 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
1794static void sig_catcher(int sig)
1795{
1796        less_exit(- sig);
1797}
1798
1799#if ENABLE_FEATURE_LESS_WINCH
1800static void sigwinch_handler(int sig UNUSED_PARAM)
1801{
1802        winch_counter++;
1803}
1804#endif
1805
1806int less_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1807int 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/*
1955Help text of less version 418 is below.
1956If you are implementing something, keeping
1957key 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*/
2160