d7898caab96ee5df2e5555cce18cbe2a7d693e82
[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         regmatch_t match_structs;
674         
675         memset(sub_line, 0, 256);
676         strcpy(line2, line);
677
678         match_found = 0;
679         match_status = regexec(pattern, line2, 1, &match_structs, 0);
680
681         while (match_status == 0) {
682
683                 memset(sub_line, 0, 256);
684
685                 if (match_found == 0)
686                         match_found = 1;
687
688                 line2 = insert_highlights(line2, match_structs.rm_so + prev_eo, match_structs.rm_eo + prev_eo);
689                 if (match_structs.rm_eo + 11 + prev_eo < strlen(line2))
690                         strcat(sub_line, line2 + match_structs.rm_eo + 11 + prev_eo);
691
692                 prev_eo += match_structs.rm_eo + 11;
693                 match_status = regexec(pattern, sub_line, 1, &match_structs, REG_NOTBOL);
694         }
695
696         return line2;
697 }
698
699 static void regex_process(void) {
700
701         char uncomp_regex[100];
702         char current_line[256];
703         int i;
704         int j = 0;
705         regex_t *pattern;
706
707         /* Reset variables */
708         match_lines[0] = -1;
709         match_pos = 0;
710         num_matches = 0;
711         match_found = 0;
712
713         pattern = (regex_t *) malloc(sizeof(regex_t));
714         memset(pattern, 0, sizeof(regex_t));
715
716         /* Get the uncompiled regular expression from the user */
717         clear_line();
718         if (match_backwards)
719                 printf("?");
720         else
721                 printf("/");
722         scanf("%s", uncomp_regex);
723
724         /* Compile the regex and check for errors */
725         xregcomp(pattern, uncomp_regex, 0);
726
727         /* Run the regex on each line of the current file here */
728         for (i = 0; i <= num_flines; i++) {
729                 strcpy(current_line, process_regex_on_line(flines[i], pattern));
730                 flines[i] = (char *) bb_xstrndup(current_line, sizeof(char) * (strlen(current_line)+1));
731
732                 if (match_found) {
733                         match_lines[j] = i;
734                         j++;
735                 }
736         }
737
738         num_matches = j;
739
740         if ((match_lines[0] != -1) && (num_flines > height - 2))
741                 buffer_line(match_lines[0]);
742         else
743                 buffer_init();
744 }
745
746 static void goto_match(int match) {
747
748         /* This goes to a specific match - all line positions of matches are
749            stored within the match_lines[] array. */
750         if ((match < num_matches) && (match >= 0)) {
751                 buffer_line(match_lines[match]);
752                 match_pos = match;
753         }
754 }
755
756 static void search_backwards(void) {
757
758         int current_linepos = line_pos;
759         int i;
760
761         match_backwards = 1;
762         regex_process();
763
764         for (i = 0; i < num_matches; i++) {
765                 if (match_lines[i] > current_linepos) {
766                         buffer_line(match_lines[i - num_back_match]);
767                         break;
768                 }
769         }
770
771         /* Reset variables */
772         match_backwards = 0;
773         num_back_match = 1;
774
775 }
776 #endif
777
778 static void number_process(int first_digit) {
779
780         int i = 1;
781         int num;
782         char num_input[80];
783         char keypress;
784         num_input[0] = first_digit;
785
786         /* Clear the current line, print a prompt, and then print the digit */
787         clear_line();
788         printf(":%c", first_digit);
789
790         /* Receive input until a letter is given */
791         while((num_input[i] = tless_getch()) && isdigit(num_input[i])) {
792                 printf("%c",num_input[i]);
793                 i++;
794         }
795
796         /* Take the final letter out of the digits string */
797         keypress = num_input[i];
798         num_input[i] = '\0';
799         i--;
800         num = atoi(num_input);
801
802         /* We now know the number and the letter entered, so we process them */
803         switch (keypress) {
804                 case KEY_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015':
805                         buffer_down(num);
806                         buffer_print();
807                         break;
808                 case KEY_UP: case 'b': case 'w': case 'y': case 'u':
809                         buffer_up(num);
810                         buffer_print();
811                         break;
812                 case 'g': case '<': case 'G': case '>':
813                         if (num_flines >= height - 2)
814                                 buffer_line(num - 1);
815                         buffer_print();
816                         break;
817                 case 'p': case '%':
818                         buffer_line(reverse_percent(num));
819                         buffer_print();
820                         break;
821 #ifdef CONFIG_FEATURE_LESS_REGEXP
822                 case 'n':
823                         goto_match(match_pos + num - 1);
824                         buffer_print();
825                         break;
826                 case '/':
827                         regex_process();
828                         goto_match(num - 1);
829                         buffer_print();
830                         break;
831                 case '?':
832                         num_back_match = num;
833                         search_backwards();
834                         buffer_print();
835                         break;
836 #endif
837                 default:
838                         break;
839         }
840 }
841
842 #ifdef CONFIG_FEATURE_LESS_FLAGCS
843 static void flag_change(void) {
844
845         int keypress;
846
847         clear_line();
848         printf("-");
849         keypress = tless_getch();
850
851         switch (keypress) {
852                 case 'M':
853                         M_FLAG = !M_FLAG;
854                         break;
855                 case 'm':
856                         m_FLAG = !m_FLAG;
857                         break;
858                 case 'E':
859                         E_FLAG = !E_FLAG;
860                         break;
861                 case '~':
862                         TILDE_FLAG = !TILDE_FLAG;
863                         break;
864                 default:
865                         break;
866         }
867 }
868
869 static void show_flag_status(void) {
870
871         int keypress;
872         int flag_val;
873
874         clear_line();
875         printf("_");
876         keypress = tless_getch();
877
878         switch (keypress) {
879                 case 'M':
880                         flag_val = M_FLAG;
881                         break;
882                 case 'm':
883                         flag_val = m_FLAG;
884                         break;
885                 case '~':
886                         flag_val = TILDE_FLAG;
887                         break;
888                 case 'N':
889                         flag_val = N_FLAG;
890                         break;
891                 case 'E':
892                         flag_val = E_FLAG;
893                         break;
894                 default:
895                         flag_val = 0;
896                         break;
897         }
898
899         clear_line();
900         printf("%s%s%i%s", HIGHLIGHT, "The status of the flag is: ", flag_val != 0, NORMAL);
901 }
902 #endif
903
904 static void full_repaint(void) {
905
906         int temp_line_pos = line_pos;
907         data_readlines();
908         buffer_init();
909         buffer_line(temp_line_pos);
910         buffer_print();
911 }
912
913
914 static void save_input_to_file(void) {
915
916         char current_line[256];
917         int i;
918         FILE *fp;
919
920         clear_line();
921         printf("Log file: ");
922         fgets(current_line, 256, inp);
923         current_line[strlen(current_line) - 1] = '\0';
924         if (strlen(current_line)) {
925                 fp = bb_xfopen(current_line, "w");
926                 for (i = 0; i < num_flines; i++)
927                         fprintf(fp, "%s", flines[i]);
928                 fclose(fp);
929                 buffer_print();
930         }
931         else
932                 printf("%sNo log file%s", HIGHLIGHT, NORMAL);
933 }
934
935 #ifdef CONFIG_FEATURE_LESS_MARKS
936 static void add_mark(void) {
937
938         int letter;
939         int mark_line;
940
941         clear_line();
942         printf("Mark: ");
943         letter = tless_getch();
944
945         if (isalpha(letter)) {
946                 mark_line = line_pos;
947
948                 /* If we exceed 15 marks, start overwriting previous ones */
949                 if (num_marks == 14)
950                         num_marks = 0;
951
952                 mark_lines[num_marks][0] = letter;
953                 mark_lines[num_marks][1] = line_pos;
954                 num_marks++;
955         }
956         else {
957                 clear_line();
958                 printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL);
959         }
960 }
961
962 static void goto_mark(void) {
963
964         int letter;
965         int i;
966
967         clear_line();
968         printf("Go to mark: ");
969         letter = tless_getch();
970         if (isalpha(letter)) {
971                 for (i = 0; i <= num_marks; i++)
972                         if (letter == mark_lines[i][0]) {
973                                 buffer_line(mark_lines[i][1]);
974                                 break;
975                         }
976                 if ((num_marks == 14) && (letter != mark_lines[14][0])) {
977                         clear_line();
978                         printf("%s%s%s", HIGHLIGHT, "Mark not set", NORMAL);
979                 }
980         }
981         else {
982                 clear_line();
983                 printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL);
984         }
985 }
986 #endif
987
988
989 #ifdef CONFIG_FEATURE_LESS_BRACKETS
990
991 static char opp_bracket (char bracket) {
992
993         switch (bracket) {
994                 case '{': case '[':
995                         return bracket + 2;
996                         break;
997                 case '(':
998                         return ')';
999                         break;
1000                 case '}': case ']':
1001                         return bracket - 2;
1002                         break;
1003                 case ')':
1004                         return '(';
1005                         break;
1006                 default:
1007                         return 0;
1008                         break;
1009         }
1010 }
1011
1012 static void match_right_bracket(char bracket) {
1013
1014         int bracket_line = -1;
1015         int i;
1016
1017         if (strchr(flines[line_pos], bracket) == NULL) {
1018                 clear_line();
1019                 printf("%s%s%s", HIGHLIGHT, "No bracket in top line", NORMAL);
1020         }
1021         else {
1022                 for (i = line_pos + 1; i < num_flines; i++) {
1023                         if (strchr(flines[i], opp_bracket(bracket)) != NULL) {
1024                                 bracket_line = i;
1025                                 break;
1026                         }
1027                 }
1028
1029                 if (bracket_line == -1) {
1030                         clear_line();
1031                         printf("%s%s%s", HIGHLIGHT, "No matching bracket found", NORMAL);
1032                 }
1033
1034                 buffer_line(bracket_line - height + 2);
1035                 buffer_print();
1036         }
1037 }
1038
1039 static void match_left_bracket (char bracket) {
1040
1041         int bracket_line = -1;
1042         int i;
1043
1044         if (strchr(flines[line_pos + height - 2], bracket) == NULL) {
1045                 clear_line();
1046                 printf("%s%s%s", HIGHLIGHT, "No bracket in bottom line", NORMAL);
1047                 printf("%s", flines[line_pos + height]);
1048                 sleep(4);
1049         }
1050         else {
1051                 for (i = line_pos + height - 2; i >= 0; i--) {
1052                         if (strchr(flines[i], opp_bracket(bracket)) != NULL) {
1053                                 bracket_line = i;
1054                                 break;
1055                         }
1056                 }
1057
1058                 if (bracket_line == -1) {
1059                         clear_line();
1060                         printf("%s%s%s", HIGHLIGHT, "No matching bracket found", NORMAL);
1061                 }
1062
1063                 buffer_line(bracket_line);
1064                 buffer_print();
1065         }
1066 }
1067
1068 #endif  /* CONFIG_FEATURE_LESS_BRACKETS */
1069
1070 static void keypress_process(int keypress) {
1071         switch (keypress) {
1072                 case KEY_DOWN: case 'e': case 'j': case '\015':
1073                         buffer_down(1);
1074                         buffer_print();
1075                         break;
1076                 case KEY_UP: case 'y': case 'k':
1077                         buffer_up(1);
1078                         buffer_print();
1079                         break;
1080                 case PAGE_DOWN: case ' ': case 'z':
1081                         buffer_down(height - 1);
1082                         buffer_print();
1083                         break;
1084                 case PAGE_UP: case 'w': case 'b':
1085                         buffer_up(height - 1);
1086                         buffer_print();
1087                         break;
1088                 case 'd':
1089                         buffer_down((height - 1) / 2);
1090                         buffer_print();
1091                         break;
1092                 case 'u':
1093                         buffer_up((height - 1) / 2);
1094                         buffer_print();
1095                         break;
1096                 case 'g': case 'p': case '<': case '%':
1097                         buffer_up(num_flines + 1);
1098                         buffer_print();
1099                         break;
1100                 case 'G': case '>':
1101                         buffer_down(num_flines + 1);
1102                         buffer_print();
1103                         break;
1104                 case 'q': case 'Q':
1105                         tless_exit(0);
1106                         break;
1107 #ifdef CONFIG_FEATURE_LESS_MARKS
1108                 case 'm':
1109                         add_mark();
1110                         buffer_print();
1111                         break;
1112                 case '\'':
1113                         goto_mark();
1114                         buffer_print();
1115                         break;
1116 #endif
1117                 case 'r':
1118                         buffer_print();
1119                         break;
1120                 case 'R':
1121                         full_repaint();
1122                         break;
1123                 case 's':
1124                         if (inp_stdin)
1125                                 save_input_to_file();
1126                         break;
1127                 case 'E':
1128                         examine_file();
1129                         break;
1130 #ifdef CONFIG_FEATURE_LESS_FLAGS
1131                 case '=':
1132                         clear_line();
1133                         m_status_print();
1134                         break;
1135 #endif
1136 #ifdef CONFIG_FEATURE_LESS_REGEXP
1137                 case '/':
1138                         regex_process();
1139                         buffer_print();
1140                         break;
1141                 case 'n':
1142                         goto_match(match_pos + 1);
1143                         buffer_print();
1144                         break;
1145                 case 'N':
1146                         goto_match(match_pos - 1);
1147                         buffer_print();
1148                         break;
1149                 case '?':
1150                         search_backwards();
1151                         buffer_print();
1152                         break;
1153 #endif
1154 #ifdef CONFIG_FEATURE_LESS_FLAGCS
1155                 case '-':
1156                         flag_change();
1157                         buffer_print();
1158                         break;
1159                 case '_':
1160                         show_flag_status();
1161                         break;
1162 #endif
1163 #ifdef CONFIG_FEATURE_LESS_BRACKETS
1164                 case '{': case '(': case '[':
1165                         match_right_bracket(keypress);
1166                         break;
1167                 case '}': case ')': case ']':
1168                         match_left_bracket(keypress);
1169                         break;
1170 #endif
1171                 case ':':
1172                         colon_process();
1173                         break;
1174                 default:
1175                         break;
1176         }
1177         if (isdigit(keypress))
1178                 number_process(keypress);
1179 }
1180
1181 int less_main(int argc, char **argv) {
1182
1183         unsigned long flags;
1184         int keypress;
1185
1186         flags =  bb_getopt_ulflags(argc, argv, "EMNm~");
1187         E_FLAG = (flags & 1);
1188         M_FLAG = (flags & 2);
1189         N_FLAG = (flags & 4);
1190         m_FLAG = (flags & 8);
1191         TILDE_FLAG = (flags & 16);
1192
1193         argc -= optind;
1194         argv += optind;
1195         files = argv;
1196         num_files = argc;
1197
1198         if (!num_files) {
1199                 if (ttyname(STDIN_FILENO) == NULL)
1200                         inp_stdin = 1;
1201                 else {
1202                         bb_error_msg("Missing filename");
1203                         bb_show_usage();
1204                 }
1205         }
1206
1207         strcpy(filename, (inp_stdin) ? "stdin" : files[0]);
1208         tty_width_height();
1209         data_readlines();
1210         buffer_init();
1211         buffer_print();
1212
1213         while (1) {
1214                 keypress = tless_getch();
1215                 keypress_process(keypress);
1216         }
1217 }