blob: 43bc6737e2cbc7558c8f79e9a60b7f1174ae3d51 [file] [log] [blame]
Rob Landley9200e792005-09-15 19:26:59 +00001/* 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 */
84static void set_tty_cooked(void);
85static void set_tty_raw(void);
86static void tless_exit(int code);
87static int tless_getch(void);
88static void move_cursor(int x, int y);
89static void clear_line(void);
90static void data_readlines(void);
91static void free_flines(void);
92#ifdef CONFIG_FEATURE_LESS_FLAGS
93static int calc_percent(void);
94#endif
95static int reverse_percent(int percentage);
96#ifdef CONFIG_FEATURE_LESS_FLAGS
97static void m_status_print(void);
98static void medium_status_print(void);
99#endif
100static void status_print(void);
101static void buffer_print(void);
102static void buffer_init(void);
103static void buffer_down(int nlines);
104static void buffer_up(int nlines);
105static void buffer_line(int linenum);
106static void keypress_process(int keypress);
107static void colon_process(void);
108static void number_process(int first_digit);
109#ifdef CONFIG_FEATURE_LESS_FLAGCS
110static void flag_change(void);
111static void show_flag_status(void);
112#endif
113static void examine_file(void);
114static void next_file(void);
115static void previous_file(void);
116static void first_file(void);
117static void remove_current_file(void);
118static void full_repaint(void);
119static void add_linenumbers(void);
120static void save_input_to_file(void);
121#ifdef CONFIG_FEATURE_LESS_MARKS
122static void add_mark(void);
123static void goto_mark(void);
124#endif
125#ifdef CONFIG_FEATURE_LESS_REGEXP
126static void regex_process(void);
127char *process_regex_on_line(char *line, regex_t *pattern);
128char *insert_highlights(char *line, int start, int end);
129static void goto_match (int match);
130static void search_backwards(void);
131#endif
132#ifdef CONFIG_FEATURE_LESS_BRACKETS
133static char opp_bracket (char bracket);
134static void match_right_bracket (char bracket);
135static void match_left_bracket (char bracket);
136#endif
137int less_main(int argc, char *argv[]);
138
139static int height;
140static int width;
141static char **files;
142static char filename[256];
143static char buffer[100][256];
144static char *flines[MAXLINES];
145static int current_file = 1;
146static int line_pos = 0;
147static int num_flines;
148static int num_files = 1;
149static int past_eof = 0;
150
151/* Command line options */
152static int E_FLAG = 0;
153static int M_FLAG = 0;
154static int N_FLAG = 0;
155static int m_FLAG = 0;
156static int TILDE_FLAG = 0;
157
158/* This is needed so that program behaviour changes when input comes from
159 stdin */
160static 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. */
165static int ea_inp_stdin = 0;
166
167#ifdef CONFIG_FEATURE_LESS_MARKS
168static int mark_lines[15][2];
169static int num_marks = 0;
170#endif
171
172#ifdef CONFIG_FEATURE_LESS_REGEXP
173static int match_found = 0;
174static int match_lines[100];
175static int match_pos = 0;
176static int num_matches = 0;
177static int match_backwards = 0;
178static int num_back_match = 1;
179#endif
180
181/* Needed termios structures */
182static struct termios term_orig, term_vi;
183
184/* File pointer to get input from */
185static FILE *inp;
186
187/* Reset terminal input to normal */
188static void set_tty_cooked() {
189 fflush(stdout);
190 tcsetattr(0, TCSANOW, &term_orig);
191}
192
193/* Set terminal input to raw mode */
194static 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 */
206static 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. */
220int 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 */
258static void move_cursor(int x, int y) {
259 printf("\033[%i;%iH", x, y);
260}
261
262static void clear_line() {
263 move_cursor(height, 0);
264 printf("\033[K");
265}
266
267static 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 */
305static 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 */
315int calc_percent() {
316 return ((100 * (line_pos + height - 2) / num_flines) + 1);
317}
318#endif
319
320/* Turn a percentage into a line number */
321int 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 */
329static 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 */
364static 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 */
379static 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 */
409static 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 */
432static 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 */
452static 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
478static 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
525static 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
553static 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
664static 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
703static 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
768static 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
794static 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
829static 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
854static 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
869static 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
885static 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
896static 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
917static 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 */
927static 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
939static 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
961static 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
987static 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. */
1018static 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
1065char *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
1100char *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
1113static 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
1123static 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
1147static 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
1168static 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
1195static 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
1226int 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}