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