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