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