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