As usual, I forgot "svn del"...
[oweals/busybox.git] / miscutils / less.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Mini less implementation for busybox
4  *
5  * Copyright (C) 2005 by Rob Sullivan <cogito.ergo.cogito@gmail.com>
6  *
7  * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
8  */
9
10 /*
11  *      This program needs a lot of development, so consider it in a beta stage
12  *      at best.
13  *
14  *      TODO:
15  *      - Add more regular expression support - search modifiers, certain matches, etc.
16  *      - Add more complex bracket searching - currently, nested brackets are
17  *      not considered.
18  *      - Add support for "F" as an input. This causes less to act in
19  *      a similar way to tail -f.
20  *      - Check for binary files, and prompt the user if a binary file
21  *      is detected.
22  *      - Allow horizontal scrolling. Currently, lines simply continue onto
23  *      the next line, per the terminal's discretion
24  *
25  *      Notes:
26  *      - filename is an array and not a pointer because that avoids all sorts
27  *      of complications involving the fact that something that is pointed to
28  *      will be changed if the pointer is changed.
29  *      - the inp file pointer is used so that keyboard input works after
30  *      redirected input has been read from stdin
31 */
32
33 #include "busybox.h"
34
35 #ifdef CONFIG_FEATURE_LESS_REGEXP
36 #include "xregex.h"
37 #endif
38
39
40 /* These are the escape sequences corresponding to special keys */
41 #define REAL_KEY_UP 'A'
42 #define REAL_KEY_DOWN 'B'
43 #define REAL_KEY_RIGHT 'C'
44 #define REAL_KEY_LEFT 'D'
45 #define REAL_PAGE_UP '5'
46 #define REAL_PAGE_DOWN '6'
47
48 /* These are the special codes assigned by this program to the special keys */
49 #define PAGE_UP 20
50 #define PAGE_DOWN 21
51 #define KEY_UP 22
52 #define KEY_DOWN 23
53 #define KEY_RIGHT 24
54 #define KEY_LEFT 25
55
56 /* The escape codes for highlighted and normal text */
57 #define HIGHLIGHT "\033[7m"
58 #define NORMAL "\033[0m"
59
60 /* The escape code to clear the screen */
61 #define CLEAR "\033[H\033[J"
62
63 /* Maximum number of lines in a file */
64 #define MAXLINES 10000
65
66 static int height;
67 static int width;
68 static char **files;
69 static char filename[256];
70 static char **buffer;
71 static char **flines;
72 static int current_file = 1;
73 static int line_pos;
74 static int num_flines;
75 static int num_files = 1;
76 static int past_eof;
77
78 /* Command line options */
79 static unsigned flags;
80 #define FLAG_E 1
81 #define FLAG_M (1<<1)
82 #define FLAG_m (1<<2)
83 #define FLAG_N (1<<3)
84 #define FLAG_TILDE (1<<4)
85
86 /* This is needed so that program behaviour changes when input comes from
87    stdin */
88 static int inp_stdin;
89
90 #ifdef CONFIG_FEATURE_LESS_MARKS
91 static int mark_lines[15][2];
92 static int num_marks;
93 #endif
94
95 #ifdef CONFIG_FEATURE_LESS_REGEXP
96 static int match_found;
97 static int *match_lines;
98 static int match_pos;
99 static int num_matches;
100 static int match_backwards;
101 static regex_t old_pattern;
102 #endif
103
104 /* Needed termios structures */
105 static struct termios term_orig, term_vi;
106
107 /* File pointer to get input from */
108 static FILE *inp;
109
110 /* Reset terminal input to normal */
111 static void set_tty_cooked(void)
112 {
113         fflush(stdout);
114         tcsetattr(fileno(inp), TCSANOW, &term_orig);
115 }
116
117 /* Set terminal input to raw mode  (taken from vi.c) */
118 static void set_tty_raw(void)
119 {
120         tcsetattr(fileno(inp), TCSANOW, &term_vi);
121 }
122
123 /* Exit the program gracefully */
124 static void tless_exit(int code)
125 {
126         /* TODO: We really should save the terminal state when we start,
127                  and restore it when we exit. Less does this with the
128                  "ti" and "te" termcap commands; can this be done with
129                  only termios.h? */
130
131         putchar('\n');
132         exit(code);
133 }
134
135 /* Grab a character from input without requiring the return key. If the
136    character is ASCII \033, get more characters and assign certain sequences
137    special return codes. Note that this function works best with raw input. */
138 static int tless_getch(void)
139 {
140         int input;
141
142         set_tty_raw();
143
144         input = getc(inp);
145         /* Detect escape sequences (i.e. arrow keys) and handle
146            them accordingly */
147
148         if (input == '\033' && getc(inp) == '[') {
149                 input = getc(inp);
150                 set_tty_cooked();
151                 if (input == REAL_KEY_UP)
152                         return KEY_UP;
153                 else if (input == REAL_KEY_DOWN)
154                         return KEY_DOWN;
155                 else if (input == REAL_KEY_RIGHT)
156                         return KEY_RIGHT;
157                 else if (input == REAL_KEY_LEFT)
158                         return KEY_LEFT;
159                 else if (input == REAL_PAGE_UP)
160                         return PAGE_UP;
161                 else if (input == REAL_PAGE_DOWN)
162                         return PAGE_DOWN;
163         }
164         /* The input is a normal ASCII value */
165         else {
166                 set_tty_cooked();
167                 return input;
168         }
169         return 0;
170 }
171
172 /* Move the cursor to a position (x,y), where (0,0) is the
173    top-left corner of the console */
174 static void move_cursor(int x, int y)
175 {
176         printf("\033[%i;%iH", x, y);
177 }
178
179 static void clear_line(void)
180 {
181         move_cursor(height, 0);
182         printf("\033[K");
183 }
184
185 /* This adds line numbers to every line, as the -N flag necessitates */
186 static void add_linenumbers(void)
187 {
188         int i;
189
190         for (i = 0; i <= num_flines; i++) {
191                 char *new = xasprintf("%5d %s", i + 1, flines[i]);
192                 free(flines[i]);
193                 flines[i] = new;
194         }
195 }
196
197 static void data_readlines(void)
198 {
199         int i;
200         char current_line[256];
201         FILE *fp;
202
203         fp = (inp_stdin) ? stdin : xfopen(filename, "r");
204         flines = NULL;
205         for (i = 0; (feof(fp)==0) && (i <= MAXLINES); i++) {
206                 strcpy(current_line, "");
207                 fgets(current_line, 256, fp);
208                 if (fp != stdin)
209                         die_if_ferror(fp, filename);
210                 flines = xrealloc(flines, (i+1) * sizeof(char *));
211                 flines[i] = xstrdup(current_line);
212         }
213         num_flines = i - 2;
214
215         /* Reset variables for a new file */
216
217         line_pos = 0;
218         past_eof = 0;
219
220         fclose(fp);
221
222         if (inp == NULL)
223                 inp = (inp_stdin) ? xfopen(CURRENT_TTY, "r") : stdin;
224
225         if (flags & FLAG_N)
226                 add_linenumbers();
227 }
228
229 #ifdef CONFIG_FEATURE_LESS_FLAGS
230
231 /* Interestingly, writing calc_percent as a function and not a prototype saves around 32 bytes
232  * on my build. */
233 static int calc_percent(void)
234 {
235         return ((100 * (line_pos + height - 2) / num_flines) + 1);
236 }
237
238 /* Print a status line if -M was specified */
239 static void m_status_print(void)
240 {
241         int percentage;
242
243         if (!past_eof) {
244                 if (!line_pos) {
245                         if (num_files > 1)
246                                 printf("%s%s %s%i%s%i%s%i-%i/%i ", HIGHLIGHT,
247                                         filename, "(file ", current_file, " of ", num_files, ") lines ",
248                                         line_pos + 1, line_pos + height - 1, num_flines + 1);
249                         else {
250                                 printf("%s%s lines %i-%i/%i ", HIGHLIGHT,
251                                         filename, line_pos + 1, line_pos + height - 1,
252                                         num_flines + 1);
253                         }
254                 }
255                 else {
256                         printf("%s %s lines %i-%i/%i ", HIGHLIGHT, filename,
257                                 line_pos + 1, line_pos + height - 1, num_flines + 1);
258                 }
259
260                 if (line_pos == num_flines - height + 2) {
261                         printf("(END) %s", NORMAL);
262                         if ((num_files > 1) && (current_file != num_files))
263                                 printf("%s- Next: %s%s", HIGHLIGHT, files[current_file], NORMAL);
264                 }
265                 else {
266                         percentage = calc_percent();
267                         printf("%i%% %s", percentage, NORMAL);
268                 }
269         }
270         else {
271                 printf("%s%s lines %i-%i/%i (END) ", HIGHLIGHT, filename,
272                                 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 ",
310                                         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] = 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] = 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] = 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] = 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] = 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] = 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] = xstrdup(flines[line_pos + i]);
444                                 else {
445                                         if (line_pos >= num_flines - height + 2)
446                                                 buffer[i] = 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] = 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] = xstrdup(flines[linenum + i]);
475                         else
476                                 buffer[i] = 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] = 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 = xmalloc((sizeof(char) * (strlen(line) + 1)) + 64);
613         char *growline = "";
614         regmatch_t match_structs;
615
616         line2 = 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 = xasprintf("%s%.*s%s%.*s%s", growline,
627                                 match_structs.rm_so, line2, HIGHLIGHT,
628                                 match_structs.rm_eo - match_structs.rm_so,
629                                 line2 + match_structs.rm_so, NORMAL);
630                 }
631                 else {
632                         growline = xasprintf("%s%.*s%.*s", growline,
633                                 match_structs.rm_so - 4, line2,
634                                 match_structs.rm_eo - match_structs.rm_so,
635                                 line2 + match_structs.rm_so);
636                 }
637
638                 line2 += match_structs.rm_eo;
639                 match_status = regexec(pattern, line2, 1, &match_structs, REG_NOTBOL);
640         }
641
642         growline = xasprintf("%s%s", growline, line2);
643
644         return (match_found ? growline : line);
645
646         free(growline);
647         free(line2);
648 }
649
650 static void goto_match(int match)
651 {
652         /* This goes to a specific match - all line positions of matches are
653            stored within the match_lines[] array. */
654         if ((match < num_matches) && (match >= 0)) {
655                 buffer_line(match_lines[match]);
656                 match_pos = match;
657         }
658 }
659
660 static void regex_process(void)
661 {
662         char uncomp_regex[100];
663         char *current_line;
664         int i;
665         int j = 0;
666         regex_t pattern;
667         /* Get the uncompiled regular expression from the user */
668         clear_line();
669         putchar((match_backwards) ? '?' : '/');
670         uncomp_regex[0] = 0;
671         fgets(uncomp_regex, sizeof(uncomp_regex), inp);
672
673         if (strlen(uncomp_regex) == 1) {
674                 if (num_matches)
675                         goto_match(match_backwards ? match_pos - 1 : match_pos + 1);
676                 else
677                         buffer_print();
678                 return;
679         }
680         uncomp_regex[strlen(uncomp_regex) - 1] = '\0';
681
682         /* Compile the regex and check for errors */
683         xregcomp(&pattern, uncomp_regex, 0);
684
685         if (num_matches) {
686                 /* Get rid of all the highlights we added previously */
687                 for (i = 0; i <= num_flines; i++) {
688                         current_line = process_regex_on_line(flines[i], &old_pattern, 0);
689                         flines[i] = xstrdup(current_line);
690                 }
691         }
692         old_pattern = pattern;
693
694         /* Reset variables */
695         match_lines = xrealloc(match_lines, sizeof(int));
696         match_lines[0] = -1;
697         match_pos = 0;
698         num_matches = 0;
699         match_found = 0;
700         /* Run the regex on each line of the current file here */
701         for (i = 0; i <= num_flines; i++) {
702                 current_line = process_regex_on_line(flines[i], &pattern, 1);
703                 flines[i] = xstrdup(current_line);
704                 if (match_found) {
705                         match_lines = xrealloc(match_lines, (j + 1) * sizeof(int));
706                         match_lines[j] = i;
707                         j++;
708                 }
709         }
710
711         num_matches = j;
712         if ((match_lines[0] != -1) && (num_flines > height - 2)) {
713                 if (match_backwards) {
714                         for (i = 0; i < num_matches; i++) {
715                                 if (match_lines[i] > line_pos) {
716                                         match_pos = i - 1;
717                                         buffer_line(match_lines[match_pos]);
718                                         break;
719                                 }
720                         }
721                 }
722                 else
723                         buffer_line(match_lines[0]);
724         }
725         else
726                 buffer_init();
727 }
728 #endif
729
730 static void number_process(int first_digit)
731 {
732         int i = 1;
733         int num;
734         char num_input[80];
735         char keypress;
736         char *endptr;
737
738         num_input[0] = first_digit;
739
740         /* Clear the current line, print a prompt, and then print the digit */
741         clear_line();
742         printf(":%c", first_digit);
743
744         /* Receive input until a letter is given (max 80 chars)*/
745         while((i < 80) && (num_input[i] = tless_getch()) && isdigit(num_input[i])) {
746                 putchar(num_input[i]);
747                 i++;
748         }
749
750         /* Take the final letter out of the digits string */
751         keypress = num_input[i];
752         num_input[i] = '\0';
753         num = strtol(num_input, &endptr, 10);
754         if (endptr==num_input || *endptr!='\0' || num < 1 || num > MAXLINES) {
755                 buffer_print();
756                 return;
757         }
758
759         /* We now know the number and the letter entered, so we process them */
760         switch (keypress) {
761                 case KEY_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015':
762                         buffer_down(num);
763                         break;
764                 case KEY_UP: case 'b': case 'w': case 'y': case 'u':
765                         buffer_up(num);
766                         break;
767                 case 'g': case '<': case 'G': case '>':
768                         if (num_flines >= height - 2)
769                                 buffer_line(num - 1);
770                         break;
771                 case 'p': case '%':
772                         buffer_line(((num / 100) * num_flines) - 1);
773                         break;
774 #ifdef CONFIG_FEATURE_LESS_REGEXP
775                 case 'n':
776                         goto_match(match_pos + num);
777                         break;
778                 case '/':
779                         match_backwards = 0;
780                         regex_process();
781                         break;
782                 case '?':
783                         match_backwards = 1;
784                         regex_process();
785                         break;
786 #endif
787                 default:
788                         break;
789         }
790 }
791
792 #ifdef CONFIG_FEATURE_LESS_FLAGCS
793 static void flag_change(void)
794 {
795         int keypress;
796
797         clear_line();
798         putchar('-');
799         keypress = tless_getch();
800
801         switch (keypress) {
802                 case 'M':
803                         flags ^= FLAG_M;
804                         break;
805                 case 'm':
806                         flags ^= FLAG_m;
807                         break;
808                 case 'E':
809                         flags ^= FLAG_E;
810                         break;
811                 case '~':
812                         flags ^= FLAG_TILDE;
813                         break;
814                 default:
815                         break;
816         }
817 }
818
819 static void show_flag_status(void)
820 {
821         int keypress;
822         int flag_val;
823
824         clear_line();
825         putchar('_');
826         keypress = tless_getch();
827
828         switch (keypress) {
829                 case 'M':
830                         flag_val = flags & FLAG_M;
831                         break;
832                 case 'm':
833                         flag_val = flags & FLAG_m;
834                         break;
835                 case '~':
836                         flag_val = flags & FLAG_TILDE;
837                         break;
838                 case 'N':
839                         flag_val = flags & FLAG_N;
840                         break;
841                 case 'E':
842                         flag_val = flags & FLAG_E;
843                         break;
844                 default:
845                         flag_val = 0;
846                         break;
847         }
848
849         clear_line();
850         printf("%s%s%i%s", HIGHLIGHT, "The status of the flag is: ", flag_val != 0, NORMAL);
851 }
852 #endif
853
854 static void full_repaint(void)
855 {
856         int temp_line_pos = line_pos;
857         data_readlines();
858         buffer_init();
859         buffer_line(temp_line_pos);
860 }
861
862
863 static void save_input_to_file(void)
864 {
865         char current_line[256];
866         int i;
867         FILE *fp;
868
869         clear_line();
870         printf("Log file: ");
871         fgets(current_line, 256, inp);
872         current_line[strlen(current_line) - 1] = '\0';
873         if (strlen(current_line) > 1) {
874                 fp = xfopen(current_line, "w");
875                 for (i = 0; i < num_flines; i++)
876                         fprintf(fp, "%s", flines[i]);
877                 fclose(fp);
878                 buffer_print();
879         }
880         else
881                 printf("%sNo log file%s", HIGHLIGHT, NORMAL);
882 }
883
884 #ifdef CONFIG_FEATURE_LESS_MARKS
885 static void add_mark(void)
886 {
887         int letter;
888
889         clear_line();
890         printf("Mark: ");
891         letter = tless_getch();
892
893         if (isalpha(letter)) {
894
895                 /* If we exceed 15 marks, start overwriting previous ones */
896                 if (num_marks == 14)
897                         num_marks = 0;
898
899                 mark_lines[num_marks][0] = letter;
900                 mark_lines[num_marks][1] = line_pos;
901                 num_marks++;
902         }
903         else {
904                 clear_line();
905                 printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL);
906         }
907 }
908
909 static void goto_mark(void)
910 {
911         int letter;
912         int i;
913
914         clear_line();
915         printf("Go to mark: ");
916         letter = tless_getch();
917         clear_line();
918
919         if (isalpha(letter)) {
920                 for (i = 0; i <= num_marks; i++)
921                         if (letter == mark_lines[i][0]) {
922                                 buffer_line(mark_lines[i][1]);
923                                 break;
924                         }
925                 if ((num_marks == 14) && (letter != mark_lines[14][0]))
926                         printf("%s%s%s", HIGHLIGHT, "Mark not set", NORMAL);
927         }
928         else
929                 printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL);
930 }
931 #endif
932
933
934 #ifdef CONFIG_FEATURE_LESS_BRACKETS
935
936 static char opp_bracket(char bracket)
937 {
938         switch (bracket) {
939                 case '{': case '[':
940                         return bracket + 2;
941                 case '(':
942                         return ')';
943                 case '}': case ']':
944                         return bracket - 2;
945                 case ')':
946                         return '(';
947                 default:
948                         return 0;
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 = getopt32(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 }