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