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