1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24#include <sched.h>
25
26#include "libbb.h"
27#if ENABLE_FEATURE_LESS_REGEXP
28#include "xregex.h"
29#endif
30
31
32#define HIGHLIGHT "\033[7m"
33#define NORMAL "\033[0m"
34
35#define CLEAR "\033[H\033[J"
36
37#define CLEAR_2_EOL "\033[K"
38
39enum {
40
41 MAXLINES = CONFIG_FEATURE_LESS_MAXLINES,
42
43 TILDES = 1,
44};
45
46
47enum {
48 FLAG_E = 1 << 0,
49 FLAG_M = 1 << 1,
50 FLAG_m = 1 << 2,
51 FLAG_N = 1 << 3,
52 FLAG_TILDE = 1 << 4,
53 FLAG_I = 1 << 5,
54 FLAG_S = (1 << 6) * ENABLE_FEATURE_LESS_DASHCMD,
55
56 LESS_STATE_MATCH_BACKWARDS = 1 << 15,
57};
58
59#if !ENABLE_FEATURE_LESS_REGEXP
60enum { pattern_valid = 0 };
61#endif
62
63struct globals {
64 int cur_fline;
65 int kbd_fd;
66 int less_gets_pos;
67
68 size_t last_line_pos;
69 unsigned max_fline;
70 unsigned max_lineno;
71 unsigned max_displayed_line;
72 unsigned width;
73#if ENABLE_FEATURE_LESS_WINCH
74 unsigned winch_counter;
75#endif
76 ssize_t eof_error;
77 ssize_t readpos;
78 ssize_t readeof;
79 const char **buffer;
80 const char **flines;
81 const char *empty_line_marker;
82 unsigned num_files;
83 unsigned current_file;
84 char *filename;
85 char **files;
86#if ENABLE_FEATURE_LESS_MARKS
87 unsigned num_marks;
88 unsigned mark_lines[15][2];
89#endif
90#if ENABLE_FEATURE_LESS_REGEXP
91 unsigned *match_lines;
92 int match_pos;
93 int wanted_match;
94 int num_matches;
95 regex_t pattern;
96 smallint pattern_valid;
97#endif
98 smallint terminated;
99 struct termios term_orig, term_less;
100 char kbd_input[KEYCODE_BUFFER_SIZE];
101};
102#define G (*ptr_to_globals)
103#define cur_fline (G.cur_fline )
104#define kbd_fd (G.kbd_fd )
105#define less_gets_pos (G.less_gets_pos )
106#define last_line_pos (G.last_line_pos )
107#define max_fline (G.max_fline )
108#define max_lineno (G.max_lineno )
109#define max_displayed_line (G.max_displayed_line)
110#define width (G.width )
111#define winch_counter (G.winch_counter )
112
113#define WINCH_COUNTER (*(volatile unsigned *)&winch_counter)
114#define eof_error (G.eof_error )
115#define readpos (G.readpos )
116#define readeof (G.readeof )
117#define buffer (G.buffer )
118#define flines (G.flines )
119#define empty_line_marker (G.empty_line_marker )
120#define num_files (G.num_files )
121#define current_file (G.current_file )
122#define filename (G.filename )
123#define files (G.files )
124#define num_marks (G.num_marks )
125#define mark_lines (G.mark_lines )
126#if ENABLE_FEATURE_LESS_REGEXP
127#define match_lines (G.match_lines )
128#define match_pos (G.match_pos )
129#define num_matches (G.num_matches )
130#define wanted_match (G.wanted_match )
131#define pattern (G.pattern )
132#define pattern_valid (G.pattern_valid )
133#endif
134#define terminated (G.terminated )
135#define term_orig (G.term_orig )
136#define term_less (G.term_less )
137#define kbd_input (G.kbd_input )
138#define INIT_G() do { \
139 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
140 less_gets_pos = -1; \
141 empty_line_marker = "~"; \
142 num_files = 1; \
143 current_file = 1; \
144 eof_error = 1; \
145 terminated = 1; \
146 IF_FEATURE_LESS_REGEXP(wanted_match = -1;) \
147} while (0)
148
149
150
151
152
153#define MEMPTR(p) ((char*)(p) - 4)
154#define LINENO(p) (*(uint32_t*)((p) - 4))
155
156
157
158static void set_tty_cooked(void)
159{
160 fflush_all();
161 tcsetattr(kbd_fd, TCSANOW, &term_orig);
162}
163
164
165
166static void move_cursor(int line, int row)
167{
168 printf("\033[%u;%uH", line, row);
169}
170
171static void clear_line(void)
172{
173 printf("\033[%u;0H" CLEAR_2_EOL, max_displayed_line + 2);
174}
175
176static void print_hilite(const char *str)
177{
178 printf(HIGHLIGHT"%s"NORMAL, str);
179}
180
181static void print_statusline(const char *str)
182{
183 clear_line();
184 printf(HIGHLIGHT"%.*s"NORMAL, width - 1, str);
185}
186
187
188static void less_exit(int code)
189{
190 set_tty_cooked();
191 clear_line();
192 if (code < 0)
193 kill_myself_with_sig(- code);
194 exit(code);
195}
196
197#if (ENABLE_FEATURE_LESS_DASHCMD && ENABLE_FEATURE_LESS_LINENUMS) \
198 || ENABLE_FEATURE_LESS_WINCH
199static void re_wrap(void)
200{
201 int w = width;
202 int new_line_pos;
203 int src_idx;
204 int dst_idx;
205 int new_cur_fline = 0;
206 uint32_t lineno;
207 char linebuf[w + 1];
208 const char **old_flines = flines;
209 const char *s;
210 char **new_flines = NULL;
211 char *d;
212
213 if (option_mask32 & FLAG_N)
214 w -= 8;
215
216 src_idx = 0;
217 dst_idx = 0;
218 s = old_flines[0];
219 lineno = LINENO(s);
220 d = linebuf;
221 new_line_pos = 0;
222 while (1) {
223 *d = *s;
224 if (*d != '\0') {
225 new_line_pos++;
226 if (*d == '\t')
227 new_line_pos += 7;
228 s++;
229 d++;
230 if (new_line_pos >= w) {
231 int sz;
232
233 *d = '\0';
234 next_new:
235 sz = (d - linebuf) + 1;
236 d = ((char*)xmalloc(sz + 4)) + 4;
237 LINENO(d) = lineno;
238 memcpy(d, linebuf, sz);
239 new_flines = xrealloc_vector(new_flines, 8, dst_idx);
240 new_flines[dst_idx] = d;
241 dst_idx++;
242 if (new_line_pos < w) {
243
244 if (src_idx > max_fline)
245 break;
246 lineno = LINENO(s);
247 }
248 d = linebuf;
249 new_line_pos = 0;
250 }
251 continue;
252 }
253
254 free(MEMPTR(old_flines[src_idx]));
255
256 if (cur_fline == src_idx)
257 new_cur_fline = dst_idx;
258 src_idx++;
259
260 if (src_idx > max_fline)
261 goto next_new;
262 s = old_flines[src_idx];
263 if (lineno != LINENO(s)) {
264
265
266 goto next_new;
267 }
268 }
269
270 free(old_flines);
271 flines = (const char **)new_flines;
272
273 max_fline = dst_idx - 1;
274 last_line_pos = new_line_pos;
275 cur_fline = new_cur_fline;
276
277#if ENABLE_FEATURE_LESS_REGEXP
278 pattern_valid = 0;
279#endif
280}
281#endif
282
283#if ENABLE_FEATURE_LESS_REGEXP
284static void fill_match_lines(unsigned pos);
285#else
286#define fill_match_lines(pos) ((void)0)
287#endif
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312static void read_lines(void)
313{
314#define readbuf bb_common_bufsiz1
315 char *current_line, *p;
316 int w = width;
317 char last_terminated = terminated;
318#if ENABLE_FEATURE_LESS_REGEXP
319 unsigned old_max_fline = max_fline;
320 time_t last_time = 0;
321 unsigned seconds_p1 = 3;
322#endif
323
324 if (option_mask32 & FLAG_N)
325 w -= 8;
326
327 IF_FEATURE_LESS_REGEXP(again0:)
328
329 p = current_line = ((char*)xmalloc(w + 4)) + 4;
330 max_fline += last_terminated;
331 if (!last_terminated) {
332 const char *cp = flines[max_fline];
333 strcpy(p, cp);
334 p += strlen(current_line);
335 free(MEMPTR(flines[max_fline]));
336
337 } else {
338 last_line_pos = 0;
339 }
340
341 while (1) {
342 *p = '\0';
343 terminated = 0;
344 while (1) {
345 char c;
346
347 if (readpos >= readeof) {
348 ndelay_on(0);
349 eof_error = safe_read(STDIN_FILENO, readbuf, sizeof(readbuf));
350 ndelay_off(0);
351 readpos = 0;
352 readeof = eof_error;
353 if (eof_error <= 0)
354 goto reached_eof;
355 }
356 c = readbuf[readpos];
357
358
359
360 if (c == '\x8' && last_line_pos && p[-1] != '\t') {
361 readpos++;
362 last_line_pos--;
363
364 *--p = '\0';
365 continue;
366 }
367 {
368 size_t new_last_line_pos = last_line_pos + 1;
369 if (c == '\t') {
370 new_last_line_pos += 7;
371 new_last_line_pos &= (~7);
372 }
373 if ((int)new_last_line_pos >= w)
374 break;
375 last_line_pos = new_last_line_pos;
376 }
377
378 readpos++;
379 if (c == '\n') {
380 terminated = 1;
381 last_line_pos = 0;
382 break;
383 }
384
385 if (c == '\0') c = '\n';
386 *p++ = c;
387 *p = '\0';
388 }
389
390
391 if (!last_terminated && !current_line[0]) {
392 last_terminated = 1;
393 max_lineno++;
394 continue;
395 }
396 reached_eof:
397 last_terminated = terminated;
398 flines = xrealloc_vector(flines, 8, max_fline);
399
400 flines[max_fline] = (char*)xrealloc(MEMPTR(current_line), strlen(current_line) + 1 + 4) + 4;
401 LINENO(flines[max_fline]) = max_lineno;
402 if (terminated)
403 max_lineno++;
404
405 if (max_fline >= MAXLINES) {
406 eof_error = 0;
407 break;
408 }
409 if (!(option_mask32 & FLAG_S)
410 ? (max_fline > cur_fline + max_displayed_line)
411 : (max_fline >= cur_fline
412 && max_lineno > LINENO(flines[cur_fline]) + max_displayed_line)
413 ) {
414#if !ENABLE_FEATURE_LESS_REGEXP
415 break;
416#else
417 if (wanted_match >= num_matches) {
418 fill_match_lines(old_max_fline);
419 old_max_fline = max_fline;
420 }
421 if (wanted_match < num_matches)
422 break;
423#endif
424 }
425 if (eof_error <= 0) {
426 if (eof_error < 0) {
427 if (errno == EAGAIN) {
428
429
430
431 eof_error = 1;
432 } else {
433 print_statusline(bb_msg_read_error);
434 }
435 }
436#if !ENABLE_FEATURE_LESS_REGEXP
437 break;
438#else
439 if (wanted_match < num_matches) {
440 break;
441 } else {
442 time_t t = time(NULL);
443 if (t != last_time) {
444 last_time = t;
445 if (--seconds_p1 == 0)
446 break;
447 }
448 sched_yield();
449 goto again0;
450 }
451#endif
452 }
453 max_fline++;
454 current_line = ((char*)xmalloc(w + 4)) + 4;
455 p = current_line;
456 last_line_pos = 0;
457 }
458 fill_match_lines(old_max_fline);
459#if ENABLE_FEATURE_LESS_REGEXP
460
461 wanted_match = -1;
462#endif
463#undef readbuf
464}
465
466#if ENABLE_FEATURE_LESS_FLAGS
467
468
469static int calc_percent(void)
470{
471 unsigned p = (100 * (cur_fline+max_displayed_line+1) + max_fline/2) / (max_fline+1);
472 return p <= 100 ? p : 100;
473}
474
475
476static void m_status_print(void)
477{
478 int percentage;
479
480 if (less_gets_pos >= 0)
481 return;
482
483 clear_line();
484 printf(HIGHLIGHT"%s", filename);
485 if (num_files > 1)
486 printf(" (file %i of %i)", current_file, num_files);
487 printf(" lines %i-%i/%i ",
488 cur_fline + 1, cur_fline + max_displayed_line + 1,
489 max_fline + 1);
490 if (cur_fline >= (int)(max_fline - max_displayed_line)) {
491 printf("(END)"NORMAL);
492 if (num_files > 1 && current_file != num_files)
493 printf(HIGHLIGHT" - next: %s"NORMAL, files[current_file]);
494 return;
495 }
496 percentage = calc_percent();
497 printf("%i%%"NORMAL, percentage);
498}
499#endif
500
501
502static void status_print(void)
503{
504 const char *p;
505
506 if (less_gets_pos >= 0)
507 return;
508
509
510#if ENABLE_FEATURE_LESS_FLAGS
511 if (option_mask32 & (FLAG_M|FLAG_m)) {
512 m_status_print();
513 return;
514 }
515
516#endif
517
518 clear_line();
519 if (cur_fline && cur_fline < (int)(max_fline - max_displayed_line)) {
520 bb_putchar(':');
521 return;
522 }
523 p = "(END)";
524 if (!cur_fline)
525 p = filename;
526 if (num_files > 1) {
527 printf(HIGHLIGHT"%s (file %i of %i)"NORMAL,
528 p, current_file, num_files);
529 return;
530 }
531 print_hilite(p);
532}
533
534static void cap_cur_fline(int nlines)
535{
536 int diff;
537 if (cur_fline < 0)
538 cur_fline = 0;
539 if (cur_fline + max_displayed_line > max_fline + TILDES) {
540 cur_fline -= nlines;
541 if (cur_fline < 0)
542 cur_fline = 0;
543 diff = max_fline - (cur_fline + max_displayed_line) + TILDES;
544
545
546 if (diff > 0)
547 cur_fline += diff;
548 }
549}
550
551static const char controls[] ALIGN1 =
552
553 "\x01\x02\x03\x04\x05\x06\x07\x08" "\x0a\x0b\x0c\x0d\x0e\x0f"
554 "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
555 "\x7f\x9b";
556static const char ctrlconv[] ALIGN1 =
557
558
559 "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x40\x4b\x4c\x4d\x4e\x4f"
560 "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f";
561
562static void lineno_str(char *nbuf9, const char *line)
563{
564 nbuf9[0] = '\0';
565 if (option_mask32 & FLAG_N) {
566 const char *fmt;
567 unsigned n;
568
569 if (line == empty_line_marker) {
570 memset(nbuf9, ' ', 8);
571 nbuf9[8] = '\0';
572 return;
573 }
574
575 fmt = "%7u ";
576 n = LINENO(line) + 1;
577 if (n > 9999999) {
578 n %= 10000000;
579 fmt = "%07u ";
580 }
581 sprintf(nbuf9, fmt, n);
582 }
583}
584
585
586#if ENABLE_FEATURE_LESS_REGEXP
587static void print_found(const char *line)
588{
589 int match_status;
590 int eflags;
591 char *growline;
592 regmatch_t match_structs;
593
594 char buf[width];
595 char nbuf9[9];
596 const char *str = line;
597 char *p = buf;
598 size_t n;
599
600 while (*str) {
601 n = strcspn(str, controls);
602 if (n) {
603 if (!str[n]) break;
604 memcpy(p, str, n);
605 p += n;
606 str += n;
607 }
608 n = strspn(str, controls);
609 memset(p, '.', n);
610 p += n;
611 str += n;
612 }
613 strcpy(p, str);
614
615
616
617
618
619
620
621 str = buf;
622 growline = NULL;
623 eflags = 0;
624 goto start;
625
626 while (match_status == 0) {
627 char *new = xasprintf("%s%.*s"HIGHLIGHT"%.*s"NORMAL,
628 growline ? growline : "",
629 match_structs.rm_so, str,
630 match_structs.rm_eo - match_structs.rm_so,
631 str + match_structs.rm_so);
632 free(growline);
633 growline = new;
634 str += match_structs.rm_eo;
635 line += match_structs.rm_eo;
636 eflags = REG_NOTBOL;
637 start:
638
639 match_status = regexec(&pattern, line, 1, &match_structs, eflags);
640
641 if (match_structs.rm_so >= match_structs.rm_eo)
642 match_status = 1;
643 }
644
645 lineno_str(nbuf9, line);
646 if (!growline) {
647 printf(CLEAR_2_EOL"%s%s\n", nbuf9, str);
648 return;
649 }
650 printf(CLEAR_2_EOL"%s%s%s\n", nbuf9, growline, str);
651 free(growline);
652}
653#else
654void print_found(const char *line);
655#endif
656
657static void print_ascii(const char *str)
658{
659 char buf[width];
660 char nbuf9[9];
661 char *p;
662 size_t n;
663
664 lineno_str(nbuf9, str);
665 printf(CLEAR_2_EOL"%s", nbuf9);
666
667 while (*str) {
668 n = strcspn(str, controls);
669 if (n) {
670 if (!str[n]) break;
671 printf("%.*s", (int) n, str);
672 str += n;
673 }
674 n = strspn(str, controls);
675 p = buf;
676 do {
677 if (*str == 0x7f)
678 *p++ = '?';
679 else if (*str == (char)0x9b)
680
681
682 *p++ = '{';
683 else
684 *p++ = ctrlconv[(unsigned char)*str];
685 str++;
686 } while (--n);
687 *p = '\0';
688 print_hilite(buf);
689 }
690 puts(str);
691}
692
693
694static void buffer_print(void)
695{
696 unsigned i;
697
698 move_cursor(0, 0);
699 for (i = 0; i <= max_displayed_line; i++)
700 if (pattern_valid)
701 print_found(buffer[i]);
702 else
703 print_ascii(buffer[i]);
704 status_print();
705}
706
707static void buffer_fill_and_print(void)
708{
709 unsigned i;
710#if ENABLE_FEATURE_LESS_DASHCMD
711 int fpos = cur_fline;
712
713 if (option_mask32 & FLAG_S) {
714
715 while (fpos && LINENO(flines[fpos]) == LINENO(flines[fpos-1]))
716 fpos--;
717 }
718
719 i = 0;
720 while (i <= max_displayed_line && fpos <= max_fline) {
721 int lineno = LINENO(flines[fpos]);
722 buffer[i] = flines[fpos];
723 i++;
724 do {
725 fpos++;
726 } while ((fpos <= max_fline)
727 && (option_mask32 & FLAG_S)
728 && lineno == LINENO(flines[fpos])
729 );
730 }
731#else
732 for (i = 0; i <= max_displayed_line && cur_fline + i <= max_fline; i++) {
733 buffer[i] = flines[cur_fline + i];
734 }
735#endif
736 for (; i <= max_displayed_line; i++) {
737 buffer[i] = empty_line_marker;
738 }
739 buffer_print();
740}
741
742
743static void buffer_down(int nlines)
744{
745 cur_fline += nlines;
746 read_lines();
747 cap_cur_fline(nlines);
748 buffer_fill_and_print();
749}
750
751static void buffer_up(int nlines)
752{
753 cur_fline -= nlines;
754 if (cur_fline < 0) cur_fline = 0;
755 read_lines();
756 buffer_fill_and_print();
757}
758
759static void buffer_line(int linenum)
760{
761 if (linenum < 0)
762 linenum = 0;
763 cur_fline = linenum;
764 read_lines();
765 if (linenum + max_displayed_line > max_fline)
766 linenum = max_fline - max_displayed_line + TILDES;
767 if (linenum < 0)
768 linenum = 0;
769 cur_fline = linenum;
770 buffer_fill_and_print();
771}
772
773static void open_file_and_read_lines(void)
774{
775 if (filename) {
776 xmove_fd(xopen(filename, O_RDONLY), STDIN_FILENO);
777 } else {
778
779
780 filename = xstrdup(bb_msg_standard_input);
781 }
782 readpos = 0;
783 readeof = 0;
784 last_line_pos = 0;
785 terminated = 1;
786 read_lines();
787}
788
789
790static void reinitialize(void)
791{
792 unsigned i;
793
794 if (flines) {
795 for (i = 0; i <= max_fline; i++)
796 free(MEMPTR(flines[i]));
797 free(flines);
798 flines = NULL;
799 }
800
801 max_fline = -1;
802 cur_fline = 0;
803 max_lineno = 0;
804 open_file_and_read_lines();
805 buffer_fill_and_print();
806}
807
808static int getch_nowait(void)
809{
810 int rd;
811 struct pollfd pfd[2];
812
813 pfd[0].fd = STDIN_FILENO;
814 pfd[0].events = POLLIN;
815 pfd[1].fd = kbd_fd;
816 pfd[1].events = POLLIN;
817 again:
818 tcsetattr(kbd_fd, TCSANOW, &term_less);
819
820
821
822
823
824
825 rd = 1;
826
827
828 if (!(option_mask32 & FLAG_S)
829 ? !(max_fline > cur_fline + max_displayed_line)
830 : !(max_fline >= cur_fline
831 && max_lineno > LINENO(flines[cur_fline]) + max_displayed_line)
832 ) {
833 if (eof_error > 0)
834 rd = 0;
835 }
836
837 if (less_gets_pos >= 0)
838 move_cursor(max_displayed_line + 2, less_gets_pos + 1);
839 fflush_all();
840
841 if (kbd_input[0] == 0) {
842#if ENABLE_FEATURE_LESS_WINCH
843 while (1) {
844 int r;
845
846 r = poll(pfd + rd, 2 - rd, -1);
847 if ( winch_counter)
848 return '\\';
849 if (r) break;
850 }
851#else
852 safe_poll(pfd + rd, 2 - rd, -1);
853#endif
854 }
855
856
857
858 rd = read_key(kbd_fd, kbd_input, -2);
859 if (rd == -1) {
860 if (errno == EAGAIN) {
861
862
863 read_lines();
864 buffer_fill_and_print();
865 goto again;
866 }
867
868 less_exit(0);
869 }
870 set_tty_cooked();
871 return rd;
872}
873
874
875
876
877static int less_getch(int pos)
878{
879 int i;
880
881 again:
882 less_gets_pos = pos;
883 i = getch_nowait();
884 less_gets_pos = -1;
885
886
887 if (i >= 0 && i < ' ' && i != 0x0d && i != 8)
888 goto again;
889 return i;
890}
891
892static char* less_gets(int sz)
893{
894 int c;
895 unsigned i = 0;
896 char *result = xzalloc(1);
897
898 while (1) {
899 c = '\0';
900 less_gets_pos = sz + i;
901 c = getch_nowait();
902 if (c == 0x0d) {
903 result[i] = '\0';
904 less_gets_pos = -1;
905 return result;
906 }
907 if (c == 0x7f)
908 c = 8;
909 if (c == 8 && i) {
910 printf("\x8 \x8");
911 i--;
912 }
913 if (c < ' ')
914 continue;
915 if (i >= width - sz - 1)
916 continue;
917 bb_putchar(c);
918 result[i++] = c;
919 result = xrealloc(result, i+1);
920 }
921}
922
923static void examine_file(void)
924{
925 char *new_fname;
926
927 print_statusline("Examine: ");
928 new_fname = less_gets(sizeof("Examine: ") - 1);
929 if (!new_fname[0]) {
930 status_print();
931 err:
932 free(new_fname);
933 return;
934 }
935 if (access(new_fname, R_OK) != 0) {
936 print_statusline("Cannot read this file");
937 goto err;
938 }
939 free(filename);
940 filename = new_fname;
941
942
943
944
945 files[0] = filename;
946 num_files = current_file = 1;
947 reinitialize();
948}
949
950
951
952
953
954static void change_file(int direction)
955{
956 if (current_file != ((direction > 0) ? num_files : 1)) {
957 current_file = direction ? current_file + direction : 1;
958 free(filename);
959 filename = xstrdup(files[current_file - 1]);
960 reinitialize();
961 } else {
962 print_statusline(direction > 0 ? "No next file" : "No previous file");
963 }
964}
965
966static void remove_current_file(void)
967{
968 unsigned i;
969
970 if (num_files < 2)
971 return;
972
973 if (current_file != 1) {
974 change_file(-1);
975 for (i = 3; i <= num_files; i++)
976 files[i - 2] = files[i - 1];
977 num_files--;
978 } else {
979 change_file(1);
980 for (i = 2; i <= num_files; i++)
981 files[i - 2] = files[i - 1];
982 num_files--;
983 current_file--;
984 }
985}
986
987static void colon_process(void)
988{
989 int keypress;
990
991
992 print_statusline(" :");
993
994 keypress = less_getch(2);
995 switch (keypress) {
996 case 'd':
997 remove_current_file();
998 break;
999 case 'e':
1000 examine_file();
1001 break;
1002#if ENABLE_FEATURE_LESS_FLAGS
1003 case 'f':
1004 m_status_print();
1005 break;
1006#endif
1007 case 'n':
1008 change_file(1);
1009 break;
1010 case 'p':
1011 change_file(-1);
1012 break;
1013 case 'q':
1014 less_exit(EXIT_SUCCESS);
1015 break;
1016 case 'x':
1017 change_file(0);
1018 break;
1019 }
1020}
1021
1022#if ENABLE_FEATURE_LESS_REGEXP
1023static void normalize_match_pos(int match)
1024{
1025 if (match >= num_matches)
1026 match = num_matches - 1;
1027 if (match < 0)
1028 match = 0;
1029 match_pos = match;
1030}
1031
1032static void goto_match(int match)
1033{
1034 if (!pattern_valid)
1035 return;
1036 if (match < 0)
1037 match = 0;
1038
1039 if (match >= num_matches && eof_error > 0) {
1040 wanted_match = match;
1041 read_lines();
1042 }
1043 if (num_matches) {
1044 normalize_match_pos(match);
1045 buffer_line(match_lines[match_pos]);
1046 } else {
1047 print_statusline("No matches found");
1048 }
1049}
1050
1051static void fill_match_lines(unsigned pos)
1052{
1053 if (!pattern_valid)
1054 return;
1055
1056 while (pos <= max_fline) {
1057
1058 if (regexec(&pattern, flines[pos], 0, NULL, 0) == 0
1059
1060 && !(num_matches && match_lines[num_matches-1] == pos)
1061 ) {
1062 match_lines = xrealloc_vector(match_lines, 4, num_matches);
1063 match_lines[num_matches++] = pos;
1064 }
1065 pos++;
1066 }
1067}
1068
1069static void regex_process(void)
1070{
1071 char *uncomp_regex, *err;
1072
1073
1074 free(match_lines);
1075 match_lines = NULL;
1076 match_pos = 0;
1077 num_matches = 0;
1078 if (pattern_valid) {
1079 regfree(&pattern);
1080 pattern_valid = 0;
1081 }
1082
1083
1084 clear_line();
1085 bb_putchar((option_mask32 & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/');
1086 uncomp_regex = less_gets(1);
1087 if (!uncomp_regex[0]) {
1088 free(uncomp_regex);
1089 buffer_print();
1090 return;
1091 }
1092
1093
1094 err = regcomp_or_errmsg(&pattern, uncomp_regex,
1095 (option_mask32 & FLAG_I) ? REG_ICASE : 0);
1096 free(uncomp_regex);
1097 if (err) {
1098 print_statusline(err);
1099 free(err);
1100 return;
1101 }
1102
1103 pattern_valid = 1;
1104 match_pos = 0;
1105 fill_match_lines(0);
1106 while (match_pos < num_matches) {
1107 if ((int)match_lines[match_pos] > cur_fline)
1108 break;
1109 match_pos++;
1110 }
1111 if (option_mask32 & LESS_STATE_MATCH_BACKWARDS)
1112 match_pos--;
1113
1114
1115
1116
1117 goto_match(match_pos);
1118}
1119#endif
1120
1121static void number_process(int first_digit)
1122{
1123 unsigned i;
1124 int num;
1125 int keypress;
1126 char num_input[sizeof(int)*4];
1127
1128 num_input[0] = first_digit;
1129
1130
1131 clear_line();
1132 printf(":%c", first_digit);
1133
1134
1135 i = 1;
1136 while (i < sizeof(num_input)-1) {
1137 keypress = less_getch(i + 1);
1138 if ((unsigned)keypress > 255 || !isdigit(num_input[i]))
1139 break;
1140 num_input[i] = keypress;
1141 bb_putchar(keypress);
1142 i++;
1143 }
1144
1145 num_input[i] = '\0';
1146 num = bb_strtou(num_input, NULL, 10);
1147
1148 if (num < 1 || num > MAXLINES) {
1149 buffer_print();
1150 return;
1151 }
1152
1153
1154 switch (keypress) {
1155 case KEYCODE_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015':
1156 buffer_down(num);
1157 break;
1158 case KEYCODE_UP: case 'b': case 'w': case 'y': case 'u':
1159 buffer_up(num);
1160 break;
1161 case 'g': case '<': case 'G': case '>':
1162 cur_fline = num + max_displayed_line;
1163 read_lines();
1164 buffer_line(num - 1);
1165 break;
1166 case 'p': case '%':
1167 num = num * (max_fline / 100);
1168 cur_fline = num + max_displayed_line;
1169 read_lines();
1170 buffer_line(num);
1171 break;
1172#if ENABLE_FEATURE_LESS_REGEXP
1173 case 'n':
1174 goto_match(match_pos + num);
1175 break;
1176 case '/':
1177 option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
1178 regex_process();
1179 break;
1180 case '?':
1181 option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
1182 regex_process();
1183 break;
1184#endif
1185 }
1186}
1187
1188#if ENABLE_FEATURE_LESS_DASHCMD
1189static void flag_change(void)
1190{
1191 int keypress;
1192
1193 clear_line();
1194 bb_putchar('-');
1195 keypress = less_getch(1);
1196
1197 switch (keypress) {
1198 case 'M':
1199 option_mask32 ^= FLAG_M;
1200 break;
1201 case 'm':
1202 option_mask32 ^= FLAG_m;
1203 break;
1204 case 'E':
1205 option_mask32 ^= FLAG_E;
1206 break;
1207 case '~':
1208 option_mask32 ^= FLAG_TILDE;
1209 break;
1210 case 'S':
1211 option_mask32 ^= FLAG_S;
1212 buffer_fill_and_print();
1213 break;
1214#if ENABLE_FEATURE_LESS_LINENUMS
1215 case 'N':
1216 option_mask32 ^= FLAG_N;
1217 re_wrap();
1218 buffer_fill_and_print();
1219 break;
1220#endif
1221 }
1222}
1223
1224#ifdef BLOAT
1225static void show_flag_status(void)
1226{
1227 int keypress;
1228 int flag_val;
1229
1230 clear_line();
1231 bb_putchar('_');
1232 keypress = less_getch(1);
1233
1234 switch (keypress) {
1235 case 'M':
1236 flag_val = option_mask32 & FLAG_M;
1237 break;
1238 case 'm':
1239 flag_val = option_mask32 & FLAG_m;
1240 break;
1241 case '~':
1242 flag_val = option_mask32 & FLAG_TILDE;
1243 break;
1244 case 'N':
1245 flag_val = option_mask32 & FLAG_N;
1246 break;
1247 case 'E':
1248 flag_val = option_mask32 & FLAG_E;
1249 break;
1250 default:
1251 flag_val = 0;
1252 break;
1253 }
1254
1255 clear_line();
1256 printf(HIGHLIGHT"The status of the flag is: %u"NORMAL, flag_val != 0);
1257}
1258#endif
1259
1260#endif
1261
1262static void save_input_to_file(void)
1263{
1264 const char *msg = "";
1265 char *current_line;
1266 unsigned i;
1267 FILE *fp;
1268
1269 print_statusline("Log file: ");
1270 current_line = less_gets(sizeof("Log file: ")-1);
1271 if (current_line[0]) {
1272 fp = fopen_for_write(current_line);
1273 if (!fp) {
1274 msg = "Error opening log file";
1275 goto ret;
1276 }
1277 for (i = 0; i <= max_fline; i++)
1278 fprintf(fp, "%s\n", flines[i]);
1279 fclose(fp);
1280 msg = "Done";
1281 }
1282 ret:
1283 print_statusline(msg);
1284 free(current_line);
1285}
1286
1287#if ENABLE_FEATURE_LESS_MARKS
1288static void add_mark(void)
1289{
1290 int letter;
1291
1292 print_statusline("Mark: ");
1293 letter = less_getch(sizeof("Mark: ") - 1);
1294
1295 if (isalpha(letter)) {
1296
1297 if (num_marks == 14)
1298 num_marks = 0;
1299
1300 mark_lines[num_marks][0] = letter;
1301 mark_lines[num_marks][1] = cur_fline;
1302 num_marks++;
1303 } else {
1304 print_statusline("Invalid mark letter");
1305 }
1306}
1307
1308static void goto_mark(void)
1309{
1310 int letter;
1311 int i;
1312
1313 print_statusline("Go to mark: ");
1314 letter = less_getch(sizeof("Go to mark: ") - 1);
1315 clear_line();
1316
1317 if (isalpha(letter)) {
1318 for (i = 0; i <= num_marks; i++)
1319 if (letter == mark_lines[i][0]) {
1320 buffer_line(mark_lines[i][1]);
1321 break;
1322 }
1323 if (num_marks == 14 && letter != mark_lines[14][0])
1324 print_statusline("Mark not set");
1325 } else
1326 print_statusline("Invalid mark letter");
1327}
1328#endif
1329
1330#if ENABLE_FEATURE_LESS_BRACKETS
1331static char opp_bracket(char bracket)
1332{
1333 switch (bracket) {
1334 case '{': case '[':
1335 bracket++;
1336 case '(':
1337 bracket++;
1338 break;
1339 case '}': case ']':
1340 bracket--;
1341 case ')':
1342 bracket--;
1343 break;
1344 };
1345 return bracket;
1346}
1347
1348static void match_right_bracket(char bracket)
1349{
1350 unsigned i;
1351
1352 if (strchr(flines[cur_fline], bracket) == NULL) {
1353 print_statusline("No bracket in top line");
1354 return;
1355 }
1356 bracket = opp_bracket(bracket);
1357 for (i = cur_fline + 1; i < max_fline; i++) {
1358 if (strchr(flines[i], bracket) != NULL) {
1359 buffer_line(i);
1360 return;
1361 }
1362 }
1363 print_statusline("No matching bracket found");
1364}
1365
1366static void match_left_bracket(char bracket)
1367{
1368 int i;
1369
1370 if (strchr(flines[cur_fline + max_displayed_line], bracket) == NULL) {
1371 print_statusline("No bracket in bottom line");
1372 return;
1373 }
1374
1375 bracket = opp_bracket(bracket);
1376 for (i = cur_fline + max_displayed_line; i >= 0; i--) {
1377 if (strchr(flines[i], bracket) != NULL) {
1378 buffer_line(i);
1379 return;
1380 }
1381 }
1382 print_statusline("No matching bracket found");
1383}
1384#endif
1385
1386static void keypress_process(int keypress)
1387{
1388 switch (keypress) {
1389 case KEYCODE_DOWN: case 'e': case 'j': case 0x0d:
1390 buffer_down(1);
1391 break;
1392 case KEYCODE_UP: case 'y': case 'k':
1393 buffer_up(1);
1394 break;
1395 case KEYCODE_PAGEDOWN: case ' ': case 'z': case 'f':
1396 buffer_down(max_displayed_line + 1);
1397 break;
1398 case KEYCODE_PAGEUP: case 'w': case 'b':
1399 buffer_up(max_displayed_line + 1);
1400 break;
1401 case 'd':
1402 buffer_down((max_displayed_line + 1) / 2);
1403 break;
1404 case 'u':
1405 buffer_up((max_displayed_line + 1) / 2);
1406 break;
1407 case KEYCODE_HOME: case 'g': case 'p': case '<': case '%':
1408 buffer_line(0);
1409 break;
1410 case KEYCODE_END: case 'G': case '>':
1411 cur_fline = MAXLINES;
1412 read_lines();
1413 buffer_line(cur_fline);
1414 break;
1415 case 'q': case 'Q':
1416 less_exit(EXIT_SUCCESS);
1417 break;
1418#if ENABLE_FEATURE_LESS_MARKS
1419 case 'm':
1420 add_mark();
1421 buffer_print();
1422 break;
1423 case '\'':
1424 goto_mark();
1425 buffer_print();
1426 break;
1427#endif
1428 case 'r': case 'R':
1429 buffer_print();
1430 break;
1431
1432
1433
1434 case 's':
1435 save_input_to_file();
1436 break;
1437 case 'E':
1438 examine_file();
1439 break;
1440#if ENABLE_FEATURE_LESS_FLAGS
1441 case '=':
1442 m_status_print();
1443 break;
1444#endif
1445#if ENABLE_FEATURE_LESS_REGEXP
1446 case '/':
1447 option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
1448 regex_process();
1449 break;
1450 case 'n':
1451 goto_match(match_pos + 1);
1452 break;
1453 case 'N':
1454 goto_match(match_pos - 1);
1455 break;
1456 case '?':
1457 option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
1458 regex_process();
1459 break;
1460#endif
1461#if ENABLE_FEATURE_LESS_DASHCMD
1462 case '-':
1463 flag_change();
1464 buffer_print();
1465 break;
1466#ifdef BLOAT
1467 case '_':
1468 show_flag_status();
1469 break;
1470#endif
1471#endif
1472#if ENABLE_FEATURE_LESS_BRACKETS
1473 case '{': case '(': case '[':
1474 match_right_bracket(keypress);
1475 break;
1476 case '}': case ')': case ']':
1477 match_left_bracket(keypress);
1478 break;
1479#endif
1480 case ':':
1481 colon_process();
1482 break;
1483 }
1484
1485 if (isdigit(keypress))
1486 number_process(keypress);
1487}
1488
1489static void sig_catcher(int sig)
1490{
1491 less_exit(- sig);
1492}
1493
1494#if ENABLE_FEATURE_LESS_WINCH
1495static void sigwinch_handler(int sig UNUSED_PARAM)
1496{
1497 winch_counter++;
1498}
1499#endif
1500
1501int less_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1502int less_main(int argc, char **argv)
1503{
1504 int keypress;
1505
1506 INIT_G();
1507
1508
1509
1510
1511 getopt32(argv, "EMmN~I" IF_FEATURE_LESS_DASHCMD("S"));
1512 argc -= optind;
1513 argv += optind;
1514 num_files = argc;
1515 files = argv;
1516
1517
1518
1519 if (!isatty(STDOUT_FILENO))
1520 return bb_cat(argv);
1521
1522 if (!num_files) {
1523 if (isatty(STDIN_FILENO)) {
1524
1525 bb_error_msg("missing filename");
1526 bb_show_usage();
1527 }
1528 } else {
1529 filename = xstrdup(files[0]);
1530 }
1531
1532 if (option_mask32 & FLAG_TILDE)
1533 empty_line_marker = "";
1534
1535 kbd_fd = open(CURRENT_TTY, O_RDONLY);
1536 if (kbd_fd < 0)
1537 return bb_cat(argv);
1538 ndelay_on(kbd_fd);
1539
1540 tcgetattr(kbd_fd, &term_orig);
1541 term_less = term_orig;
1542 term_less.c_lflag &= ~(ICANON | ECHO);
1543 term_less.c_iflag &= ~(IXON | ICRNL);
1544
1545 term_less.c_cc[VMIN] = 1;
1546 term_less.c_cc[VTIME] = 0;
1547
1548 get_terminal_width_height(kbd_fd, &width, &max_displayed_line);
1549
1550 if (width < 20 || max_displayed_line < 3)
1551 return bb_cat(argv);
1552 max_displayed_line -= 2;
1553
1554
1555 bb_signals(BB_FATAL_SIGS, sig_catcher);
1556#if ENABLE_FEATURE_LESS_WINCH
1557 signal(SIGWINCH, sigwinch_handler);
1558#endif
1559
1560 buffer = xmalloc((max_displayed_line+1) * sizeof(char *));
1561 reinitialize();
1562 while (1) {
1563#if ENABLE_FEATURE_LESS_WINCH
1564 while (WINCH_COUNTER) {
1565 again:
1566 winch_counter--;
1567 get_terminal_width_height(kbd_fd, &width, &max_displayed_line);
1568
1569 if (width < 20)
1570 width = 20;
1571 if (max_displayed_line < 3)
1572 max_displayed_line = 3;
1573 max_displayed_line -= 2;
1574 free(buffer);
1575 buffer = xmalloc((max_displayed_line+1) * sizeof(char *));
1576
1577
1578 if (WINCH_COUNTER)
1579 goto again;
1580 re_wrap();
1581 if (WINCH_COUNTER)
1582 goto again;
1583 buffer_fill_and_print();
1584
1585
1586 }
1587#endif
1588 keypress = less_getch(-1);
1589 keypress_process(keypress);
1590 }
1591}
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799