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