source: branches/3.2/mindi-busybox/miscutils/less.c @ 3232

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