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