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