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