068c3f3932ca0fe7e6d69fe6473363063dc30912
[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 "busybox.h"
25 #if ENABLE_FEATURE_LESS_REGEXP
26 #include "xregex.h"
27 #endif
28
29
30 /* These are the escape sequences corresponding to special keys */
31 #define REAL_KEY_UP 'A'
32 #define REAL_KEY_DOWN 'B'
33 #define REAL_KEY_RIGHT 'C'
34 #define REAL_KEY_LEFT 'D'
35 #define REAL_PAGE_UP '5'
36 #define REAL_PAGE_DOWN '6'
37 #define REAL_KEY_HOME '7'
38 #define REAL_KEY_END '8'
39
40 /* These are the special codes assigned by this program to the special keys */
41 #define KEY_UP 20
42 #define KEY_DOWN 21
43 #define KEY_RIGHT 22
44 #define KEY_LEFT 23
45 #define PAGE_UP 24
46 #define PAGE_DOWN 25
47 #define KEY_HOME 26
48 #define KEY_END 27
49
50 /* The escape codes for highlighted and normal text */
51 #define HIGHLIGHT "\033[7m"
52 #define NORMAL "\033[0m"
53 /* The escape code to clear the screen */
54 #define CLEAR "\033[H\033[J"
55 /* The escape code to clear to end of line */
56 #define CLEAR_2_EOL "\033[K"
57
58 #define MAXLINES CONFIG_FEATURE_LESS_MAXLINES
59
60 static int height;
61 static int width;
62 static char **files;
63 static char *filename;
64 static const char **buffer;
65 static char **flines;
66 static int current_file = 1;
67 static int line_pos;
68 static int num_flines;
69 static int num_files = 1;
70 static const char *empty_line_marker = "~";
71
72 /* Command line options */
73 #define FLAG_E 1
74 #define FLAG_M (1<<1)
75 #define FLAG_m (1<<2)
76 #define FLAG_N (1<<3)
77 #define FLAG_TILDE (1<<4)
78 /* hijack command line options variable for internal state vars */
79 #define LESS_STATE_MATCH_BACKWARDS (1<<6)
80
81 #if ENABLE_FEATURE_LESS_MARKS
82 static int mark_lines[15][2];
83 static int num_marks;
84 #endif
85
86 #if ENABLE_FEATURE_LESS_REGEXP
87 static int *match_lines;
88 static int match_pos;
89 static int num_matches;
90 static regex_t pattern;
91 static int pattern_valid;
92 #endif
93
94 /* Needed termios structures */
95 static struct termios term_orig, term_vi;
96
97 /* File pointer to get input from */
98 static FILE *inp;
99
100 /* Reset terminal input to normal */
101 static void set_tty_cooked(void)
102 {
103         fflush(stdout);
104         tcsetattr(fileno(inp), TCSANOW, &term_orig);
105 }
106
107 /* Exit the program gracefully */
108 static void tless_exit(int code)
109 {
110         /* TODO: We really should save the terminal state when we start,
111                  and restore it when we exit. Less does this with the
112                  "ti" and "te" termcap commands; can this be done with
113                  only termios.h? */
114
115         putchar('\n');
116         fflush_stdout_and_exit(code);
117 }
118
119 /* Grab a character from input without requiring the return key. If the
120    character is ASCII \033, get more characters and assign certain sequences
121    special return codes. Note that this function works best with raw input. */
122 static int tless_getch(void)
123 {
124         int input;
125         /* Set terminal input to raw mode (taken from vi.c) */
126         tcsetattr(fileno(inp), TCSANOW, &term_vi);
127  again:
128         input = getc(inp);
129         /* Detect escape sequences (i.e. arrow keys) and handle
130            them accordingly */
131
132         if (input == '\033' && getc(inp) == '[') {
133                 unsigned i;
134                 input = getc(inp);
135                 set_tty_cooked();
136
137                 i = input - REAL_KEY_UP;
138                 if (i < 4)
139                         return 20 + i;
140                 i = input - REAL_PAGE_UP;
141                 if (i < 4)
142                         return 24 + i;
143                 return 0; /* ?? */
144         }
145         /* Reject almost all control chars */
146         if (input < ' ' && input != 0x0d && input != 8) goto again;
147         set_tty_cooked();
148         return input;
149 }
150
151 static char* tless_gets(void)
152 {
153         int c;
154         int i = 0;
155         char *result = xzalloc(1);
156         while (1) {
157                 c = tless_getch();
158                 if (c == 0x0d)
159                         return result;
160                 if (c == 0x7f) c = 8;
161                 if (c == 8 && i) {
162                         printf("\x8 \x8");
163                         i--;
164                 }
165                 if (c < ' ')
166                         continue;
167                 putchar(c);
168                 result[i++] = c;
169                 result = xrealloc(result, i+1);
170                 result[i] = '\0';
171                 if (i >= width-1) return result; 
172         }
173 }
174
175 /* Move the cursor to a position (x,y), where (0,0) is the
176    top-left corner of the console */
177 static void move_cursor(int line, int row)
178 {
179         printf("\033[%u;%uH", line, row);
180 }
181
182 static void clear_line(void)
183 {
184         printf("\033[%u;0H" CLEAR_2_EOL, height);
185 }
186
187 static void print_hilite(const char *str)
188 {
189         printf(HIGHLIGHT"%s"NORMAL, str);
190 }
191
192 static void data_readlines(void)
193 {
194         unsigned i;
195         unsigned n = 1;
196         int w = width;
197         /* "remained unused space" in a line (0 if line fills entire width) */
198         int rem, last_rem = 1; /* "not 0" */
199         char *current_line, *p;
200         FILE *fp;
201
202         fp = filename ? xfopen(filename, "r") : stdin;
203         flines = NULL;
204         if (option_mask32 & FLAG_N) {
205                 w -= 6;
206         }
207         for (i = 0; !feof(fp) && i <= MAXLINES; i++) {
208                 flines = xrealloc(flines, (i+1) * sizeof(char *));
209                 current_line = xmalloc(w);
210  again:
211                 p = current_line;
212                 rem = w - 1;
213                 do {
214                         int c = getc(fp);
215                         if (c == EOF) break;
216                         if (c == '\n') break;
217                         /* NUL is substituted by '\n'! */
218                         if (c == '\0') c = '\n';
219                         *p++ = c;
220                 } while (--rem);
221                 *p = '\0';
222                 if (fp != stdin)
223                         die_if_ferror(fp, filename);
224
225                 /* Corner case: linewrap with only "" wrapping to next line */
226                 /* Looks ugly on screen, so we do not store this empty line */
227                 if (!last_rem && !current_line[0]) {
228                         last_rem = 1; /* "not 0" */
229                         n++;
230                         goto again;
231                 }
232                 last_rem = rem;
233                 if (option_mask32 & FLAG_N) {
234                         flines[i] = xasprintf((n <= 99999) ? "%5u %s" : "%05u %s",
235                                                 n % 100000, current_line);
236                         free(current_line);
237                         if (rem)
238                                 n++;
239                 } else {
240                         flines[i] = xrealloc(current_line, strlen(current_line)+1);
241                 }
242         }
243         num_flines = i - 1; /* buggie: 'num_flines' must be 'max_fline' */
244
245         /* Reset variables for a new file */
246
247         line_pos = 0;
248
249         fclose(fp);
250 }
251
252 #if ENABLE_FEATURE_LESS_FLAGS
253
254 /* Interestingly, writing calc_percent as a function and not a prototype saves around 32 bytes
255  * on my build. */
256 static int calc_percent(void)
257 {
258         return ((100 * (line_pos + height - 2) / num_flines) + 1);
259 }
260
261 /* Print a status line if -M was specified */
262 static void m_status_print(void)
263 {
264         int percentage;
265
266         if (!line_pos) {
267                 if (num_files > 1) {
268                         printf(HIGHLIGHT"%s (file %i of %i) lines %i-%i/%i ",
269                                 filename, current_file, num_files,
270                                 line_pos + 1, line_pos + height - 1, num_flines + 1);
271                 } else {
272                         printf(HIGHLIGHT"%s lines %i-%i/%i ",
273                                 filename, line_pos + 1, line_pos + height - 1,
274                                 num_flines + 1);
275                 }
276         } else {
277                 printf(HIGHLIGHT" %s lines %i-%i/%i ", filename,
278                         line_pos + 1, line_pos + height - 1, num_flines + 1);
279         }
280
281         if (line_pos >= num_flines - height + 2) {
282                 printf("(END) "NORMAL);
283                 if (num_files > 1 && current_file != num_files)
284                         printf(HIGHLIGHT"- Next: %s"NORMAL, files[current_file]);
285         } else {
286                 percentage = calc_percent();
287                 printf("%i%% "NORMAL, percentage);
288         }
289 }
290
291 /* Print a status line if -m was specified */
292 static void medium_status_print(void)
293 {
294         int percentage;
295         percentage = calc_percent();
296
297         if (!line_pos)
298                 printf(HIGHLIGHT"%s %i%%"NORMAL, filename, percentage);
299         else if (line_pos == num_flines - height + 2)
300                 print_hilite("(END)");
301         else
302                 printf(HIGHLIGHT"%i%%"NORMAL, percentage);
303 }
304 #endif
305
306 /* Print the status line */
307 static void status_print(void)
308 {
309         /* Change the status if flags have been set */
310 #if ENABLE_FEATURE_LESS_FLAGS
311         if (option_mask32 & FLAG_M)
312                 m_status_print();
313         else if (option_mask32 & FLAG_m)
314                 medium_status_print();
315         /* No flags set */
316         else {
317 #endif
318                 if (!line_pos) {
319                         print_hilite(filename);
320                         if (num_files > 1)
321                                 printf(HIGHLIGHT"(file %i of %i)"NORMAL,
322                                         current_file, num_files);
323                 } else if (line_pos == num_flines - height + 2) {
324                         print_hilite("(END) ");
325                         if (num_files > 1 && current_file != num_files)
326                                 printf(HIGHLIGHT"- Next: %s"NORMAL, files[current_file]);
327                 } else {
328                         putchar(':');
329                 }
330 #if ENABLE_FEATURE_LESS_FLAGS
331         }
332 #endif
333 }
334
335 static char controls[] =
336         /**/"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
337         "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
338         "\x7f\x9b"; /* DEL and infamous Meta-ESC :( */
339 static char ctrlconv[] =
340         /* Note that on input NUL is converted to '\n' ('\x0a') */
341         /* Therefore we subst '\n' with '@', not 'J' */
342         "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x40\x4b\x4c\x4d\x4e\x4f"
343         "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f";
344
345 static void print_found(const char *line)
346 {
347         int match_status;
348         int eflags;
349         char *growline;
350         regmatch_t match_structs;
351
352         char buf[width];
353         const char *str = line;
354         char *p = buf;
355         size_t n;
356
357         while (*str) {
358                 n = strcspn(str, controls);
359                 if (n) {
360                         if (!str[n]) break;
361                         memcpy(p, str, n);
362                         p += n;
363                         str += n;
364                 }
365                 n = strspn(str, controls);
366                 memset(p, '.', n);
367                 p += n;
368                 str += n;
369         }
370         strcpy(p, str);
371
372         /* buf[] holds quarantined version of str */
373
374         /* Each part of the line that matches has the HIGHLIGHT
375            and NORMAL escape sequences placed around it.
376            NB: we regex against line, but insert text
377            from quarantined copy (buf[]) */
378         str = buf;
379         growline = NULL;
380         eflags = 0;
381         goto start;
382
383         while (match_status == 0) {
384                 char *new = xasprintf("%s%.*s"HIGHLIGHT"%.*s"NORMAL,
385                                 growline ? : "",
386                                 match_structs.rm_so, str,
387                                 match_structs.rm_eo - match_structs.rm_so,
388                                                 str + match_structs.rm_so);
389                 free(growline); growline = new;
390                 str += match_structs.rm_eo;
391                 line += match_structs.rm_eo;
392                 eflags = REG_NOTBOL;
393  start:
394                 /* Most of the time doesn't find the regex, optimize for that */
395                 match_status = regexec(&pattern, line, 1, &match_structs, eflags);
396         }
397
398         if (!growline) {
399                 printf("%s"CLEAR_2_EOL"\n", str);
400                 return;
401         }
402         printf("%s%s"CLEAR_2_EOL"\n", growline, str);
403         free(growline);
404 }
405
406 static void print_ascii(const char *str)
407 {
408         char buf[width];
409         char *p;
410         size_t n;
411
412         while (*str) {
413                 n = strcspn(str, controls);
414                 if (n) {
415                         if (!str[n]) break;
416                         printf("%.*s", n, str);
417                         str += n;
418                 }
419                 n = strspn(str, controls);
420                 p = buf;
421                 do {
422                         if (*str == 0x7f)
423                                 *p++ = '?';
424                         else if (*str == 0x9b)
425                         /* VT100's CSI, aka Meta-ESC. Who's inventor? */
426                         /* I want to know who committed this sin */
427                                 *p++ = '{';
428                         else
429                                 *p++ = ctrlconv[(unsigned char)*str];
430                         str++;
431                 } while (--n);
432                 *p = '\0';
433                 print_hilite(buf);
434         }
435         printf("%s"CLEAR_2_EOL"\n", str);
436 }
437
438 /* Print the buffer */
439 static void buffer_print(void)
440 {
441         int i;
442
443         move_cursor(0, 0);
444         for (i = 0; i < height - 1; i++)
445                 if (pattern_valid)
446                         print_found(buffer[i]);
447                 else
448                         print_ascii(buffer[i]);
449         fputs(CLEAR_2_EOL, stdout); /* clears status line */
450         status_print();
451 }
452
453 /* Initialise the buffer */
454 static void buffer_init(void)
455 {
456         int i;
457
458         /* Fill the buffer until the end of the file or the
459            end of the buffer is reached */
460         for (i = 0; i < height - 1 && i <= num_flines; i++) {
461                 buffer[i] = flines[i];
462         }
463
464         /* If the buffer still isn't full, fill it with blank lines */
465         for (; i < height - 1; i++) {
466                 buffer[i] = empty_line_marker;
467         }
468 }
469
470 /* Move the buffer up and down in the file in order to scroll */
471 static void buffer_down(int nlines)
472 {
473         int i;
474
475         if (line_pos + (height - 3) + nlines < num_flines) {
476                 line_pos += nlines;
477                 for (i = 0; i < (height - 1); i++) {
478                         buffer[i] = flines[line_pos + i];
479                 }
480         } else {
481                 /* As the number of lines requested was too large, we just move
482                 to the end of the file */
483                 while (line_pos + (height - 3) + 1 < num_flines) {
484                         line_pos += 1;
485                         for (i = 0; i < (height - 1); i++) {
486                                 buffer[i] = flines[line_pos + i];
487                         }
488                 }
489         }
490
491         /* We exit if the -E flag has been set */
492         if ((option_mask32 & FLAG_E) && (line_pos + (height - 2) == num_flines))
493                 tless_exit(0);
494 }
495
496 static void buffer_up(int nlines)
497 {
498         int i;
499
500         line_pos -= nlines;
501         if (line_pos < 0) line_pos = 0;
502         for (i = 0; i < height - 1; i++) {
503                 if (line_pos + i <= num_flines) {
504                         buffer[i] = flines[line_pos + i];
505                 } else {
506                         buffer[i] = empty_line_marker;
507                 }
508         }
509 }
510
511 static void buffer_line(int linenum)
512 {
513         int i;
514
515         if (linenum < 0 || linenum > num_flines) {
516                 clear_line();
517                 printf(HIGHLIGHT"%s%u"NORMAL, "Cannot seek to line ", linenum + 1);
518                 return;
519         }
520
521         for (i = 0; i < height - 1; i++) {
522                 if (linenum + i <= num_flines)
523                         buffer[i] = flines[linenum + i];
524                 else {
525                         buffer[i] = empty_line_marker;
526                 }
527         }
528         line_pos = linenum;
529         buffer_print();
530 }
531
532 /* Reinitialise everything for a new file - free the memory and start over */
533 static void reinitialise(void)
534 {
535         int i;
536
537         for (i = 0; i <= num_flines; i++)
538                 free(flines[i]);
539         free(flines);
540
541         data_readlines();
542         buffer_init();
543         buffer_print();
544 }
545
546 static void examine_file(void)
547 {
548         clear_line();
549         printf("Examine: ");
550         free(filename);
551         filename = tless_gets();
552         /* files start by = argv. why we assume that argv is infinitely long??
553         files[num_files] = filename;
554         current_file = num_files + 1;
555         num_files++; */
556         files[0] = filename;
557         current_file = 1;
558         reinitialise();
559 }
560
561 /* This function changes the file currently being paged. direction can be one of the following:
562  * -1: go back one file
563  *  0: go to the first file
564  *  1: go forward one file
565 */
566 static void change_file(int direction)
567 {
568         if (current_file != ((direction > 0) ? num_files : 1)) {
569                 current_file = direction ? current_file + direction : 1;
570                 free(filename);
571                 filename = xstrdup(files[current_file - 1]);
572                 reinitialise();
573         } else {
574                 clear_line();
575                 print_hilite((direction > 0) ? "No next file" : "No previous file");
576         }
577 }
578
579 static void remove_current_file(void)
580 {
581         int i;
582
583         if (current_file != 1) {
584                 change_file(-1);
585                 for (i = 3; i <= num_files; i++)
586                         files[i - 2] = files[i - 1];
587                 num_files--;
588                 buffer_print();
589         } else {
590                 change_file(1);
591                 for (i = 2; i <= num_files; i++)
592                         files[i - 2] = files[i - 1];
593                 num_files--;
594                 current_file--;
595                 buffer_print();
596         }
597 }
598
599 static void colon_process(void)
600 {
601         int keypress;
602
603         /* Clear the current line and print a prompt */
604         clear_line();
605         printf(" :");
606
607         keypress = tless_getch();
608         switch (keypress) {
609                 case 'd':
610                         remove_current_file();
611                         break;
612                 case 'e':
613                         examine_file();
614                         break;
615 #if ENABLE_FEATURE_LESS_FLAGS
616                 case 'f':
617                         clear_line();
618                         m_status_print();
619                         break;
620 #endif
621                 case 'n':
622                         change_file(1);
623                         break;
624                 case 'p':
625                         change_file(-1);
626                         break;
627                 case 'q':
628                         tless_exit(0);
629                         break;
630                 case 'x':
631                         change_file(0);
632                         break;
633                 default:
634                         break;
635         }
636 }
637
638 static int normalize_match_pos(int match)
639 {
640         match_pos = match;
641         if (match < 0)
642                 return (match_pos = 0);
643         if (match >= num_matches)
644 {
645                 match_pos = num_matches - 1;
646 }
647         return match_pos;
648 }
649
650 #if ENABLE_FEATURE_LESS_REGEXP
651 static void goto_match(int match)
652 {
653         buffer_line(match_lines[normalize_match_pos(match)]);
654 }
655
656 static void regex_process(void)
657 {
658         char *uncomp_regex, *err;
659
660         /* Reset variables */
661         match_lines = xrealloc(match_lines, sizeof(int));
662         match_lines[0] = -1;
663         match_pos = 0;
664         num_matches = 0;
665         if (pattern_valid) {
666                 regfree(&pattern);
667                 pattern_valid = 0;
668         }
669
670         /* Get the uncompiled regular expression from the user */
671         clear_line();
672         putchar((option_mask32 & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/');
673         uncomp_regex = tless_gets();
674         if (/*!uncomp_regex ||*/ !uncomp_regex[0]) {
675                 free(uncomp_regex);
676                 buffer_print();
677                 return;
678         }
679
680         /* Compile the regex and check for errors */
681         err = regcomp_or_errmsg(&pattern, uncomp_regex, 0);
682         free(uncomp_regex);
683         if (err) {
684                 clear_line();
685                 fputs(err, stdout);
686                 free(err);
687                 return;
688         }
689         pattern_valid = 1;
690
691         /* Run the regex on each line of the current file */
692         for (match_pos = 0; match_pos <= num_flines; match_pos++) {
693                 if (regexec(&pattern, flines[match_pos], 0, NULL, 0) == 0) {
694                         match_lines = xrealloc(match_lines, (num_matches+1) * sizeof(int));
695                         match_lines[num_matches++] = match_pos;
696                 }
697         }
698
699         if (num_matches == 0 || num_flines <= height - 2) {
700                 buffer_print();
701                 return;
702         }
703         for (match_pos = 0; match_pos < num_matches; match_pos++) {
704                 if (match_lines[match_pos] > line_pos)
705                         break;
706         }
707         if (option_mask32 & LESS_STATE_MATCH_BACKWARDS) match_pos--;
708         buffer_line(match_lines[normalize_match_pos(match_pos)]);
709 }
710 #endif
711
712 static void number_process(int first_digit)
713 {
714         int i = 1;
715         int num;
716         char num_input[sizeof(int)*4]; /* more than enough */
717         char keypress;
718
719         num_input[0] = first_digit;
720
721         /* Clear the current line, print a prompt, and then print the digit */
722         clear_line();
723         printf(":%c", first_digit);
724
725         /* Receive input until a letter is given */
726         while (i < sizeof(num_input)-1) {
727                 num_input[i] = tless_getch();
728                 if (!num_input[i] || !isdigit(num_input[i]))
729                         break;
730                 putchar(num_input[i]);
731                 i++;
732         }
733
734         /* Take the final letter out of the digits string */
735         keypress = num_input[i];
736         num_input[i] = '\0';
737         num = bb_strtou(num_input, NULL, 10);
738         /* on format error, num == -1 */
739         if (num < 1 || num > MAXLINES) {
740                 buffer_print();
741                 return;
742         }
743
744         /* We now know the number and the letter entered, so we process them */
745         switch (keypress) {
746                 case KEY_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015':
747                         buffer_down(num);
748                         break;
749                 case KEY_UP: case 'b': case 'w': case 'y': case 'u':
750                         buffer_up(num);
751                         break;
752                 case 'g': case '<': case 'G': case '>':
753                         if (num_flines >= height - 2)
754                                 buffer_line(num - 1);
755                         break;
756                 case 'p': case '%':
757                         buffer_line(((num / 100) * num_flines) - 1);
758                         break;
759 #if ENABLE_FEATURE_LESS_REGEXP
760                 case 'n':
761                         goto_match(match_pos + num);
762                         break;
763                 case '/':
764                         option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
765                         regex_process();
766                         break;
767                 case '?':
768                         option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
769                         regex_process();
770                         break;
771 #endif
772                 default:
773                         break;
774         }
775 }
776
777 #if ENABLE_FEATURE_LESS_FLAGCS
778 static void flag_change(void)
779 {
780         int keypress;
781
782         clear_line();
783         putchar('-');
784         keypress = tless_getch();
785
786         switch (keypress) {
787                 case 'M':
788                         option_mask32 ^= FLAG_M;
789                         break;
790                 case 'm':
791                         option_mask32 ^= FLAG_m;
792                         break;
793                 case 'E':
794                         option_mask32 ^= FLAG_E;
795                         break;
796                 case '~':
797                         option_mask32 ^= FLAG_TILDE;
798                         break;
799                 default:
800                         break;
801         }
802 }
803
804 static void show_flag_status(void)
805 {
806         int keypress;
807         int flag_val;
808
809         clear_line();
810         putchar('_');
811         keypress = tless_getch();
812
813         switch (keypress) {
814                 case 'M':
815                         flag_val = option_mask32 & FLAG_M;
816                         break;
817                 case 'm':
818                         flag_val = option_mask32 & FLAG_m;
819                         break;
820                 case '~':
821                         flag_val = option_mask32 & FLAG_TILDE;
822                         break;
823                 case 'N':
824                         flag_val = option_mask32 & FLAG_N;
825                         break;
826                 case 'E':
827                         flag_val = option_mask32 & FLAG_E;
828                         break;
829                 default:
830                         flag_val = 0;
831                         break;
832         }
833
834         clear_line();
835         printf(HIGHLIGHT"%s%u"NORMAL, "The status of the flag is: ", flag_val != 0);
836 }
837 #endif
838
839 static void full_repaint(void)
840 {
841         int temp_line_pos = line_pos;
842         data_readlines();
843         buffer_init();
844         buffer_line(temp_line_pos);
845 }
846
847
848 static void save_input_to_file(void)
849 {
850         char *current_line;
851         int i;
852         FILE *fp;
853
854         clear_line();
855         printf("Log file: ");
856         current_line = tless_gets();
857         if (strlen(current_line) > 0) {
858                 fp = fopen(current_line, "w");
859                 free(current_line);
860                 if (!fp) {
861                         print_hilite("Error opening log file");
862                         return;
863                 }
864                 for (i = 0; i < num_flines; i++)
865                         fprintf(fp, "%s\n", flines[i]);
866                 fclose(fp);
867                 buffer_print();
868                 return;
869         }
870         free(current_line);
871         print_hilite("No log file");
872 }
873
874 #if ENABLE_FEATURE_LESS_MARKS
875 static void add_mark(void)
876 {
877         int letter;
878
879         clear_line();
880         printf("Mark: ");
881         letter = tless_getch();
882
883         if (isalpha(letter)) {
884
885                 /* If we exceed 15 marks, start overwriting previous ones */
886                 if (num_marks == 14)
887                         num_marks = 0;
888
889                 mark_lines[num_marks][0] = letter;
890                 mark_lines[num_marks][1] = line_pos;
891                 num_marks++;
892         } else {
893                 clear_line();
894                 print_hilite("Invalid mark letter");
895         }
896 }
897
898 static void goto_mark(void)
899 {
900         int letter;
901         int i;
902
903         clear_line();
904         printf("Go to mark: ");
905         letter = tless_getch();
906         clear_line();
907
908         if (isalpha(letter)) {
909                 for (i = 0; i <= num_marks; i++)
910                         if (letter == mark_lines[i][0]) {
911                                 buffer_line(mark_lines[i][1]);
912                                 break;
913                         }
914                 if (num_marks == 14 && letter != mark_lines[14][0])
915                         print_hilite("Mark not set");
916         } else
917                 print_hilite("Invalid mark letter");
918 }
919 #endif
920
921
922 #if ENABLE_FEATURE_LESS_BRACKETS
923
924 static char opp_bracket(char bracket)
925 {
926         switch (bracket) {
927                 case '{': case '[':
928                         return bracket + 2;
929                 case '(':
930                         return ')';
931                 case '}': case ']':
932                         return bracket - 2;
933                 case ')':
934                         return '(';
935                 default:
936                         return 0;
937         }
938 }
939
940 static void match_right_bracket(char bracket)
941 {
942         int bracket_line = -1;
943         int i;
944
945         clear_line();
946
947         if (strchr(flines[line_pos], bracket) == NULL) {
948                 print_hilite("No bracket in top line");
949                 return;
950         }
951         for (i = line_pos + 1; i < num_flines; i++) {
952                 if (strchr(flines[i], opp_bracket(bracket)) != NULL) {
953                         bracket_line = i;
954                         break;
955                 }
956         }
957         if (bracket_line == -1)
958         print_hilite("No matching bracket found");
959         buffer_line(bracket_line - height + 2);
960 }
961
962 static void match_left_bracket(char bracket)
963 {
964         int bracket_line = -1;
965         int i;
966
967         clear_line();
968
969         if (strchr(flines[line_pos + height - 2], bracket) == NULL) {
970                 print_hilite("No bracket in bottom line");
971                 /* ?? */
972                 /*printf("%s", flines[line_pos + height]);*/
973                 /*sleep(4);*/
974                 return;
975         }
976
977         for (i = line_pos + height - 2; i >= 0; i--) {
978                 if (strchr(flines[i], opp_bracket(bracket)) != NULL) {
979                         bracket_line = i;
980                         break;
981                 }
982         }
983         if (bracket_line == -1)
984                 print_hilite("No matching bracket found");
985         buffer_line(bracket_line);
986 }
987
988 #endif  /* FEATURE_LESS_BRACKETS */
989
990 static void keypress_process(int keypress)
991 {
992         switch (keypress) {
993                 case KEY_DOWN: case 'e': case 'j': case 0x0d:
994                         buffer_down(1);
995                         buffer_print();
996                         break;
997                 case KEY_UP: case 'y': case 'k':
998                         buffer_up(1);
999                         buffer_print();
1000                         break;
1001                 case PAGE_DOWN: case ' ': case 'z':
1002                         buffer_down(height - 1);
1003                         buffer_print();
1004                         break;
1005                 case PAGE_UP: case 'w': case 'b':
1006                         buffer_up(height - 1);
1007                         buffer_print();
1008                         break;
1009                 case 'd':
1010                         buffer_down((height - 1) / 2);
1011                         buffer_print();
1012                         break;
1013                 case 'u':
1014                         buffer_up((height - 1) / 2);
1015                         buffer_print();
1016                         break;
1017                 case KEY_HOME: case 'g': case 'p': case '<': case '%':
1018                         buffer_line(0);
1019                         break;
1020                 case KEY_END: case 'G': case '>':
1021                         buffer_line(num_flines - height + 2);
1022                         break;
1023                 case 'q': case 'Q':
1024                         tless_exit(0);
1025                         break;
1026 #if ENABLE_FEATURE_LESS_MARKS
1027                 case 'm':
1028                         add_mark();
1029                         buffer_print();
1030                         break;
1031                 case '\'':
1032                         goto_mark();
1033                         buffer_print();
1034                         break;
1035 #endif
1036                 case 'r':
1037                         buffer_print();
1038                         break;
1039                 case 'R':
1040                         full_repaint();
1041                         break;
1042                 case 's':
1043                         save_input_to_file();
1044                         break;
1045                 case 'E':
1046                         examine_file();
1047                         break;
1048 #if ENABLE_FEATURE_LESS_FLAGS
1049                 case '=':
1050                         clear_line();
1051                         m_status_print();
1052                         break;
1053 #endif
1054 #if ENABLE_FEATURE_LESS_REGEXP
1055                 case '/':
1056                         option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
1057                         regex_process();
1058                         break;
1059                 case 'n':
1060                         goto_match(match_pos + 1);
1061                         break;
1062                 case 'N':
1063                         goto_match(match_pos - 1);
1064                         break;
1065                 case '?':
1066                         option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
1067                         regex_process();
1068                         break;
1069 #endif
1070 #if ENABLE_FEATURE_LESS_FLAGCS
1071                 case '-':
1072                         flag_change();
1073                         buffer_print();
1074                         break;
1075                 case '_':
1076                         show_flag_status();
1077                         break;
1078 #endif
1079 #if ENABLE_FEATURE_LESS_BRACKETS
1080                 case '{': case '(': case '[':
1081                         match_right_bracket(keypress);
1082                         break;
1083                 case '}': case ')': case ']':
1084                         match_left_bracket(keypress);
1085                         break;
1086 #endif
1087                 case ':':
1088                         colon_process();
1089                         break;
1090                 default:
1091                         break;
1092         }
1093
1094         if (isdigit(keypress))
1095                 number_process(keypress);
1096 }
1097
1098 static void sig_catcher(int sig ATTRIBUTE_UNUSED)
1099 {
1100         set_tty_cooked();
1101         exit(1);
1102 }
1103
1104 int less_main(int argc, char **argv)
1105 {
1106         int keypress;
1107
1108         getopt32(argc, argv, "EMmN~");
1109         argc -= optind;
1110         argv += optind;
1111         files = argv;
1112         num_files = argc;
1113
1114         /* Another popular pager, most, detects when stdout
1115          * is not a tty and turns into cat. This makes sense. */
1116         if (!isatty(STDOUT_FILENO))
1117                 return bb_cat(argv);
1118
1119         if (!num_files) {
1120                 if (isatty(STDIN_FILENO)) {
1121                         /* Just "less"? No args and no redirection? */
1122                         bb_error_msg("missing filename");
1123                         bb_show_usage();
1124                 }
1125         } else
1126                 filename = xstrdup(files[0]);
1127
1128         inp = xfopen(CURRENT_TTY, "r");
1129
1130         get_terminal_width_height(fileno(inp), &width, &height);
1131         if (width < 10 || height < 3)
1132                 bb_error_msg_and_die("too narrow here");
1133
1134         buffer = xmalloc(height * sizeof(char *));
1135         if (option_mask32 & FLAG_TILDE)
1136                 empty_line_marker = "";
1137
1138         data_readlines();
1139
1140         tcgetattr(fileno(inp), &term_orig);
1141         signal(SIGTERM, sig_catcher);
1142         signal(SIGINT, sig_catcher);
1143         term_vi = term_orig;
1144         term_vi.c_lflag &= (~ICANON & ~ECHO);
1145         term_vi.c_iflag &= (~IXON & ~ICRNL);
1146         term_vi.c_oflag &= (~ONLCR);
1147         term_vi.c_cc[VMIN] = 1;
1148         term_vi.c_cc[VTIME] = 0;
1149
1150         buffer_init();
1151         buffer_print();
1152
1153         while (1) {
1154                 keypress = tless_getch();
1155                 keypress_process(keypress);
1156         }
1157 }