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