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