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