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