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