246cc6e9be0bec0b00f3e7861d41ff912fcf984f
[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 /* FIXME: currently doesn't work right */
32 #undef ENABLE_FEATURE_LESS_FLAGCS
33 #define ENABLE_FEATURE_LESS_FLAGCS 0
34
35 /* The escape codes for highlighted and normal text */
36 #define HIGHLIGHT "\033[7m"
37 #define NORMAL "\033[0m"
38 /* The escape code to clear the screen */
39 #define CLEAR "\033[H\033[J"
40 /* The escape code to clear to end of line */
41 #define CLEAR_2_EOL "\033[K"
42
43 /* These are the escape sequences corresponding to special keys */
44 enum {
45         REAL_KEY_UP = 'A',
46         REAL_KEY_DOWN = 'B',
47         REAL_KEY_RIGHT = 'C',
48         REAL_KEY_LEFT = 'D',
49         REAL_PAGE_UP = '5',
50         REAL_PAGE_DOWN = '6',
51         REAL_KEY_HOME = '7', // vt100? linux vt? or what?
52         REAL_KEY_END = '8',
53         REAL_KEY_HOME_ALT = '1', // ESC [1~ (vt100? linux vt? or what?)
54         REAL_KEY_END_ALT = '4', // ESC [4~
55         REAL_KEY_HOME_XTERM = 'H',
56         REAL_KEY_END_XTERM = 'F',
57
58 /* These are the special codes assigned by this program to the special keys */
59         KEY_UP = 20,
60         KEY_DOWN = 21,
61         KEY_RIGHT = 22,
62         KEY_LEFT = 23,
63         PAGE_UP = 24,
64         PAGE_DOWN = 25,
65         KEY_HOME = 26,
66         KEY_END = 27,
67
68 /* Absolute max of lines eaten */
69         MAXLINES = CONFIG_FEATURE_LESS_MAXLINES,
70
71 /* This many "after the end" lines we will show (at max) */
72         TILDES = 1,
73 };
74
75 /* Command line options */
76 enum {
77         FLAG_E = 1,
78         FLAG_M = 1 << 1,
79         FLAG_m = 1 << 2,
80         FLAG_N = 1 << 3,
81         FLAG_TILDE = 1 << 4,
82 /* hijack command line options variable for internal state vars */
83         LESS_STATE_MATCH_BACKWARDS = 1 << 15,
84 };
85
86 #if !ENABLE_FEATURE_LESS_REGEXP
87 enum { pattern_valid = 0 };
88 #endif
89
90 struct globals {
91         int cur_fline; /* signed */
92         int kbd_fd;  /* fd to get input from */
93         int less_gets_pos;
94 /* last position in last line, taking into account tabs */
95         size_t linepos;
96         unsigned max_displayed_line;
97         unsigned max_fline;
98         unsigned max_lineno; /* this one tracks linewrap */
99         unsigned width;
100         ssize_t eof_error; /* eof if 0, error if < 0 */
101         size_t readpos;
102         size_t readeof;
103         const char **buffer;
104         const char **flines;
105         const char *empty_line_marker;
106         unsigned num_files;
107         unsigned current_file;
108         char *filename;
109         char **files;
110 #if ENABLE_FEATURE_LESS_MARKS
111         unsigned num_marks;
112         unsigned mark_lines[15][2];
113 #endif
114 #if ENABLE_FEATURE_LESS_REGEXP
115         unsigned *match_lines;
116         int match_pos; /* signed! */
117         unsigned num_matches;
118         regex_t pattern;
119         smallint pattern_valid;
120 #endif
121         smallint terminated;
122         struct termios term_orig, term_less;
123 };
124 #define G (*ptr_to_globals)
125 #define cur_fline           (G.cur_fline         )
126 #define kbd_fd              (G.kbd_fd            )
127 #define less_gets_pos       (G.less_gets_pos     )
128 #define linepos             (G.linepos           )
129 #define max_displayed_line  (G.max_displayed_line)
130 #define max_fline           (G.max_fline         )
131 #define max_lineno          (G.max_lineno        )
132 #define width               (G.width             )
133 #define eof_error           (G.eof_error         )
134 #define readpos             (G.readpos           )
135 #define readeof             (G.readeof           )
136 #define buffer              (G.buffer            )
137 #define flines              (G.flines            )
138 #define empty_line_marker   (G.empty_line_marker )
139 #define num_files           (G.num_files         )
140 #define current_file        (G.current_file      )
141 #define filename            (G.filename          )
142 #define files               (G.files             )
143 #define num_marks           (G.num_marks         )
144 #define mark_lines          (G.mark_lines        )
145 #if ENABLE_FEATURE_LESS_REGEXP
146 #define match_lines         (G.match_lines       )
147 #define match_pos           (G.match_pos         )
148 #define num_matches         (G.num_matches       )
149 #define pattern             (G.pattern           )
150 #define pattern_valid       (G.pattern_valid     )
151 #endif
152 #define terminated          (G.terminated        )
153 #define term_orig           (G.term_orig         )
154 #define term_less           (G.term_less         )
155 #define INIT_G() do { \
156                 PTR_TO_GLOBALS = xzalloc(sizeof(G)); \
157                 less_gets_pos = -1; \
158                 empty_line_marker = "~"; \
159                 num_files = 1; \
160                 current_file = 1; \
161                 eof_error = 1; \
162                 terminated = 1; \
163         } while (0)
164
165 /* Reset terminal input to normal */
166 static void set_tty_cooked(void)
167 {
168         fflush(stdout);
169         tcsetattr(kbd_fd, TCSANOW, &term_orig);
170 }
171
172 /* Exit the program gracefully */
173 static void less_exit(int code)
174 {
175         /* TODO: We really should save the terminal state when we start,
176          * and restore it when we exit. Less does this with the
177          * "ti" and "te" termcap commands; can this be done with
178          * only termios.h? */
179         bb_putchar('\n');
180         fflush_stdout_and_exit(code);
181 }
182
183 /* Move the cursor to a position (x,y), where (0,0) is the
184    top-left corner of the console */
185 static void move_cursor(int line, int row)
186 {
187         printf("\033[%u;%uH", line, row);
188 }
189
190 static void clear_line(void)
191 {
192         printf("\033[%u;0H" CLEAR_2_EOL, max_displayed_line + 2);
193 }
194
195 static void print_hilite(const char *str)
196 {
197         printf(HIGHLIGHT"%s"NORMAL, str);
198 }
199
200 static void print_statusline(const char *str)
201 {
202         clear_line();
203         printf(HIGHLIGHT"%.*s"NORMAL, width - 1, str);
204 }
205
206 #if ENABLE_FEATURE_LESS_REGEXP
207 static void fill_match_lines(unsigned pos);
208 #else
209 #define fill_match_lines(pos) ((void)0)
210 #endif
211
212 /* Devilishly complex routine.
213  *
214  * Has to deal with EOF and EPIPE on input,
215  * with line wrapping, with last line not ending in '\n'
216  * (possibly not ending YET!), with backspace and tabs.
217  * It reads input again if last time we got an EOF (thus supporting
218  * growing files) or EPIPE (watching output of slow process like make).
219  *
220  * Variables used:
221  * flines[] - array of lines already read. Linewrap may cause
222  *      one source file line to occupy several flines[n].
223  * flines[max_fline] - last line, possibly incomplete.
224  * terminated - 1 if flines[max_fline] is 'terminated'
225  *      (if there was '\n' [which isn't stored itself, we just remember
226  *      that it was seen])
227  * max_lineno - last line's number, this one doesn't increment
228  *      on line wrap, only on "real" new lines.
229  * readbuf[0..readeof-1] - small preliminary buffer.
230  * readbuf[readpos] - next character to add to current line.
231  * linepos - screen line position of next char to be read
232  *      (takes into account tabs and backspaces)
233  * eof_error - < 0 error, == 0 EOF, > 0 not EOF/error
234  */
235 static void read_lines(void)
236 {
237 #define readbuf bb_common_bufsiz1
238         char *current_line, *p;
239         USE_FEATURE_LESS_REGEXP(unsigned old_max_fline = max_fline;)
240         int w = width;
241         char last_terminated = terminated;
242
243         if (option_mask32 & FLAG_N)
244                 w -= 8;
245
246         current_line = xmalloc(w);
247         p = current_line;
248         max_fline += last_terminated;
249         if (!last_terminated) {
250                 const char *cp = flines[max_fline];
251                 if (option_mask32 & FLAG_N)
252                         cp += 8;
253                 strcpy(current_line, cp);
254                 p += strlen(current_line);
255                 /* linepos is still valid from previous read_lines() */
256         } else {
257                 linepos = 0;
258         }
259
260         while (1) {
261  again:
262                 *p = '\0';
263                 terminated = 0;
264                 while (1) {
265                         char c;
266                         /* if no unprocessed chars left, eat more */
267                         if (readpos >= readeof) {
268                                 smallint yielded = 0;
269
270                                 ndelay_on(0);
271  read_again:
272                                 eof_error = safe_read(0, readbuf, sizeof(readbuf));
273                                 readpos = 0;
274                                 readeof = eof_error;
275                                 if (eof_error < 0) {
276                                         if (errno == EAGAIN && !yielded) {
277                         /* We can hit EAGAIN while searching for regexp match.
278                          * Yield is not 100% reliable solution in general,
279                          * but for less it should be good enough -
280                          * we give stdin supplier some CPU time to produce
281                          * more input. We do it just once.
282                          * Currently, we do not stop when we found the Nth
283                          * occurrence we were looking for. We read till end
284                          * (or double EAGAIN). TODO? */
285                                                 sched_yield();
286                                                 yielded = 1;
287                                                 goto read_again;
288                                         }
289                                         readeof = 0;
290                                         if (errno != EAGAIN)
291                                                 print_statusline("read error");
292                                 }
293                                 ndelay_off(0);
294
295                                 if (eof_error <= 0) {
296                                         goto reached_eof;
297                                 }
298                         }
299                         c = readbuf[readpos];
300                         /* backspace? [needed for manpages] */
301                         /* <tab><bs> is (a) insane and */
302                         /* (b) harder to do correctly, so we refuse to do it */
303                         if (c == '\x8' && linepos && p[-1] != '\t') {
304                                 readpos++; /* eat it */
305                                 linepos--;
306                         /* was buggy (p could end up <= current_line)... */
307                                 *--p = '\0';
308                                 continue;
309                         }
310                         {
311                                 size_t new_linepos = linepos + 1;
312                                 if (c == '\t') {
313                                         new_linepos += 7;
314                                         new_linepos &= (~7);
315                                 }
316                                 if (new_linepos >= w)
317                                         break;
318                                 linepos = new_linepos;
319                         }
320                         /* ok, we will eat this char */
321                         readpos++;
322                         if (c == '\n') {
323                                 terminated = 1;
324                                 linepos = 0;
325                                 break;
326                         }
327                         /* NUL is substituted by '\n'! */
328                         if (c == '\0') c = '\n';
329                         *p++ = c;
330                         *p = '\0';
331                 }
332                 /* Corner case: linewrap with only "" wrapping to next line */
333                 /* Looks ugly on screen, so we do not store this empty line */
334                 if (!last_terminated && !current_line[0]) {
335                         last_terminated = 1;
336                         max_lineno++;
337                         goto again;
338                 }
339  reached_eof:
340                 last_terminated = terminated;
341                 flines = xrealloc(flines, (max_fline+1) * sizeof(char *));
342                 if (option_mask32 & FLAG_N) {
343                         /* Width of 7 preserves tab spacing in the text */
344                         flines[max_fline] = xasprintf(
345                                 (max_lineno <= 9999999) ? "%7u %s" : "%07u %s",
346                                 max_lineno % 10000000, current_line);
347                         free(current_line);
348                         if (terminated)
349                                 max_lineno++;
350                 } else {
351                         flines[max_fline] = xrealloc(current_line, strlen(current_line)+1);
352                 }
353                 if (max_fline >= MAXLINES) {
354                         eof_error = 0; /* Pretend we saw EOF */
355                         break;
356                 }
357                 if (max_fline > cur_fline + max_displayed_line)
358                         break;
359                 if (eof_error <= 0) {
360                         if (eof_error < 0 && errno == EAGAIN) {
361                                 /* not yet eof or error, reset flag (or else
362                                  * we will hog CPU - select() will return
363                                  * immediately */
364                                 eof_error = 1;
365                         }
366                         break;
367                 }
368                 max_fline++;
369                 current_line = xmalloc(w);
370                 p = current_line;
371                 linepos = 0;
372         }
373         fill_match_lines(old_max_fline);
374 #undef readbuf
375 }
376
377 #if ENABLE_FEATURE_LESS_FLAGS
378 /* Interestingly, writing calc_percent as a function saves around 32 bytes
379  * on my build. */
380 static int calc_percent(void)
381 {
382         unsigned p = (100 * (cur_fline+max_displayed_line+1) + max_fline/2) / (max_fline+1);
383         return p <= 100 ? p : 100;
384 }
385
386 /* Print a status line if -M was specified */
387 static void m_status_print(void)
388 {
389         int percentage;
390
391         if (less_gets_pos >= 0) /* don't touch statusline while input is done! */
392                 return;
393
394         clear_line();
395         printf(HIGHLIGHT"%s", filename);
396         if (num_files > 1)
397                 printf(" (file %i of %i)", current_file, num_files);
398         printf(" lines %i-%i/%i ",
399                         cur_fline + 1, cur_fline + max_displayed_line + 1,
400                         max_fline + 1);
401         if (cur_fline >= max_fline - max_displayed_line) {
402                 printf("(END)"NORMAL);
403                 if (num_files > 1 && current_file != num_files)
404                         printf(HIGHLIGHT" - next: %s"NORMAL, files[current_file]);
405                 return;
406         }
407         percentage = calc_percent();
408         printf("%i%%"NORMAL, percentage);
409 }
410 #endif
411
412 /* Print the status line */
413 static void status_print(void)
414 {
415         const char *p;
416
417         if (less_gets_pos >= 0) /* don't touch statusline while input is done! */
418                 return;
419
420         /* Change the status if flags have been set */
421 #if ENABLE_FEATURE_LESS_FLAGS
422         if (option_mask32 & (FLAG_M|FLAG_m)) {
423                 m_status_print();
424                 return;
425         }
426         /* No flags set */
427 #endif
428
429         clear_line();
430         if (cur_fline && cur_fline < max_fline - max_displayed_line) {
431                 bb_putchar(':');
432                 return;
433         }
434         p = "(END)";
435         if (!cur_fline)
436                 p = filename;
437         if (num_files > 1) {
438                 printf(HIGHLIGHT"%s (file %i of %i)"NORMAL,
439                                 p, current_file, num_files);
440                 return;
441         }
442         print_hilite(p);
443 }
444
445 static void cap_cur_fline(int nlines)
446 {
447         int diff;
448         if (cur_fline < 0)
449                 cur_fline = 0;
450         if (cur_fline + max_displayed_line > max_fline + TILDES) {
451                 cur_fline -= nlines;
452                 if (cur_fline < 0)
453                         cur_fline = 0;
454                 diff = max_fline - (cur_fline + max_displayed_line) + TILDES;
455                 /* As the number of lines requested was too large, we just move
456                 to the end of the file */
457                 if (diff > 0)
458                         cur_fline += diff;
459         }
460 }
461
462 static const char controls[] ALIGN1 =
463         /* NUL: never encountered; TAB: not converted */
464         /**/"\x01\x02\x03\x04\x05\x06\x07\x08"  "\x0a\x0b\x0c\x0d\x0e\x0f"
465         "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
466         "\x7f\x9b"; /* DEL and infamous Meta-ESC :( */
467 static const char ctrlconv[] ALIGN1 =
468         /* '\n': it's a former NUL - subst with '@', not 'J' */
469         "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x40\x4b\x4c\x4d\x4e\x4f"
470         "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f";
471
472 #if ENABLE_FEATURE_LESS_REGEXP
473 static void print_found(const char *line)
474 {
475         int match_status;
476         int eflags;
477         char *growline;
478         regmatch_t match_structs;
479
480         char buf[width];
481         const char *str = line;
482         char *p = buf;
483         size_t n;
484
485         while (*str) {
486                 n = strcspn(str, controls);
487                 if (n) {
488                         if (!str[n]) break;
489                         memcpy(p, str, n);
490                         p += n;
491                         str += n;
492                 }
493                 n = strspn(str, controls);
494                 memset(p, '.', n);
495                 p += n;
496                 str += n;
497         }
498         strcpy(p, str);
499
500         /* buf[] holds quarantined version of str */
501
502         /* Each part of the line that matches has the HIGHLIGHT
503            and NORMAL escape sequences placed around it.
504            NB: we regex against line, but insert text
505            from quarantined copy (buf[]) */
506         str = buf;
507         growline = NULL;
508         eflags = 0;
509         goto start;
510
511         while (match_status == 0) {
512                 char *new = xasprintf("%s%.*s"HIGHLIGHT"%.*s"NORMAL,
513                                 growline ? : "",
514                                 match_structs.rm_so, str,
515                                 match_structs.rm_eo - match_structs.rm_so,
516                                                 str + match_structs.rm_so);
517                 free(growline); growline = new;
518                 str += match_structs.rm_eo;
519                 line += match_structs.rm_eo;
520                 eflags = REG_NOTBOL;
521  start:
522                 /* Most of the time doesn't find the regex, optimize for that */
523                 match_status = regexec(&pattern, line, 1, &match_structs, eflags);
524         }
525
526         if (!growline) {
527                 printf(CLEAR_2_EOL"%s\n", str);
528                 return;
529         }
530         printf(CLEAR_2_EOL"%s%s\n", growline, str);
531         free(growline);
532 }
533 #else
534 void print_found(const char *line);
535 #endif
536
537 static void print_ascii(const char *str)
538 {
539         char buf[width];
540         char *p;
541         size_t n;
542
543         printf(CLEAR_2_EOL);
544         while (*str) {
545                 n = strcspn(str, controls);
546                 if (n) {
547                         if (!str[n]) break;
548                         printf("%.*s", (int) n, str);
549                         str += n;
550                 }
551                 n = strspn(str, controls);
552                 p = buf;
553                 do {
554                         if (*str == 0x7f)
555                                 *p++ = '?';
556                         else if (*str == (char)0x9b)
557                         /* VT100's CSI, aka Meta-ESC. Who's inventor? */
558                         /* I want to know who committed this sin */
559                                 *p++ = '{';
560                         else
561                                 *p++ = ctrlconv[(unsigned char)*str];
562                         str++;
563                 } while (--n);
564                 *p = '\0';
565                 print_hilite(buf);
566         }
567         puts(str);
568 }
569
570 /* Print the buffer */
571 static void buffer_print(void)
572 {
573         int i;
574
575         move_cursor(0, 0);
576         for (i = 0; i <= max_displayed_line; i++)
577                 if (pattern_valid)
578                         print_found(buffer[i]);
579                 else
580                         print_ascii(buffer[i]);
581         status_print();
582 }
583
584 static void buffer_fill_and_print(void)
585 {
586         int i;
587         for (i = 0; i <= max_displayed_line && cur_fline + i <= max_fline; i++) {
588                 buffer[i] = flines[cur_fline + i];
589         }
590         for (; i <= max_displayed_line; i++) {
591                 buffer[i] = empty_line_marker;
592         }
593         buffer_print();
594 }
595
596 /* Move the buffer up and down in the file in order to scroll */
597 static void buffer_down(int nlines)
598 {
599         cur_fline += nlines;
600         read_lines();
601         cap_cur_fline(nlines);
602         buffer_fill_and_print();
603 }
604
605 static void buffer_up(int nlines)
606 {
607         cur_fline -= nlines;
608         if (cur_fline < 0) cur_fline = 0;
609         read_lines();
610         buffer_fill_and_print();
611 }
612
613 static void buffer_line(int linenum)
614 {
615         if (linenum < 0)
616                 linenum = 0;
617         cur_fline = linenum;
618         read_lines();
619         if (linenum + max_displayed_line > max_fline)
620                 linenum = max_fline - max_displayed_line + TILDES;
621         if (linenum < 0)
622                 linenum = 0;
623         cur_fline = linenum;
624         buffer_fill_and_print();
625 }
626
627 static void open_file_and_read_lines(void)
628 {
629         if (filename) {
630                 int fd = xopen(filename, O_RDONLY);
631                 dup2(fd, 0);
632                 if (fd) close(fd);
633         } else {
634                 /* "less" with no arguments in argv[] */
635                 /* For status line only */
636                 filename = xstrdup(bb_msg_standard_input);
637         }
638         readpos = 0;
639         readeof = 0;
640         linepos = 0;
641         terminated = 1;
642         read_lines();
643 }
644
645 /* Reinitialize everything for a new file - free the memory and start over */
646 static void reinitialize(void)
647 {
648         int i;
649
650         if (flines) {
651                 for (i = 0; i <= max_fline; i++)
652                         free((void*)(flines[i]));
653                 free(flines);
654                 flines = NULL;
655         }
656
657         max_fline = -1;
658         cur_fline = 0;
659         max_lineno = 0;
660         open_file_and_read_lines();
661         buffer_fill_and_print();
662 }
663
664 static ssize_t getch_nowait(char* input, int sz)
665 {
666         ssize_t rd;
667         struct pollfd pfd[2];
668
669         pfd[0].fd = STDIN_FILENO;
670         pfd[0].events = POLLIN;
671         pfd[1].fd = kbd_fd;
672         pfd[1].events = POLLIN;
673  again:
674         tcsetattr(kbd_fd, TCSANOW, &term_less);
675         /* NB: select/poll returns whenever read will not block. Therefore:
676          * if eof is reached, select/poll will return immediately
677          * because read will immediately return 0 bytes.
678          * Even if select/poll says that input is available, read CAN block
679          * (switch fd into O_NONBLOCK'ed mode to avoid it)
680          */
681         rd = 1;
682         if (max_fline <= cur_fline + max_displayed_line
683          && eof_error > 0 /* did NOT reach eof yet */
684         ) {
685                 /* We are interested in stdin */
686                 rd = 0;
687         }
688         /* position cursor if line input is done */
689         if (less_gets_pos >= 0)
690                 move_cursor(max_displayed_line + 2, less_gets_pos + 1);
691         fflush(stdout);
692         safe_poll(pfd + rd, 2 - rd, -1);
693
694         input[0] = '\0';
695         rd = safe_read(kbd_fd, input, sz); /* NB: kbd_fd is in O_NONBLOCK mode */
696         if (rd < 0 && errno == EAGAIN) {
697                 /* No keyboard input -> we have input on stdin! */
698                 read_lines();
699                 buffer_fill_and_print();
700                 goto again;
701         }
702         set_tty_cooked();
703         return rd;
704 }
705
706 /* Grab a character from input without requiring the return key. If the
707  * character is ASCII \033, get more characters and assign certain sequences
708  * special return codes. Note that this function works best with raw input. */
709 static int less_getch(int pos)
710 {
711         unsigned char input[16];
712         unsigned i;
713
714  again:
715         less_gets_pos = pos;
716         memset(input, 0, sizeof(input));
717         getch_nowait(input, sizeof(input));
718         less_gets_pos = -1;
719
720         /* Detect escape sequences (i.e. arrow keys) and handle
721          * them accordingly */
722         if (input[0] == '\033' && input[1] == '[') {
723                 i = input[2] - REAL_KEY_UP;
724                 if (i < 4)
725                         return 20 + i;
726                 i = input[2] - REAL_PAGE_UP;
727                 if (i < 4)
728                         return 24 + i;
729                 if (input[2] == REAL_KEY_HOME_XTERM)
730                         return KEY_HOME;
731                 if (input[2] == REAL_KEY_HOME_ALT)
732                         return KEY_HOME;
733                 if (input[2] == REAL_KEY_END_XTERM)
734                         return KEY_END;
735                 if (input[2] == REAL_KEY_END_ALT)
736                         return KEY_END;
737                 return 0;
738         }
739         /* Reject almost all control chars */
740         i = input[0];
741         if (i < ' ' && i != 0x0d && i != 8)
742                 goto again;
743         return i;
744 }
745
746 static char* less_gets(int sz)
747 {
748         char c;
749         int i = 0;
750         char *result = xzalloc(1);
751
752         while (1) {
753                 c = '\0';
754                 less_gets_pos = sz + i;
755                 getch_nowait(&c, 1);
756                 if (c == 0x0d) {
757                         less_gets_pos = -1;
758                         return result;
759                 }
760                 if (c == 0x7f)
761                         c = 8;
762                 if (c == 8 && i) {
763                         printf("\x8 \x8");
764                         i--;
765                         result[i] = '\0';
766                 }
767                 if (c < ' ')
768                         continue;
769                 if (i >= width - sz - 1)
770                         continue; /* len limit */
771                 bb_putchar(c);
772                 result[i++] = c;
773                 result = xrealloc(result, i+1);
774                 result[i] = '\0';
775         }
776 }
777
778 static void examine_file(void)
779 {
780         char *new_fname;
781
782         print_statusline("Examine: ");
783         new_fname = less_gets(sizeof("Examine: ") - 1);
784         if (!new_fname[0]) {
785                 status_print();
786  err:
787                 free(new_fname);
788                 return;
789         }
790         if (access(new_fname, R_OK) != 0) {
791                 print_statusline("Cannot read this file");
792                 goto err;
793         }
794         free(filename);
795         filename = new_fname;
796         /* files start by = argv. why we assume that argv is infinitely long??
797         files[num_files] = filename;
798         current_file = num_files + 1;
799         num_files++; */
800         files[0] = filename;
801         num_files = current_file = 1;
802         reinitialize();
803 }
804
805 /* This function changes the file currently being paged. direction can be one of the following:
806  * -1: go back one file
807  *  0: go to the first file
808  *  1: go forward one file */
809 static void change_file(int direction)
810 {
811         if (current_file != ((direction > 0) ? num_files : 1)) {
812                 current_file = direction ? current_file + direction : 1;
813                 free(filename);
814                 filename = xstrdup(files[current_file - 1]);
815                 reinitialize();
816         } else {
817                 print_statusline(direction > 0 ? "No next file" : "No previous file");
818         }
819 }
820
821 static void remove_current_file(void)
822 {
823         int i;
824
825         if (num_files < 2)
826                 return;
827
828         if (current_file != 1) {
829                 change_file(-1);
830                 for (i = 3; i <= num_files; i++)
831                         files[i - 2] = files[i - 1];
832                 num_files--;
833         } else {
834                 change_file(1);
835                 for (i = 2; i <= num_files; i++)
836                         files[i - 2] = files[i - 1];
837                 num_files--;
838                 current_file--;
839         }
840 }
841
842 static void colon_process(void)
843 {
844         int keypress;
845
846         /* Clear the current line and print a prompt */
847         print_statusline(" :");
848
849         keypress = less_getch(2);
850         switch (keypress) {
851         case 'd':
852                 remove_current_file();
853                 break;
854         case 'e':
855                 examine_file();
856                 break;
857 #if ENABLE_FEATURE_LESS_FLAGS
858         case 'f':
859                 m_status_print();
860                 break;
861 #endif
862         case 'n':
863                 change_file(1);
864                 break;
865         case 'p':
866                 change_file(-1);
867                 break;
868         case 'q':
869                 less_exit(0);
870                 break;
871         case 'x':
872                 change_file(0);
873                 break;
874         }
875 }
876
877 #if ENABLE_FEATURE_LESS_REGEXP
878 static void normalize_match_pos(int match)
879 {
880         if (match >= num_matches)
881                 match = num_matches - 1;
882         if (match < 0)
883                 match = 0;
884         match_pos = match;
885 }
886
887 static void goto_match(int match)
888 {
889         int sv;
890
891         if (!pattern_valid)
892                 return;
893         if (match < 0)
894                 match = 0;
895         sv = cur_fline;
896         /* Try to find next match if eof isn't reached yet */
897         if (match >= num_matches && eof_error > 0) {
898                 cur_fline = MAXLINES; /* look as far as needed */
899                 read_lines();
900         }
901         if (num_matches) {
902                 cap_cur_fline(cur_fline);
903                 normalize_match_pos(match);
904                 buffer_line(match_lines[match_pos]);
905         } else {
906                 cur_fline = sv;
907                 print_statusline("No matches found");
908         }
909 }
910
911 static void fill_match_lines(unsigned pos)
912 {
913         if (!pattern_valid)
914                 return;
915         /* Run the regex on each line of the current file */
916         while (pos <= max_fline) {
917                 /* If this line matches */
918                 if (regexec(&pattern, flines[pos], 0, NULL, 0) == 0
919                 /* and we didn't match it last time */
920                  && !(num_matches && match_lines[num_matches-1] == pos)
921                 ) {
922                         match_lines = xrealloc(match_lines, (num_matches+1) * sizeof(int));
923                         match_lines[num_matches++] = pos;
924                 }
925                 pos++;
926         }
927 }
928
929 static void regex_process(void)
930 {
931         char *uncomp_regex, *err;
932
933         /* Reset variables */
934         free(match_lines);
935         match_lines = NULL;
936         match_pos = 0;
937         num_matches = 0;
938         if (pattern_valid) {
939                 regfree(&pattern);
940                 pattern_valid = 0;
941         }
942
943         /* Get the uncompiled regular expression from the user */
944         clear_line();
945         bb_putchar((option_mask32 & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/');
946         uncomp_regex = less_gets(1);
947         if (!uncomp_regex[0]) {
948                 free(uncomp_regex);
949                 buffer_print();
950                 return;
951         }
952
953         /* Compile the regex and check for errors */
954         err = regcomp_or_errmsg(&pattern, uncomp_regex, 0);
955         free(uncomp_regex);
956         if (err) {
957                 print_statusline(err);
958                 free(err);
959                 return;
960         }
961
962         pattern_valid = 1;
963         match_pos = 0;
964         fill_match_lines(0);
965         while (match_pos < num_matches) {
966                 if (match_lines[match_pos] > cur_fline)
967                         break;
968                 match_pos++;
969         }
970         if (option_mask32 & LESS_STATE_MATCH_BACKWARDS)
971                 match_pos--;
972
973         /* It's possible that no matches are found yet.
974          * goto_match() will read input looking for match,
975          * if needed */
976         goto_match(match_pos);
977 }
978 #endif
979
980 static void number_process(int first_digit)
981 {
982         int i;
983         int num;
984         char num_input[sizeof(int)*4]; /* more than enough */
985         char keypress;
986
987         num_input[0] = first_digit;
988
989         /* Clear the current line, print a prompt, and then print the digit */
990         clear_line();
991         printf(":%c", first_digit);
992
993         /* Receive input until a letter is given */
994         i = 1;
995         while (i < sizeof(num_input)-1) {
996                 num_input[i] = less_getch(i + 1);
997                 if (!num_input[i] || !isdigit(num_input[i]))
998                         break;
999                 bb_putchar(num_input[i]);
1000                 i++;
1001         }
1002
1003         /* Take the final letter out of the digits string */
1004         keypress = num_input[i];
1005         num_input[i] = '\0';
1006         num = bb_strtou(num_input, NULL, 10);
1007         /* on format error, num == -1 */
1008         if (num < 1 || num > MAXLINES) {
1009                 buffer_print();
1010                 return;
1011         }
1012
1013         /* We now know the number and the letter entered, so we process them */
1014         switch (keypress) {
1015         case KEY_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015':
1016                 buffer_down(num);
1017                 break;
1018         case KEY_UP: case 'b': case 'w': case 'y': case 'u':
1019                 buffer_up(num);
1020                 break;
1021         case 'g': case '<': case 'G': case '>':
1022                 cur_fline = num + max_displayed_line;
1023                 read_lines();
1024                 buffer_line(num - 1);
1025                 break;
1026         case 'p': case '%':
1027                 num = num * (max_fline / 100); /* + max_fline / 2; */
1028                 cur_fline = num + max_displayed_line;
1029                 read_lines();
1030                 buffer_line(num);
1031                 break;
1032 #if ENABLE_FEATURE_LESS_REGEXP
1033         case 'n':
1034                 goto_match(match_pos + num);
1035                 break;
1036         case '/':
1037                 option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
1038                 regex_process();
1039                 break;
1040         case '?':
1041                 option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
1042                 regex_process();
1043                 break;
1044 #endif
1045         }
1046 }
1047
1048 #if ENABLE_FEATURE_LESS_FLAGCS
1049 static void flag_change(void)
1050 {
1051         int keypress;
1052
1053         clear_line();
1054         bb_putchar('-');
1055         keypress = less_getch(1);
1056
1057         switch (keypress) {
1058         case 'M':
1059                 option_mask32 ^= FLAG_M;
1060                 break;
1061         case 'm':
1062                 option_mask32 ^= FLAG_m;
1063                 break;
1064         case 'E':
1065                 option_mask32 ^= FLAG_E;
1066                 break;
1067         case '~':
1068                 option_mask32 ^= FLAG_TILDE;
1069                 break;
1070         }
1071 }
1072
1073 static void show_flag_status(void)
1074 {
1075         int keypress;
1076         int flag_val;
1077
1078         clear_line();
1079         bb_putchar('_');
1080         keypress = less_getch(1);
1081
1082         switch (keypress) {
1083         case 'M':
1084                 flag_val = option_mask32 & FLAG_M;
1085                 break;
1086         case 'm':
1087                 flag_val = option_mask32 & FLAG_m;
1088                 break;
1089         case '~':
1090                 flag_val = option_mask32 & FLAG_TILDE;
1091                 break;
1092         case 'N':
1093                 flag_val = option_mask32 & FLAG_N;
1094                 break;
1095         case 'E':
1096                 flag_val = option_mask32 & FLAG_E;
1097                 break;
1098         default:
1099                 flag_val = 0;
1100                 break;
1101         }
1102
1103         clear_line();
1104         printf(HIGHLIGHT"The status of the flag is: %u"NORMAL, flag_val != 0);
1105 }
1106 #endif
1107
1108 static void save_input_to_file(void)
1109 {
1110         const char *msg = "";
1111         char *current_line;
1112         int i;
1113         FILE *fp;
1114
1115         print_statusline("Log file: ");
1116         current_line = less_gets(sizeof("Log file: ")-1);
1117         if (current_line[0]) {
1118                 fp = fopen(current_line, "w");
1119                 if (!fp) {
1120                         msg = "Error opening log file";
1121                         goto ret;
1122                 }
1123                 for (i = 0; i <= max_fline; i++)
1124                         fprintf(fp, "%s\n", flines[i]);
1125                 fclose(fp);
1126                 msg = "Done";
1127         }
1128  ret:
1129         print_statusline(msg);
1130         free(current_line);
1131 }
1132
1133 #if ENABLE_FEATURE_LESS_MARKS
1134 static void add_mark(void)
1135 {
1136         int letter;
1137
1138         print_statusline("Mark: ");
1139         letter = less_getch(sizeof("Mark: ") - 1);
1140
1141         if (isalpha(letter)) {
1142                 /* If we exceed 15 marks, start overwriting previous ones */
1143                 if (num_marks == 14)
1144                         num_marks = 0;
1145
1146                 mark_lines[num_marks][0] = letter;
1147                 mark_lines[num_marks][1] = cur_fline;
1148                 num_marks++;
1149         } else {
1150                 print_statusline("Invalid mark letter");
1151         }
1152 }
1153
1154 static void goto_mark(void)
1155 {
1156         int letter;
1157         int i;
1158
1159         print_statusline("Go to mark: ");
1160         letter = less_getch(sizeof("Go to mark: ") - 1);
1161         clear_line();
1162
1163         if (isalpha(letter)) {
1164                 for (i = 0; i <= num_marks; i++)
1165                         if (letter == mark_lines[i][0]) {
1166                                 buffer_line(mark_lines[i][1]);
1167                                 break;
1168                         }
1169                 if (num_marks == 14 && letter != mark_lines[14][0])
1170                         print_statusline("Mark not set");
1171         } else
1172                 print_statusline("Invalid mark letter");
1173 }
1174 #endif
1175
1176 #if ENABLE_FEATURE_LESS_BRACKETS
1177 static char opp_bracket(char bracket)
1178 {
1179         switch (bracket) {
1180         case '{': case '[':
1181                 return bracket + 2;
1182         case '(':
1183                 return ')';
1184         case '}': case ']':
1185                 return bracket - 2;
1186         case ')':
1187                 return '(';
1188         }
1189         return 0;
1190 }
1191
1192 static void match_right_bracket(char bracket)
1193 {
1194         int bracket_line = -1;
1195         int i;
1196
1197         if (strchr(flines[cur_fline], bracket) == NULL) {
1198                 print_statusline("No bracket in top line");
1199                 return;
1200         }
1201         for (i = cur_fline + 1; i < max_fline; i++) {
1202                 if (strchr(flines[i], opp_bracket(bracket)) != NULL) {
1203                         bracket_line = i;
1204                         break;
1205                 }
1206         }
1207         if (bracket_line == -1)
1208                 print_statusline("No matching bracket found");
1209         buffer_line(bracket_line - max_displayed_line);
1210 }
1211
1212 static void match_left_bracket(char bracket)
1213 {
1214         int bracket_line = -1;
1215         int i;
1216
1217         if (strchr(flines[cur_fline + max_displayed_line], bracket) == NULL) {
1218                 print_statusline("No bracket in bottom line");
1219                 return;
1220         }
1221
1222         for (i = cur_fline + max_displayed_line; i >= 0; i--) {
1223                 if (strchr(flines[i], opp_bracket(bracket)) != NULL) {
1224                         bracket_line = i;
1225                         break;
1226                 }
1227         }
1228         if (bracket_line == -1)
1229                 print_statusline("No matching bracket found");
1230         buffer_line(bracket_line);
1231 }
1232 #endif  /* FEATURE_LESS_BRACKETS */
1233
1234 static void keypress_process(int keypress)
1235 {
1236         switch (keypress) {
1237         case KEY_DOWN: case 'e': case 'j': case 0x0d:
1238                 buffer_down(1);
1239                 break;
1240         case KEY_UP: case 'y': case 'k':
1241                 buffer_up(1);
1242                 break;
1243         case PAGE_DOWN: case ' ': case 'z': case 'f':
1244                 buffer_down(max_displayed_line + 1);
1245                 break;
1246         case PAGE_UP: case 'w': case 'b':
1247                 buffer_up(max_displayed_line + 1);
1248                 break;
1249         case 'd':
1250                 buffer_down((max_displayed_line + 1) / 2);
1251                 break;
1252         case 'u':
1253                 buffer_up((max_displayed_line + 1) / 2);
1254                 break;
1255         case KEY_HOME: case 'g': case 'p': case '<': case '%':
1256                 buffer_line(0);
1257                 break;
1258         case KEY_END: case 'G': case '>':
1259                 cur_fline = MAXLINES;
1260                 read_lines();
1261                 buffer_line(cur_fline);
1262                 break;
1263         case 'q': case 'Q':
1264                 less_exit(0);
1265                 break;
1266 #if ENABLE_FEATURE_LESS_MARKS
1267         case 'm':
1268                 add_mark();
1269                 buffer_print();
1270                 break;
1271         case '\'':
1272                 goto_mark();
1273                 buffer_print();
1274                 break;
1275 #endif
1276         case 'r': case 'R':
1277                 buffer_print();
1278                 break;
1279         /*case 'R':
1280                 full_repaint();
1281                 break;*/
1282         case 's':
1283                 save_input_to_file();
1284                 break;
1285         case 'E':
1286                 examine_file();
1287                 break;
1288 #if ENABLE_FEATURE_LESS_FLAGS
1289         case '=':
1290                 m_status_print();
1291                 break;
1292 #endif
1293 #if ENABLE_FEATURE_LESS_REGEXP
1294         case '/':
1295                 option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
1296                 regex_process();
1297                 break;
1298         case 'n':
1299                 goto_match(match_pos + 1);
1300                 break;
1301         case 'N':
1302                 goto_match(match_pos - 1);
1303                 break;
1304         case '?':
1305                 option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
1306                 regex_process();
1307                 break;
1308 #endif
1309 #if ENABLE_FEATURE_LESS_FLAGCS
1310         case '-':
1311                 flag_change();
1312                 buffer_print();
1313                 break;
1314         case '_':
1315                 show_flag_status();
1316                 break;
1317 #endif
1318 #if ENABLE_FEATURE_LESS_BRACKETS
1319         case '{': case '(': case '[':
1320                 match_right_bracket(keypress);
1321                 break;
1322         case '}': case ')': case ']':
1323                 match_left_bracket(keypress);
1324                 break;
1325 #endif
1326         case ':':
1327                 colon_process();
1328                 break;
1329         }
1330
1331         if (isdigit(keypress))
1332                 number_process(keypress);
1333 }
1334
1335 static void sig_catcher(int sig ATTRIBUTE_UNUSED)
1336 {
1337         set_tty_cooked();
1338         exit(1);
1339 }
1340
1341 int less_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1342 int less_main(int argc, char **argv)
1343 {
1344         int keypress;
1345
1346         INIT_G();
1347
1348         /* TODO: -x: do not interpret backspace, -xx: tab also */
1349         /* -xxx: newline also */
1350         /* -w N: assume width N (-xxx -w 32: hex viewer of sorts) */
1351         getopt32(argv, "EMmN~");
1352         argc -= optind;
1353         argv += optind;
1354         num_files = argc;
1355         files = argv;
1356
1357         /* Another popular pager, most, detects when stdout
1358          * is not a tty and turns into cat. This makes sense. */
1359         if (!isatty(STDOUT_FILENO))
1360                 return bb_cat(argv);
1361         kbd_fd = open(CURRENT_TTY, O_RDONLY);
1362         if (kbd_fd < 0)
1363                 return bb_cat(argv);
1364         ndelay_on(kbd_fd);
1365
1366         if (!num_files) {
1367                 if (isatty(STDIN_FILENO)) {
1368                         /* Just "less"? No args and no redirection? */
1369                         bb_error_msg("missing filename");
1370                         bb_show_usage();
1371                 }
1372         } else
1373                 filename = xstrdup(files[0]);
1374
1375         get_terminal_width_height(kbd_fd, &width, &max_displayed_line);
1376         /* 20: two tabstops + 4 */
1377         if (width < 20 || max_displayed_line < 3)
1378                 bb_error_msg_and_die("too narrow here");
1379         max_displayed_line -= 2;
1380
1381         buffer = xmalloc((max_displayed_line+1) * sizeof(char *));
1382         if (option_mask32 & FLAG_TILDE)
1383                 empty_line_marker = "";
1384
1385         bb_signals(BB_SIGS_FATAL, sig_catcher);
1386
1387         tcgetattr(kbd_fd, &term_orig);
1388         term_less = term_orig;
1389         term_less.c_lflag &= ~(ICANON | ECHO);
1390         term_less.c_iflag &= ~(IXON | ICRNL);
1391         /*term_less.c_oflag &= ~ONLCR;*/
1392         term_less.c_cc[VMIN] = 1;
1393         term_less.c_cc[VTIME] = 0;
1394
1395         reinitialize();
1396         while (1) {
1397                 keypress = less_getch(-1); /* -1: do not position cursor */
1398                 keypress_process(keypress);
1399         }
1400 }