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