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