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