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