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