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