blob: cd47590ee189d475fdd7b875f06d726e549ff69f [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 *
Rob Landley9200e792005-09-15 19:26:59 +00005 * Copyright (C) 2005 by Rob Sullivan <cogito.ergo.cogito@gmail.com>
6 *
Mike Frysingerf284c762006-04-16 20:38:26 +00007 * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
8 */
9
10/*
Denis Vlasenko3f3190e2006-12-21 00:22:03 +000011 * TODO:
12 * - Add more regular expression support - search modifiers, certain matches, etc.
13 * - Add more complex bracket searching - currently, nested brackets are
Denis Vlasenko5a4f0992006-12-25 01:23:02 +000014 * not considered.
Denis Vlasenko3f3190e2006-12-21 00:22:03 +000015 * - Add support for "F" as an input. This causes less to act in
Denis Vlasenko5a4f0992006-12-25 01:23:02 +000016 * a similar way to tail -f.
Denis Vlasenko3f3190e2006-12-21 00:22:03 +000017 * - Allow horizontal scrolling.
Rob Landley9200e792005-09-15 19:26:59 +000018 *
Denis Vlasenko3f3190e2006-12-21 00:22:03 +000019 * Notes:
20 * - the inp file pointer is used so that keyboard input works after
Denis Vlasenko5a4f0992006-12-25 01:23:02 +000021 * redirected input has been read from stdin
Denis Vlasenko3f3190e2006-12-21 00:22:03 +000022 */
Rob Landley9200e792005-09-15 19:26:59 +000023
Denis Vlasenko18d6fc12007-03-08 17:52:36 +000024#include <sched.h> /* sched_yield() */
25
Denis Vlasenkob6adbf12007-05-26 19:00:18 +000026#include "libbb.h"
Denis Vlasenko9a7cef92006-12-20 02:46:48 +000027#if ENABLE_FEATURE_LESS_REGEXP
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +000028#include "xregex.h"
29#endif
30
Denis Vlasenkof4dff772006-12-24 07:14:17 +000031/* FIXME: currently doesn't work right */
32#undef ENABLE_FEATURE_LESS_FLAGCS
33#define ENABLE_FEATURE_LESS_FLAGCS 0
Rob Landley9200e792005-09-15 19:26:59 +000034
35/* The escape codes for highlighted and normal text */
36#define HIGHLIGHT "\033[7m"
37#define NORMAL "\033[0m"
Rob Landley9200e792005-09-15 19:26:59 +000038/* The escape code to clear the screen */
"Vladimir N. Oleynik"a0ae6de2005-09-19 10:28:43 +000039#define CLEAR "\033[H\033[J"
Denis Vlasenkoe865e812006-12-21 13:24:58 +000040/* The escape code to clear to end of line */
41#define CLEAR_2_EOL "\033[K"
Rob Landley9200e792005-09-15 19:26:59 +000042
Denis Vlasenkof4dff772006-12-24 07:14:17 +000043/* These are the escape sequences corresponding to special keys */
44enum {
45 REAL_KEY_UP = 'A',
46 REAL_KEY_DOWN = 'B',
47 REAL_KEY_RIGHT = 'C',
48 REAL_KEY_LEFT = 'D',
49 REAL_PAGE_UP = '5',
50 REAL_PAGE_DOWN = '6',
Denis Vlasenkof5a15762007-03-09 08:55:23 +000051 REAL_KEY_HOME = '7', // vt100? linux vt? or what?
Denis Vlasenkof4dff772006-12-24 07:14:17 +000052 REAL_KEY_END = '8',
Denis Vlasenkof5a15762007-03-09 08:55:23 +000053 REAL_KEY_HOME_ALT = '1', // ESC [1~ (vt100? linux vt? or what?)
Denis Vlasenkoc86e0522007-03-20 11:30:28 +000054 REAL_KEY_END_ALT = '4', // ESC [4~
Denis Vlasenkob30418a2007-02-14 20:49:14 +000055 REAL_KEY_HOME_XTERM = 'H',
56 REAL_KEY_END_XTERM = 'F',
Rob Landley9200e792005-09-15 19:26:59 +000057
Denis Vlasenkof4dff772006-12-24 07:14:17 +000058/* These are the special codes assigned by this program to the special keys */
59 KEY_UP = 20,
60 KEY_DOWN = 21,
61 KEY_RIGHT = 22,
62 KEY_LEFT = 23,
63 PAGE_UP = 24,
64 PAGE_DOWN = 25,
65 KEY_HOME = 26,
66 KEY_END = 27,
67
68/* Absolute max of lines eaten */
69 MAXLINES = CONFIG_FEATURE_LESS_MAXLINES,
70
71/* This many "after the end" lines we will show (at max) */
72 TILDES = 1,
73};
74
Rob Landley9200e792005-09-15 19:26:59 +000075/* Command line options */
Denis Vlasenkof4dff772006-12-24 07:14:17 +000076enum {
77 FLAG_E = 1,
78 FLAG_M = 1 << 1,
79 FLAG_m = 1 << 2,
80 FLAG_N = 1 << 3,
81 FLAG_TILDE = 1 << 4,
Bernhard Reutner-Fischer48a67732008-09-26 14:10:17 +000082 FLAG_I = 1 << 5,
Bernhard Reutner-Fischer882e60c2006-12-04 16:04:50 +000083/* hijack command line options variable for internal state vars */
Denis Vlasenkof4dff772006-12-24 07:14:17 +000084 LESS_STATE_MATCH_BACKWARDS = 1 << 15,
85};
Rob Landley9200e792005-09-15 19:26:59 +000086
Denis Vlasenkoa1d24a02007-05-31 15:55:03 +000087#if !ENABLE_FEATURE_LESS_REGEXP
Denis Vlasenko3bba5452006-12-30 17:57:03 +000088enum { pattern_valid = 0 };
Rob Landley9200e792005-09-15 19:26:59 +000089#endif
90
Denis Vlasenkoa1d24a02007-05-31 15:55:03 +000091struct globals {
92 int cur_fline; /* signed */
93 int kbd_fd; /* fd to get input from */
Denis Vlasenko33196372008-02-23 01:25:38 +000094 int less_gets_pos;
Denis Vlasenkoa1d24a02007-05-31 15:55:03 +000095/* last position in last line, taking into account tabs */
96 size_t linepos;
97 unsigned max_displayed_line;
98 unsigned max_fline;
99 unsigned max_lineno; /* this one tracks linewrap */
100 unsigned width;
101 ssize_t eof_error; /* eof if 0, error if < 0 */
Denis Vlasenko107fe7c2008-03-17 08:38:45 +0000102 ssize_t readpos;
103 ssize_t readeof; /* must be signed */
Denis Vlasenkoa1d24a02007-05-31 15:55:03 +0000104 const char **buffer;
105 const char **flines;
106 const char *empty_line_marker;
107 unsigned num_files;
108 unsigned current_file;
109 char *filename;
110 char **files;
111#if ENABLE_FEATURE_LESS_MARKS
112 unsigned num_marks;
113 unsigned mark_lines[15][2];
114#endif
115#if ENABLE_FEATURE_LESS_REGEXP
116 unsigned *match_lines;
117 int match_pos; /* signed! */
Denis Vlasenko107fe7c2008-03-17 08:38:45 +0000118 int wanted_match; /* signed! */
119 int num_matches;
Denis Vlasenkoa1d24a02007-05-31 15:55:03 +0000120 regex_t pattern;
121 smallint pattern_valid;
122#endif
123 smallint terminated;
124 struct termios term_orig, term_less;
125};
126#define G (*ptr_to_globals)
127#define cur_fline (G.cur_fline )
128#define kbd_fd (G.kbd_fd )
Denis Vlasenko33196372008-02-23 01:25:38 +0000129#define less_gets_pos (G.less_gets_pos )
Denis Vlasenkoa1d24a02007-05-31 15:55:03 +0000130#define linepos (G.linepos )
131#define max_displayed_line (G.max_displayed_line)
132#define max_fline (G.max_fline )
133#define max_lineno (G.max_lineno )
134#define width (G.width )
135#define eof_error (G.eof_error )
136#define readpos (G.readpos )
137#define readeof (G.readeof )
138#define buffer (G.buffer )
139#define flines (G.flines )
140#define empty_line_marker (G.empty_line_marker )
141#define num_files (G.num_files )
142#define current_file (G.current_file )
143#define filename (G.filename )
144#define files (G.files )
145#define num_marks (G.num_marks )
146#define mark_lines (G.mark_lines )
Denis Vlasenko342b0ab2007-05-31 23:06:18 +0000147#if ENABLE_FEATURE_LESS_REGEXP
Denis Vlasenkoa1d24a02007-05-31 15:55:03 +0000148#define match_lines (G.match_lines )
149#define match_pos (G.match_pos )
150#define num_matches (G.num_matches )
Denis Vlasenko107fe7c2008-03-17 08:38:45 +0000151#define wanted_match (G.wanted_match )
Denis Vlasenkoa1d24a02007-05-31 15:55:03 +0000152#define pattern (G.pattern )
153#define pattern_valid (G.pattern_valid )
Denis Vlasenko342b0ab2007-05-31 23:06:18 +0000154#endif
Denis Vlasenkoa1d24a02007-05-31 15:55:03 +0000155#define terminated (G.terminated )
156#define term_orig (G.term_orig )
157#define term_less (G.term_less )
158#define INIT_G() do { \
Denis Vlasenko574f2f42008-02-27 18:41:59 +0000159 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
160 less_gets_pos = -1; \
161 empty_line_marker = "~"; \
162 num_files = 1; \
163 current_file = 1; \
164 eof_error = 1; \
165 terminated = 1; \
Denis Vlasenko107fe7c2008-03-17 08:38:45 +0000166 USE_FEATURE_LESS_REGEXP(wanted_match = -1;) \
Denis Vlasenko574f2f42008-02-27 18:41:59 +0000167} while (0)
Rob Landley9200e792005-09-15 19:26:59 +0000168
169/* Reset terminal input to normal */
Mike Frysinger3a2b1032006-04-16 20:34:26 +0000170static void set_tty_cooked(void)
171{
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000172 fflush(stdout);
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000173 tcsetattr(kbd_fd, TCSANOW, &term_orig);
Rob Landley9200e792005-09-15 19:26:59 +0000174}
175
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000176/* Move the cursor to a position (x,y), where (0,0) is the
Rob Landley9200e792005-09-15 19:26:59 +0000177 top-left corner of the console */
Denis Vlasenkoe865e812006-12-21 13:24:58 +0000178static void move_cursor(int line, int row)
Mike Frysinger3a2b1032006-04-16 20:34:26 +0000179{
Denis Vlasenkoe865e812006-12-21 13:24:58 +0000180 printf("\033[%u;%uH", line, row);
Rob Landley9200e792005-09-15 19:26:59 +0000181}
182
Mike Frysinger3a2b1032006-04-16 20:34:26 +0000183static void clear_line(void)
184{
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000185 printf("\033[%u;0H" CLEAR_2_EOL, max_displayed_line + 2);
Denis Vlasenkoe865e812006-12-21 13:24:58 +0000186}
187
188static void print_hilite(const char *str)
189{
190 printf(HIGHLIGHT"%s"NORMAL, str);
Rob Landley9200e792005-09-15 19:26:59 +0000191}
192
Denis Vlasenkof1282a82006-12-21 17:03:20 +0000193static void print_statusline(const char *str)
194{
195 clear_line();
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000196 printf(HIGHLIGHT"%.*s"NORMAL, width - 1, str);
Denis Vlasenkof1282a82006-12-21 17:03:20 +0000197}
198
Denis Vlasenko3fe4f982008-06-09 16:02:39 +0000199/* Exit the program gracefully */
200static void less_exit(int code)
201{
202 set_tty_cooked();
203 clear_line();
204 if (code < 0)
205 kill_myself_with_sig(- code); /* does not return */
206 exit(code);
207}
208
Denis Vlasenko5a4f0992006-12-25 01:23:02 +0000209#if ENABLE_FEATURE_LESS_REGEXP
210static void fill_match_lines(unsigned pos);
211#else
212#define fill_match_lines(pos) ((void)0)
213#endif
214
Denis Vlasenko22a9a3c2007-05-31 15:56:10 +0000215/* Devilishly complex routine.
216 *
217 * Has to deal with EOF and EPIPE on input,
218 * with line wrapping, with last line not ending in '\n'
219 * (possibly not ending YET!), with backspace and tabs.
Denis Vlasenkoc2f011a2007-05-31 21:31:56 +0000220 * It reads input again if last time we got an EOF (thus supporting
221 * growing files) or EPIPE (watching output of slow process like make).
Denis Vlasenko22a9a3c2007-05-31 15:56:10 +0000222 *
223 * Variables used:
224 * flines[] - array of lines already read. Linewrap may cause
225 * one source file line to occupy several flines[n].
226 * flines[max_fline] - last line, possibly incomplete.
227 * terminated - 1 if flines[max_fline] is 'terminated'
228 * (if there was '\n' [which isn't stored itself, we just remember
229 * that it was seen])
230 * max_lineno - last line's number, this one doesn't increment
231 * on line wrap, only on "real" new lines.
232 * readbuf[0..readeof-1] - small preliminary buffer.
233 * readbuf[readpos] - next character to add to current line.
234 * linepos - screen line position of next char to be read
235 * (takes into account tabs and backspaces)
236 * eof_error - < 0 error, == 0 EOF, > 0 not EOF/error
237 */
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000238static void read_lines(void)
Mike Frysinger3a2b1032006-04-16 20:34:26 +0000239{
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000240#define readbuf bb_common_bufsiz1
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000241 char *current_line, *p;
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000242 int w = width;
243 char last_terminated = terminated;
Denis Vlasenko107fe7c2008-03-17 08:38:45 +0000244#if ENABLE_FEATURE_LESS_REGEXP
245 unsigned old_max_fline = max_fline;
246 time_t last_time = 0;
247 unsigned seconds_p1 = 3; /* seconds_to_loop + 1 */
248#endif
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000249
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000250 if (option_mask32 & FLAG_N)
Denis Vlasenkof65d1332006-12-21 15:23:45 +0000251 w -= 8;
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000252
Denis Vlasenko107fe7c2008-03-17 08:38:45 +0000253 USE_FEATURE_LESS_REGEXP(again0:)
254
255 p = current_line = xmalloc(w);
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000256 max_fline += last_terminated;
257 if (!last_terminated) {
258 const char *cp = flines[max_fline];
259 if (option_mask32 & FLAG_N)
260 cp += 8;
261 strcpy(current_line, cp);
262 p += strlen(current_line);
Denis Vlasenko107fe7c2008-03-17 08:38:45 +0000263 free((char*)flines[max_fline]);
Denis Vlasenko22a9a3c2007-05-31 15:56:10 +0000264 /* linepos is still valid from previous read_lines() */
Denis Vlasenko5a4f0992006-12-25 01:23:02 +0000265 } else {
266 linepos = 0;
Denis Vlasenko9a7cef92006-12-20 02:46:48 +0000267 }
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000268
Denis Vlasenko107fe7c2008-03-17 08:38:45 +0000269 while (1) { /* read lines until we reach cur_fline or wanted_match */
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000270 *p = '\0';
Denis Vlasenkof65d1332006-12-21 15:23:45 +0000271 terminated = 0;
Denis Vlasenko107fe7c2008-03-17 08:38:45 +0000272 while (1) { /* read chars until we have a line */
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000273 char c;
Denis Vlasenkob30418a2007-02-14 20:49:14 +0000274 /* if no unprocessed chars left, eat more */
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000275 if (readpos >= readeof) {
Denis Vlasenko5a4f0992006-12-25 01:23:02 +0000276 ndelay_on(0);
Bernhard Reutner-Fischer5e25ddb2008-05-19 09:48:17 +0000277 eof_error = safe_read(STDIN_FILENO, readbuf, sizeof(readbuf));
Denis Vlasenko107fe7c2008-03-17 08:38:45 +0000278 ndelay_off(0);
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000279 readpos = 0;
280 readeof = eof_error;
Denis Vlasenko107fe7c2008-03-17 08:38:45 +0000281 if (eof_error <= 0)
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000282 goto reached_eof;
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000283 }
284 c = readbuf[readpos];
Denis Vlasenko50ddabc2006-12-31 19:36:01 +0000285 /* backspace? [needed for manpages] */
286 /* <tab><bs> is (a) insane and */
287 /* (b) harder to do correctly, so we refuse to do it */
288 if (c == '\x8' && linepos && p[-1] != '\t') {
Denis Vlasenko95b30712006-12-31 19:23:31 +0000289 readpos++; /* eat it */
Denis Vlasenko95b30712006-12-31 19:23:31 +0000290 linepos--;
Denis Vlasenko22a9a3c2007-05-31 15:56:10 +0000291 /* was buggy (p could end up <= current_line)... */
Denis Vlasenko50ddabc2006-12-31 19:36:01 +0000292 *--p = '\0';
Denis Vlasenko95b30712006-12-31 19:23:31 +0000293 continue;
294 }
Denis Vlasenko22a9a3c2007-05-31 15:56:10 +0000295 {
296 size_t new_linepos = linepos + 1;
297 if (c == '\t') {
298 new_linepos += 7;
299 new_linepos &= (~7);
300 }
Denis Vlasenko6b06cb82008-05-15 21:30:45 +0000301 if ((int)new_linepos >= w)
Denis Vlasenko22a9a3c2007-05-31 15:56:10 +0000302 break;
303 linepos = new_linepos;
304 }
Denis Vlasenko95b30712006-12-31 19:23:31 +0000305 /* ok, we will eat this char */
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000306 readpos++;
Denis Vlasenko22a9a3c2007-05-31 15:56:10 +0000307 if (c == '\n') {
308 terminated = 1;
309 linepos = 0;
310 break;
311 }
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000312 /* NUL is substituted by '\n'! */
313 if (c == '\0') c = '\n';
314 *p++ = c;
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000315 *p = '\0';
Denis Vlasenko107fe7c2008-03-17 08:38:45 +0000316 } /* end of "read chars until we have a line" loop */
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000317 /* Corner case: linewrap with only "" wrapping to next line */
318 /* Looks ugly on screen, so we do not store this empty line */
Denis Vlasenkof65d1332006-12-21 15:23:45 +0000319 if (!last_terminated && !current_line[0]) {
320 last_terminated = 1;
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000321 max_lineno++;
Denis Vlasenko107fe7c2008-03-17 08:38:45 +0000322 continue;
Denis Vlasenko9a7cef92006-12-20 02:46:48 +0000323 }
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000324 reached_eof:
Denis Vlasenkof65d1332006-12-21 15:23:45 +0000325 last_terminated = terminated;
Denis Vlasenkodeeed592008-07-08 05:14:36 +0000326 flines = xrealloc_vector(flines, 8, max_fline);
Denis Vlasenko9a7cef92006-12-20 02:46:48 +0000327 if (option_mask32 & FLAG_N) {
Denis Vlasenkof65d1332006-12-21 15:23:45 +0000328 /* Width of 7 preserves tab spacing in the text */
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000329 flines[max_fline] = xasprintf(
330 (max_lineno <= 9999999) ? "%7u %s" : "%07u %s",
331 max_lineno % 10000000, current_line);
Denis Vlasenko9a7cef92006-12-20 02:46:48 +0000332 free(current_line);
Denis Vlasenkof65d1332006-12-21 15:23:45 +0000333 if (terminated)
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000334 max_lineno++;
Denis Vlasenko9a7cef92006-12-20 02:46:48 +0000335 } else {
Denis Vlasenkodeeed592008-07-08 05:14:36 +0000336 flines[max_fline] = xrealloc(current_line, strlen(current_line) + 1);
Denis Vlasenko9a7cef92006-12-20 02:46:48 +0000337 }
Denis Vlasenkofdcfa2a2007-05-31 23:55:39 +0000338 if (max_fline >= MAXLINES) {
339 eof_error = 0; /* Pretend we saw EOF */
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000340 break;
Denis Vlasenkofdcfa2a2007-05-31 23:55:39 +0000341 }
Denis Vlasenko107fe7c2008-03-17 08:38:45 +0000342 if (max_fline > cur_fline + max_displayed_line) {
343#if !ENABLE_FEATURE_LESS_REGEXP
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000344 break;
Denis Vlasenko107fe7c2008-03-17 08:38:45 +0000345#else
346 if (wanted_match >= num_matches) { /* goto_match called us */
347 fill_match_lines(old_max_fline);
348 old_max_fline = max_fline;
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000349 }
Denis Vlasenko107fe7c2008-03-17 08:38:45 +0000350 if (wanted_match < num_matches)
351 break;
352#endif
353 }
354 if (eof_error <= 0) {
355 if (eof_error < 0) {
356 if (errno == EAGAIN) {
357 /* not yet eof or error, reset flag (or else
358 * we will hog CPU - select() will return
359 * immediately */
360 eof_error = 1;
361 } else {
362 print_statusline("read error");
363 }
364 }
365#if !ENABLE_FEATURE_LESS_REGEXP
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000366 break;
Denis Vlasenko107fe7c2008-03-17 08:38:45 +0000367#else
368 if (wanted_match < num_matches) {
369 break;
370 } else { /* goto_match called us */
371 time_t t = time(NULL);
372 if (t != last_time) {
373 last_time = t;
374 if (--seconds_p1 == 0)
375 break;
376 }
377 sched_yield();
378 goto again0; /* go loop again (max 2 seconds) */
379 }
380#endif
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000381 }
382 max_fline++;
383 current_line = xmalloc(w);
384 p = current_line;
385 linepos = 0;
Denis Vlasenko107fe7c2008-03-17 08:38:45 +0000386 } /* end of "read lines until we reach cur_fline" loop */
Denis Vlasenko5a4f0992006-12-25 01:23:02 +0000387 fill_match_lines(old_max_fline);
Denis Vlasenko24f824e2008-04-13 08:32:51 +0000388#if ENABLE_FEATURE_LESS_REGEXP
389 /* prevent us from being stuck in search for a match */
390 wanted_match = -1;
391#endif
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000392#undef readbuf
Rob Landley9200e792005-09-15 19:26:59 +0000393}
394
Denis Vlasenko9a7cef92006-12-20 02:46:48 +0000395#if ENABLE_FEATURE_LESS_FLAGS
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000396/* Interestingly, writing calc_percent as a function saves around 32 bytes
Rob Landleyd57ae8b2005-09-18 00:58:49 +0000397 * on my build. */
Mike Frysinger3a2b1032006-04-16 20:34:26 +0000398static int calc_percent(void)
399{
Denis Vlasenko5a4f0992006-12-25 01:23:02 +0000400 unsigned p = (100 * (cur_fline+max_displayed_line+1) + max_fline/2) / (max_fline+1);
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000401 return p <= 100 ? p : 100;
Rob Landleyd57ae8b2005-09-18 00:58:49 +0000402}
403
Rob Landley9200e792005-09-15 19:26:59 +0000404/* Print a status line if -M was specified */
Mike Frysinger3a2b1032006-04-16 20:34:26 +0000405static void m_status_print(void)
406{
Rob Landley9200e792005-09-15 19:26:59 +0000407 int percentage;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000408
Denis Vlasenko33196372008-02-23 01:25:38 +0000409 if (less_gets_pos >= 0) /* don't touch statusline while input is done! */
410 return;
411
Denis Vlasenkof1282a82006-12-21 17:03:20 +0000412 clear_line();
Denis Vlasenkoe147a722006-12-21 13:26:54 +0000413 printf(HIGHLIGHT"%s", filename);
414 if (num_files > 1)
415 printf(" (file %i of %i)", current_file, num_files);
416 printf(" lines %i-%i/%i ",
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000417 cur_fline + 1, cur_fline + max_displayed_line + 1,
418 max_fline + 1);
Denis Vlasenko6b06cb82008-05-15 21:30:45 +0000419 if (cur_fline >= (int)(max_fline - max_displayed_line)) {
Denis Vlasenkod51d14e2006-12-21 13:57:37 +0000420 printf("(END)"NORMAL);
Denis Vlasenkoe865e812006-12-21 13:24:58 +0000421 if (num_files > 1 && current_file != num_files)
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000422 printf(HIGHLIGHT" - next: %s"NORMAL, files[current_file]);
Denis Vlasenkoe147a722006-12-21 13:26:54 +0000423 return;
Rob Landley9200e792005-09-15 19:26:59 +0000424 }
Denis Vlasenkoe147a722006-12-21 13:26:54 +0000425 percentage = calc_percent();
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000426 printf("%i%%"NORMAL, percentage);
Rob Landley9200e792005-09-15 19:26:59 +0000427}
Denis Vlasenkoe147a722006-12-21 13:26:54 +0000428#endif
429
Rob Landley9200e792005-09-15 19:26:59 +0000430/* Print the status line */
Mike Frysinger3a2b1032006-04-16 20:34:26 +0000431static void status_print(void)
432{
Denis Vlasenkoe147a722006-12-21 13:26:54 +0000433 const char *p;
434
Denis Vlasenko33196372008-02-23 01:25:38 +0000435 if (less_gets_pos >= 0) /* don't touch statusline while input is done! */
436 return;
437
Rob Landley9200e792005-09-15 19:26:59 +0000438 /* Change the status if flags have been set */
Denis Vlasenko9a7cef92006-12-20 02:46:48 +0000439#if ENABLE_FEATURE_LESS_FLAGS
Denis Vlasenkoe147a722006-12-21 13:26:54 +0000440 if (option_mask32 & (FLAG_M|FLAG_m)) {
Rob Landley9200e792005-09-15 19:26:59 +0000441 m_status_print();
Denis Vlasenkoe147a722006-12-21 13:26:54 +0000442 return;
Rob Landley9200e792005-09-15 19:26:59 +0000443 }
Denis Vlasenkoe147a722006-12-21 13:26:54 +0000444 /* No flags set */
Rob Landley9200e792005-09-15 19:26:59 +0000445#endif
Denis Vlasenkof1282a82006-12-21 17:03:20 +0000446
447 clear_line();
Denis Vlasenko6b06cb82008-05-15 21:30:45 +0000448 if (cur_fline && cur_fline < (int)(max_fline - max_displayed_line)) {
Denis Vlasenko4daad902007-09-27 10:20:47 +0000449 bb_putchar(':');
Denis Vlasenkoe147a722006-12-21 13:26:54 +0000450 return;
451 }
Denis Vlasenkod51d14e2006-12-21 13:57:37 +0000452 p = "(END)";
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000453 if (!cur_fline)
Denis Vlasenkoe147a722006-12-21 13:26:54 +0000454 p = filename;
455 if (num_files > 1) {
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000456 printf(HIGHLIGHT"%s (file %i of %i)"NORMAL,
Denis Vlasenkoe147a722006-12-21 13:26:54 +0000457 p, current_file, num_files);
458 return;
459 }
460 print_hilite(p);
Rob Landley9200e792005-09-15 19:26:59 +0000461}
462
Denis Vlasenko5c1de362007-03-08 16:44:32 +0000463static void cap_cur_fline(int nlines)
464{
465 int diff;
466 if (cur_fline < 0)
467 cur_fline = 0;
468 if (cur_fline + max_displayed_line > max_fline + TILDES) {
469 cur_fline -= nlines;
470 if (cur_fline < 0)
471 cur_fline = 0;
472 diff = max_fline - (cur_fline + max_displayed_line) + TILDES;
473 /* As the number of lines requested was too large, we just move
474 to the end of the file */
475 if (diff > 0)
476 cur_fline += diff;
477 }
478}
479
Denis Vlasenko6ca409e2007-08-12 20:58:27 +0000480static const char controls[] ALIGN1 =
Denis Vlasenkof65d1332006-12-21 15:23:45 +0000481 /* NUL: never encountered; TAB: not converted */
482 /**/"\x01\x02\x03\x04\x05\x06\x07\x08" "\x0a\x0b\x0c\x0d\x0e\x0f"
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000483 "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
484 "\x7f\x9b"; /* DEL and infamous Meta-ESC :( */
Denis Vlasenko6ca409e2007-08-12 20:58:27 +0000485static const char ctrlconv[] ALIGN1 =
Denis Vlasenkof65d1332006-12-21 15:23:45 +0000486 /* '\n': it's a former NUL - subst with '@', not 'J' */
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000487 "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x40\x4b\x4c\x4d\x4e\x4f"
488 "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f";
489
Denis Vlasenko3bba5452006-12-30 17:57:03 +0000490#if ENABLE_FEATURE_LESS_REGEXP
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000491static void print_found(const char *line)
492{
493 int match_status;
494 int eflags;
495 char *growline;
496 regmatch_t match_structs;
497
498 char buf[width];
499 const char *str = line;
500 char *p = buf;
501 size_t n;
502
503 while (*str) {
504 n = strcspn(str, controls);
505 if (n) {
506 if (!str[n]) break;
507 memcpy(p, str, n);
508 p += n;
509 str += n;
510 }
511 n = strspn(str, controls);
512 memset(p, '.', n);
513 p += n;
514 str += n;
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000515 }
516 strcpy(p, str);
517
518 /* buf[] holds quarantined version of str */
519
520 /* Each part of the line that matches has the HIGHLIGHT
521 and NORMAL escape sequences placed around it.
522 NB: we regex against line, but insert text
523 from quarantined copy (buf[]) */
524 str = buf;
525 growline = NULL;
526 eflags = 0;
527 goto start;
528
529 while (match_status == 0) {
Denis Vlasenkoe865e812006-12-21 13:24:58 +0000530 char *new = xasprintf("%s%.*s"HIGHLIGHT"%.*s"NORMAL,
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000531 growline ? : "",
532 match_structs.rm_so, str,
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000533 match_structs.rm_eo - match_structs.rm_so,
Denis Vlasenkoe865e812006-12-21 13:24:58 +0000534 str + match_structs.rm_so);
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000535 free(growline); growline = new;
536 str += match_structs.rm_eo;
537 line += match_structs.rm_eo;
538 eflags = REG_NOTBOL;
539 start:
540 /* Most of the time doesn't find the regex, optimize for that */
541 match_status = regexec(&pattern, line, 1, &match_structs, eflags);
Denis Vlasenko3fe4f982008-06-09 16:02:39 +0000542 /* if even "" matches, treat it as "not a match" */
543 if (match_structs.rm_so >= match_structs.rm_eo)
544 match_status = 1;
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000545 }
546
547 if (!growline) {
Denis Vlasenkof65d1332006-12-21 15:23:45 +0000548 printf(CLEAR_2_EOL"%s\n", str);
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000549 return;
550 }
Denis Vlasenkof65d1332006-12-21 15:23:45 +0000551 printf(CLEAR_2_EOL"%s%s\n", growline, str);
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000552 free(growline);
553}
Denis Vlasenko3bba5452006-12-30 17:57:03 +0000554#else
555void print_found(const char *line);
556#endif
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000557
558static void print_ascii(const char *str)
559{
560 char buf[width];
561 char *p;
562 size_t n;
563
Denis Vlasenkof65d1332006-12-21 15:23:45 +0000564 printf(CLEAR_2_EOL);
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000565 while (*str) {
566 n = strcspn(str, controls);
567 if (n) {
568 if (!str[n]) break;
Denis Vlasenko806116b2006-12-31 12:14:16 +0000569 printf("%.*s", (int) n, str);
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000570 str += n;
571 }
572 n = strspn(str, controls);
573 p = buf;
574 do {
Denis Vlasenkoe865e812006-12-21 13:24:58 +0000575 if (*str == 0x7f)
576 *p++ = '?';
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000577 else if (*str == (char)0x9b)
Denis Vlasenkoe865e812006-12-21 13:24:58 +0000578 /* VT100's CSI, aka Meta-ESC. Who's inventor? */
579 /* I want to know who committed this sin */
580 *p++ = '{';
581 else
582 *p++ = ctrlconv[(unsigned char)*str];
583 str++;
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000584 } while (--n);
585 *p = '\0';
Denis Vlasenkoe865e812006-12-21 13:24:58 +0000586 print_hilite(buf);
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000587 }
Denis Vlasenkof65d1332006-12-21 15:23:45 +0000588 puts(str);
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000589}
590
Rob Landley9200e792005-09-15 19:26:59 +0000591/* Print the buffer */
Mike Frysinger3a2b1032006-04-16 20:34:26 +0000592static void buffer_print(void)
593{
Denis Vlasenko6b06cb82008-05-15 21:30:45 +0000594 unsigned i;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000595
Denis Vlasenkoe865e812006-12-21 13:24:58 +0000596 move_cursor(0, 0);
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000597 for (i = 0; i <= max_displayed_line; i++)
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000598 if (pattern_valid)
599 print_found(buffer[i]);
600 else
601 print_ascii(buffer[i]);
Rob Landleyd57ae8b2005-09-18 00:58:49 +0000602 status_print();
Rob Landley9200e792005-09-15 19:26:59 +0000603}
604
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000605static void buffer_fill_and_print(void)
Mike Frysinger3a2b1032006-04-16 20:34:26 +0000606{
Denis Vlasenko6b06cb82008-05-15 21:30:45 +0000607 unsigned i;
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000608 for (i = 0; i <= max_displayed_line && cur_fline + i <= max_fline; i++) {
609 buffer[i] = flines[cur_fline + i];
Rob Landley9200e792005-09-15 19:26:59 +0000610 }
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000611 for (; i <= max_displayed_line; i++) {
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000612 buffer[i] = empty_line_marker;
Rob Landley9200e792005-09-15 19:26:59 +0000613 }
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000614 buffer_print();
Rob Landley9200e792005-09-15 19:26:59 +0000615}
616
617/* Move the buffer up and down in the file in order to scroll */
Mike Frysinger3a2b1032006-04-16 20:34:26 +0000618static void buffer_down(int nlines)
619{
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000620 cur_fline += nlines;
621 read_lines();
Denis Vlasenko5c1de362007-03-08 16:44:32 +0000622 cap_cur_fline(nlines);
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000623 buffer_fill_and_print();
Rob Landley9200e792005-09-15 19:26:59 +0000624}
625
Mike Frysinger3a2b1032006-04-16 20:34:26 +0000626static void buffer_up(int nlines)
627{
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000628 cur_fline -= nlines;
629 if (cur_fline < 0) cur_fline = 0;
630 read_lines();
631 buffer_fill_and_print();
Rob Landley9200e792005-09-15 19:26:59 +0000632}
633
Mike Frysinger3a2b1032006-04-16 20:34:26 +0000634static void buffer_line(int linenum)
635{
Denis Vlasenko5a4f0992006-12-25 01:23:02 +0000636 if (linenum < 0)
637 linenum = 0;
638 cur_fline = linenum;
639 read_lines();
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000640 if (linenum + max_displayed_line > max_fline)
641 linenum = max_fline - max_displayed_line + TILDES;
Denis Vlasenkob30418a2007-02-14 20:49:14 +0000642 if (linenum < 0)
643 linenum = 0;
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000644 cur_fline = linenum;
645 buffer_fill_and_print();
Rob Landley9200e792005-09-15 19:26:59 +0000646}
647
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000648static void open_file_and_read_lines(void)
649{
650 if (filename) {
651 int fd = xopen(filename, O_RDONLY);
652 dup2(fd, 0);
653 if (fd) close(fd);
654 } else {
655 /* "less" with no arguments in argv[] */
656 /* For status line only */
657 filename = xstrdup(bb_msg_standard_input);
658 }
659 readpos = 0;
660 readeof = 0;
661 linepos = 0;
662 terminated = 1;
663 read_lines();
664}
665
666/* Reinitialize everything for a new file - free the memory and start over */
667static void reinitialize(void)
Mike Frysinger3a2b1032006-04-16 20:34:26 +0000668{
Denis Vlasenko6b06cb82008-05-15 21:30:45 +0000669 unsigned i;
Rob Landleyd57ae8b2005-09-18 00:58:49 +0000670
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000671 if (flines) {
672 for (i = 0; i <= max_fline; i++)
673 free((void*)(flines[i]));
674 free(flines);
675 flines = NULL;
676 }
"Vladimir N. Oleynik"a0ae6de2005-09-19 10:28:43 +0000677
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000678 max_fline = -1;
679 cur_fline = 0;
680 max_lineno = 0;
681 open_file_and_read_lines();
682 buffer_fill_and_print();
683}
684
Denis Vlasenko33196372008-02-23 01:25:38 +0000685static ssize_t getch_nowait(char* input, int sz)
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000686{
Denis Vlasenko5a4f0992006-12-25 01:23:02 +0000687 ssize_t rd;
Denis Vlasenko33196372008-02-23 01:25:38 +0000688 struct pollfd pfd[2];
Denis Vlasenko5a4f0992006-12-25 01:23:02 +0000689
Denis Vlasenko33196372008-02-23 01:25:38 +0000690 pfd[0].fd = STDIN_FILENO;
691 pfd[0].events = POLLIN;
692 pfd[1].fd = kbd_fd;
693 pfd[1].events = POLLIN;
694 again:
695 tcsetattr(kbd_fd, TCSANOW, &term_less);
696 /* NB: select/poll returns whenever read will not block. Therefore:
697 * if eof is reached, select/poll will return immediately
698 * because read will immediately return 0 bytes.
699 * Even if select/poll says that input is available, read CAN block
Denis Vlasenko5a4f0992006-12-25 01:23:02 +0000700 * (switch fd into O_NONBLOCK'ed mode to avoid it)
701 */
Denis Vlasenko33196372008-02-23 01:25:38 +0000702 rd = 1;
Denis Vlasenko5a4f0992006-12-25 01:23:02 +0000703 if (max_fline <= cur_fline + max_displayed_line
704 && eof_error > 0 /* did NOT reach eof yet */
705 ) {
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000706 /* We are interested in stdin */
Denis Vlasenko33196372008-02-23 01:25:38 +0000707 rd = 0;
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000708 }
Denis Vlasenko33196372008-02-23 01:25:38 +0000709 /* position cursor if line input is done */
710 if (less_gets_pos >= 0)
711 move_cursor(max_displayed_line + 2, less_gets_pos + 1);
712 fflush(stdout);
713 safe_poll(pfd + rd, 2 - rd, -1);
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000714
715 input[0] = '\0';
Denis Vlasenko33196372008-02-23 01:25:38 +0000716 rd = safe_read(kbd_fd, input, sz); /* NB: kbd_fd is in O_NONBLOCK mode */
717 if (rd < 0 && errno == EAGAIN) {
718 /* No keyboard input -> we have input on stdin! */
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000719 read_lines();
720 buffer_fill_and_print();
721 goto again;
722 }
Denis Vlasenko33196372008-02-23 01:25:38 +0000723 set_tty_cooked();
724 return rd;
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000725}
726
727/* Grab a character from input without requiring the return key. If the
Denis Vlasenko7cea2622006-12-24 07:30:09 +0000728 * character is ASCII \033, get more characters and assign certain sequences
729 * special return codes. Note that this function works best with raw input. */
Denis Vlasenkod2172c02008-02-23 11:54:37 +0000730static int less_getch(int pos)
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000731{
Denis Vlasenko33196372008-02-23 01:25:38 +0000732 unsigned char input[16];
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000733 unsigned i;
Denis Vlasenkod2172c02008-02-23 11:54:37 +0000734
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000735 again:
Denis Vlasenkod2172c02008-02-23 11:54:37 +0000736 less_gets_pos = pos;
Denis Vlasenko46340e32007-08-03 14:17:21 +0000737 memset(input, 0, sizeof(input));
Denis Vlasenko023dc672008-05-09 18:07:15 +0000738 getch_nowait((char *)input, sizeof(input));
Denis Vlasenkod2172c02008-02-23 11:54:37 +0000739 less_gets_pos = -1;
Denis Vlasenko46340e32007-08-03 14:17:21 +0000740
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000741 /* Detect escape sequences (i.e. arrow keys) and handle
742 * them accordingly */
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000743 if (input[0] == '\033' && input[1] == '[') {
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000744 i = input[2] - REAL_KEY_UP;
745 if (i < 4)
746 return 20 + i;
747 i = input[2] - REAL_PAGE_UP;
748 if (i < 4)
749 return 24 + i;
Denis Vlasenkob30418a2007-02-14 20:49:14 +0000750 if (input[2] == REAL_KEY_HOME_XTERM)
751 return KEY_HOME;
Denis Vlasenkof5a15762007-03-09 08:55:23 +0000752 if (input[2] == REAL_KEY_HOME_ALT)
753 return KEY_HOME;
Denis Vlasenkob30418a2007-02-14 20:49:14 +0000754 if (input[2] == REAL_KEY_END_XTERM)
755 return KEY_END;
Denis Vlasenkof5a15762007-03-09 08:55:23 +0000756 if (input[2] == REAL_KEY_END_ALT)
757 return KEY_END;
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000758 return 0;
759 }
760 /* Reject almost all control chars */
761 i = input[0];
Denis Vlasenko33196372008-02-23 01:25:38 +0000762 if (i < ' ' && i != 0x0d && i != 8)
763 goto again;
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000764 return i;
765}
766
767static char* less_gets(int sz)
768{
769 char c;
Denis Vlasenko6b06cb82008-05-15 21:30:45 +0000770 unsigned i = 0;
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000771 char *result = xzalloc(1);
Denis Vlasenko33196372008-02-23 01:25:38 +0000772
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000773 while (1) {
Denis Vlasenko46340e32007-08-03 14:17:21 +0000774 c = '\0';
Denis Vlasenko33196372008-02-23 01:25:38 +0000775 less_gets_pos = sz + i;
776 getch_nowait(&c, 1);
777 if (c == 0x0d) {
Denis Vlasenkod553faf2008-02-23 12:22:17 +0000778 result[i] = '\0';
Denis Vlasenko33196372008-02-23 01:25:38 +0000779 less_gets_pos = -1;
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000780 return result;
Denis Vlasenko33196372008-02-23 01:25:38 +0000781 }
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000782 if (c == 0x7f)
783 c = 8;
784 if (c == 8 && i) {
785 printf("\x8 \x8");
786 i--;
787 }
788 if (c < ' ')
789 continue;
790 if (i >= width - sz - 1)
791 continue; /* len limit */
Denis Vlasenko4daad902007-09-27 10:20:47 +0000792 bb_putchar(c);
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000793 result[i++] = c;
794 result = xrealloc(result, i+1);
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000795 }
Rob Landleyd57ae8b2005-09-18 00:58:49 +0000796}
797
Mike Frysinger3a2b1032006-04-16 20:34:26 +0000798static void examine_file(void)
799{
Denis Vlasenko33196372008-02-23 01:25:38 +0000800 char *new_fname;
801
Denis Vlasenkof1282a82006-12-21 17:03:20 +0000802 print_statusline("Examine: ");
Denis Vlasenkod2172c02008-02-23 11:54:37 +0000803 new_fname = less_gets(sizeof("Examine: ") - 1);
Denis Vlasenko33196372008-02-23 01:25:38 +0000804 if (!new_fname[0]) {
Denis Vlasenko33196372008-02-23 01:25:38 +0000805 status_print();
Denis Vlasenkod2172c02008-02-23 11:54:37 +0000806 err:
807 free(new_fname);
Denis Vlasenko33196372008-02-23 01:25:38 +0000808 return;
809 }
Denis Vlasenkod2172c02008-02-23 11:54:37 +0000810 if (access(new_fname, R_OK) != 0) {
811 print_statusline("Cannot read this file");
812 goto err;
813 }
Denis Vlasenko9a7cef92006-12-20 02:46:48 +0000814 free(filename);
Denis Vlasenko33196372008-02-23 01:25:38 +0000815 filename = new_fname;
Denis Vlasenkoe865e812006-12-21 13:24:58 +0000816 /* files start by = argv. why we assume that argv is infinitely long??
Denis Vlasenko9a7cef92006-12-20 02:46:48 +0000817 files[num_files] = filename;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000818 current_file = num_files + 1;
Denis Vlasenkoe865e812006-12-21 13:24:58 +0000819 num_files++; */
820 files[0] = filename;
Denis Vlasenkoe147a722006-12-21 13:26:54 +0000821 num_files = current_file = 1;
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000822 reinitialize();
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000823}
824
Rob Landleyd57ae8b2005-09-18 00:58:49 +0000825/* This function changes the file currently being paged. direction can be one of the following:
826 * -1: go back one file
827 * 0: go to the first file
Denis Vlasenko7cea2622006-12-24 07:30:09 +0000828 * 1: go forward one file */
Mike Frysinger3a2b1032006-04-16 20:34:26 +0000829static void change_file(int direction)
830{
Rob Landleyd57ae8b2005-09-18 00:58:49 +0000831 if (current_file != ((direction > 0) ? num_files : 1)) {
832 current_file = direction ? current_file + direction : 1;
Denis Vlasenko9a7cef92006-12-20 02:46:48 +0000833 free(filename);
834 filename = xstrdup(files[current_file - 1]);
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000835 reinitialize();
Denis Vlasenko9a7cef92006-12-20 02:46:48 +0000836 } else {
Denis Vlasenkof1282a82006-12-21 17:03:20 +0000837 print_statusline(direction > 0 ? "No next file" : "No previous file");
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000838 }
839}
840
Mike Frysinger3a2b1032006-04-16 20:34:26 +0000841static void remove_current_file(void)
842{
Denis Vlasenko6b06cb82008-05-15 21:30:45 +0000843 unsigned i;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000844
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000845 if (num_files < 2)
846 return;
847
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000848 if (current_file != 1) {
Rob Landleyd57ae8b2005-09-18 00:58:49 +0000849 change_file(-1);
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000850 for (i = 3; i <= num_files; i++)
851 files[i - 2] = files[i - 1];
852 num_files--;
Denis Vlasenko9a7cef92006-12-20 02:46:48 +0000853 } else {
Rob Landleyd57ae8b2005-09-18 00:58:49 +0000854 change_file(1);
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000855 for (i = 2; i <= num_files; i++)
856 files[i - 2] = files[i - 1];
857 num_files--;
858 current_file--;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000859 }
860}
861
Mike Frysinger3a2b1032006-04-16 20:34:26 +0000862static void colon_process(void)
863{
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000864 int keypress;
865
866 /* Clear the current line and print a prompt */
Denis Vlasenkof1282a82006-12-21 17:03:20 +0000867 print_statusline(" :");
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000868
Denis Vlasenkod2172c02008-02-23 11:54:37 +0000869 keypress = less_getch(2);
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000870 switch (keypress) {
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000871 case 'd':
872 remove_current_file();
873 break;
874 case 'e':
875 examine_file();
876 break;
Denis Vlasenko9a7cef92006-12-20 02:46:48 +0000877#if ENABLE_FEATURE_LESS_FLAGS
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000878 case 'f':
879 m_status_print();
880 break;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000881#endif
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000882 case 'n':
883 change_file(1);
884 break;
885 case 'p':
886 change_file(-1);
887 break;
888 case 'q':
Bernhard Reutner-Fischer636a1f82008-05-19 09:29:47 +0000889 less_exit(EXIT_SUCCESS);
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000890 break;
891 case 'x':
892 change_file(0);
893 break;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000894 }
895}
896
Denis Vlasenko3bba5452006-12-30 17:57:03 +0000897#if ENABLE_FEATURE_LESS_REGEXP
Denis Vlasenkob30418a2007-02-14 20:49:14 +0000898static void normalize_match_pos(int match)
Mike Frysinger3a2b1032006-04-16 20:34:26 +0000899{
Denis Vlasenkof65d1332006-12-21 15:23:45 +0000900 if (match >= num_matches)
Denis Vlasenkob30418a2007-02-14 20:49:14 +0000901 match = num_matches - 1;
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000902 if (match < 0)
Denis Vlasenkob30418a2007-02-14 20:49:14 +0000903 match = 0;
904 match_pos = match;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000905}
906
Rob Landleya2e98042006-04-18 01:53:41 +0000907static void goto_match(int match)
908{
Denis Vlasenkob30418a2007-02-14 20:49:14 +0000909 if (!pattern_valid)
910 return;
911 if (match < 0)
912 match = 0;
913 /* Try to find next match if eof isn't reached yet */
914 if (match >= num_matches && eof_error > 0) {
Denis Vlasenko24f824e2008-04-13 08:32:51 +0000915 wanted_match = match; /* "I want to read until I see N'th match" */
Denis Vlasenkob30418a2007-02-14 20:49:14 +0000916 read_lines();
917 }
918 if (num_matches) {
919 normalize_match_pos(match);
920 buffer_line(match_lines[match_pos]);
Denis Vlasenko8465a992007-05-09 18:32:54 +0000921 } else {
Denis Vlasenko8465a992007-05-09 18:32:54 +0000922 print_statusline("No matches found");
Denis Vlasenkob30418a2007-02-14 20:49:14 +0000923 }
Rob Landleya2e98042006-04-18 01:53:41 +0000924}
925
Denis Vlasenko5a4f0992006-12-25 01:23:02 +0000926static void fill_match_lines(unsigned pos)
927{
928 if (!pattern_valid)
929 return;
930 /* Run the regex on each line of the current file */
931 while (pos <= max_fline) {
932 /* If this line matches */
933 if (regexec(&pattern, flines[pos], 0, NULL, 0) == 0
934 /* and we didn't match it last time */
935 && !(num_matches && match_lines[num_matches-1] == pos)
936 ) {
Denis Vlasenkodeeed592008-07-08 05:14:36 +0000937 match_lines = xrealloc_vector(match_lines, 4, num_matches);
Denis Vlasenko5a4f0992006-12-25 01:23:02 +0000938 match_lines[num_matches++] = pos;
939 }
940 pos++;
941 }
942}
943
Mike Frysinger3a2b1032006-04-16 20:34:26 +0000944static void regex_process(void)
945{
Denis Vlasenkoe865e812006-12-21 13:24:58 +0000946 char *uncomp_regex, *err;
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000947
948 /* Reset variables */
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000949 free(match_lines);
950 match_lines = NULL;
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000951 match_pos = 0;
952 num_matches = 0;
953 if (pattern_valid) {
954 regfree(&pattern);
955 pattern_valid = 0;
956 }
Denis Vlasenko9a7cef92006-12-20 02:46:48 +0000957
Rob Landleya2e98042006-04-18 01:53:41 +0000958 /* Get the uncompiled regular expression from the user */
959 clear_line();
Denis Vlasenko4daad902007-09-27 10:20:47 +0000960 bb_putchar((option_mask32 & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/');
Denis Vlasenkof4dff772006-12-24 07:14:17 +0000961 uncomp_regex = less_gets(1);
962 if (!uncomp_regex[0]) {
Denis Vlasenko9a7cef92006-12-20 02:46:48 +0000963 free(uncomp_regex);
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000964 buffer_print();
Rob Landleya2e98042006-04-18 01:53:41 +0000965 return;
966 }
Denis Vlasenko9213a9e2006-09-17 16:28:10 +0000967
Rob Landleya2e98042006-04-18 01:53:41 +0000968 /* Compile the regex and check for errors */
Bernhard Reutner-Fischer48a67732008-09-26 14:10:17 +0000969 err = regcomp_or_errmsg(&pattern, uncomp_regex,
970 option_mask32 & FLAG_I ? REG_ICASE : 0);
Denis Vlasenko9a7cef92006-12-20 02:46:48 +0000971 free(uncomp_regex);
Denis Vlasenkoe865e812006-12-21 13:24:58 +0000972 if (err) {
Denis Vlasenkof1282a82006-12-21 17:03:20 +0000973 print_statusline(err);
Denis Vlasenkoe865e812006-12-21 13:24:58 +0000974 free(err);
975 return;
976 }
Denis Vlasenkoa1c63122007-03-08 18:12:01 +0000977
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000978 pattern_valid = 1;
Denis Vlasenko5a4f0992006-12-25 01:23:02 +0000979 match_pos = 0;
Denis Vlasenko5a4f0992006-12-25 01:23:02 +0000980 fill_match_lines(0);
Denis Vlasenko5a4f0992006-12-25 01:23:02 +0000981 while (match_pos < num_matches) {
Denis Vlasenko6b06cb82008-05-15 21:30:45 +0000982 if ((int)match_lines[match_pos] > cur_fline)
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000983 break;
Denis Vlasenko5a4f0992006-12-25 01:23:02 +0000984 match_pos++;
Denis Vlasenko3f3190e2006-12-21 00:22:03 +0000985 }
Denis Vlasenko5a4f0992006-12-25 01:23:02 +0000986 if (option_mask32 & LESS_STATE_MATCH_BACKWARDS)
987 match_pos--;
Denis Vlasenkoa1c63122007-03-08 18:12:01 +0000988
989 /* It's possible that no matches are found yet.
990 * goto_match() will read input looking for match,
991 * if needed */
992 goto_match(match_pos);
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000993}
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000994#endif
995
Mike Frysinger3a2b1032006-04-16 20:34:26 +0000996static void number_process(int first_digit)
997{
Denis Vlasenko6b06cb82008-05-15 21:30:45 +0000998 unsigned i;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +0000999 int num;
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001000 char num_input[sizeof(int)*4]; /* more than enough */
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001001 char keypress;
"Vladimir N. Oleynik"bc374802005-09-19 14:23:46 +00001002
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001003 num_input[0] = first_digit;
1004
1005 /* Clear the current line, print a prompt, and then print the digit */
1006 clear_line();
1007 printf(":%c", first_digit);
1008
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001009 /* Receive input until a letter is given */
Denis Vlasenkod2172c02008-02-23 11:54:37 +00001010 i = 1;
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001011 while (i < sizeof(num_input)-1) {
Denis Vlasenkod2172c02008-02-23 11:54:37 +00001012 num_input[i] = less_getch(i + 1);
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001013 if (!num_input[i] || !isdigit(num_input[i]))
1014 break;
Denis Vlasenko4daad902007-09-27 10:20:47 +00001015 bb_putchar(num_input[i]);
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001016 i++;
1017 }
1018
1019 /* Take the final letter out of the digits string */
1020 keypress = num_input[i];
1021 num_input[i] = '\0';
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001022 num = bb_strtou(num_input, NULL, 10);
1023 /* on format error, num == -1 */
1024 if (num < 1 || num > MAXLINES) {
Rob Landleya2e98042006-04-18 01:53:41 +00001025 buffer_print();
1026 return;
1027 }
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001028
1029 /* We now know the number and the letter entered, so we process them */
1030 switch (keypress) {
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001031 case KEY_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015':
1032 buffer_down(num);
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001033 break;
1034 case KEY_UP: case 'b': case 'w': case 'y': case 'u':
1035 buffer_up(num);
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001036 break;
1037 case 'g': case '<': case 'G': case '>':
1038 cur_fline = num + max_displayed_line;
Denis Vlasenko8e858e22007-03-07 09:35:43 +00001039 read_lines();
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001040 buffer_line(num - 1);
1041 break;
1042 case 'p': case '%':
1043 num = num * (max_fline / 100); /* + max_fline / 2; */
1044 cur_fline = num + max_displayed_line;
Denis Vlasenko8e858e22007-03-07 09:35:43 +00001045 read_lines();
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001046 buffer_line(num);
1047 break;
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001048#if ENABLE_FEATURE_LESS_REGEXP
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001049 case 'n':
1050 goto_match(match_pos + num);
1051 break;
1052 case '/':
1053 option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
1054 regex_process();
1055 break;
1056 case '?':
1057 option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
1058 regex_process();
1059 break;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001060#endif
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001061 }
1062}
1063
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001064#if ENABLE_FEATURE_LESS_FLAGCS
Mike Frysinger3a2b1032006-04-16 20:34:26 +00001065static void flag_change(void)
1066{
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001067 int keypress;
1068
1069 clear_line();
Denis Vlasenko4daad902007-09-27 10:20:47 +00001070 bb_putchar('-');
Denis Vlasenkod2172c02008-02-23 11:54:37 +00001071 keypress = less_getch(1);
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001072
1073 switch (keypress) {
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001074 case 'M':
1075 option_mask32 ^= FLAG_M;
1076 break;
1077 case 'm':
1078 option_mask32 ^= FLAG_m;
1079 break;
1080 case 'E':
1081 option_mask32 ^= FLAG_E;
1082 break;
1083 case '~':
1084 option_mask32 ^= FLAG_TILDE;
1085 break;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001086 }
1087}
1088
Mike Frysinger3a2b1032006-04-16 20:34:26 +00001089static void show_flag_status(void)
1090{
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001091 int keypress;
1092 int flag_val;
1093
1094 clear_line();
Denis Vlasenko4daad902007-09-27 10:20:47 +00001095 bb_putchar('_');
Denis Vlasenkod2172c02008-02-23 11:54:37 +00001096 keypress = less_getch(1);
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001097
1098 switch (keypress) {
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001099 case 'M':
1100 flag_val = option_mask32 & FLAG_M;
1101 break;
1102 case 'm':
1103 flag_val = option_mask32 & FLAG_m;
1104 break;
1105 case '~':
1106 flag_val = option_mask32 & FLAG_TILDE;
1107 break;
1108 case 'N':
1109 flag_val = option_mask32 & FLAG_N;
1110 break;
1111 case 'E':
1112 flag_val = option_mask32 & FLAG_E;
1113 break;
1114 default:
1115 flag_val = 0;
1116 break;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001117 }
1118
1119 clear_line();
Denis Vlasenko5a4f0992006-12-25 01:23:02 +00001120 printf(HIGHLIGHT"The status of the flag is: %u"NORMAL, flag_val != 0);
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001121}
1122#endif
1123
Mike Frysinger3a2b1032006-04-16 20:34:26 +00001124static void save_input_to_file(void)
1125{
Denis Vlasenko5a4f0992006-12-25 01:23:02 +00001126 const char *msg = "";
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001127 char *current_line;
Denis Vlasenko6b06cb82008-05-15 21:30:45 +00001128 unsigned i;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001129 FILE *fp;
1130
Denis Vlasenkof1282a82006-12-21 17:03:20 +00001131 print_statusline("Log file: ");
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001132 current_line = less_gets(sizeof("Log file: ")-1);
Denis Vlasenko33196372008-02-23 01:25:38 +00001133 if (current_line[0]) {
Denis Vlasenko5415c852008-07-21 23:05:26 +00001134 fp = fopen_for_write(current_line);
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001135 if (!fp) {
Denis Vlasenko5a4f0992006-12-25 01:23:02 +00001136 msg = "Error opening log file";
1137 goto ret;
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001138 }
Denis Vlasenko5a4f0992006-12-25 01:23:02 +00001139 for (i = 0; i <= max_fline; i++)
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001140 fprintf(fp, "%s\n", flines[i]);
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001141 fclose(fp);
Denis Vlasenko5a4f0992006-12-25 01:23:02 +00001142 msg = "Done";
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001143 }
Denis Vlasenko5a4f0992006-12-25 01:23:02 +00001144 ret:
1145 print_statusline(msg);
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001146 free(current_line);
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001147}
1148
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001149#if ENABLE_FEATURE_LESS_MARKS
Mike Frysinger3a2b1032006-04-16 20:34:26 +00001150static void add_mark(void)
1151{
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001152 int letter;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001153
Denis Vlasenkof1282a82006-12-21 17:03:20 +00001154 print_statusline("Mark: ");
Denis Vlasenkod2172c02008-02-23 11:54:37 +00001155 letter = less_getch(sizeof("Mark: ") - 1);
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001156
1157 if (isalpha(letter)) {
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001158 /* If we exceed 15 marks, start overwriting previous ones */
1159 if (num_marks == 14)
1160 num_marks = 0;
1161
1162 mark_lines[num_marks][0] = letter;
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001163 mark_lines[num_marks][1] = cur_fline;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001164 num_marks++;
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001165 } else {
Denis Vlasenkof1282a82006-12-21 17:03:20 +00001166 print_statusline("Invalid mark letter");
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001167 }
1168}
1169
Mike Frysinger3a2b1032006-04-16 20:34:26 +00001170static void goto_mark(void)
1171{
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001172 int letter;
1173 int i;
1174
Denis Vlasenkof1282a82006-12-21 17:03:20 +00001175 print_statusline("Go to mark: ");
Denis Vlasenkod2172c02008-02-23 11:54:37 +00001176 letter = less_getch(sizeof("Go to mark: ") - 1);
Rob Landleyd57ae8b2005-09-18 00:58:49 +00001177 clear_line();
"Vladimir N. Oleynik"a0ae6de2005-09-19 10:28:43 +00001178
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001179 if (isalpha(letter)) {
1180 for (i = 0; i <= num_marks; i++)
1181 if (letter == mark_lines[i][0]) {
1182 buffer_line(mark_lines[i][1]);
1183 break;
1184 }
Denis Vlasenkoe865e812006-12-21 13:24:58 +00001185 if (num_marks == 14 && letter != mark_lines[14][0])
Denis Vlasenkof1282a82006-12-21 17:03:20 +00001186 print_statusline("Mark not set");
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001187 } else
Denis Vlasenkof1282a82006-12-21 17:03:20 +00001188 print_statusline("Invalid mark letter");
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001189}
1190#endif
1191
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001192#if ENABLE_FEATURE_LESS_BRACKETS
Mike Frysinger3a2b1032006-04-16 20:34:26 +00001193static char opp_bracket(char bracket)
1194{
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001195 switch (bracket) {
Denis Vlasenkob78d1c02008-02-24 19:18:18 +00001196 case '{': case '[': /* '}' == '{' + 2. Same for '[' */
1197 bracket++;
1198 case '(': /* ')' == '(' + 1 */
1199 bracket++;
1200 break;
1201 case '}': case ']':
1202 bracket--;
1203 case ')':
1204 bracket--;
1205 break;
1206 };
1207 return bracket;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001208}
1209
Mike Frysinger3a2b1032006-04-16 20:34:26 +00001210static void match_right_bracket(char bracket)
1211{
Denis Vlasenko6b06cb82008-05-15 21:30:45 +00001212 unsigned i;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001213
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001214 if (strchr(flines[cur_fline], bracket) == NULL) {
Denis Vlasenkof1282a82006-12-21 17:03:20 +00001215 print_statusline("No bracket in top line");
Denis Vlasenkoe865e812006-12-21 13:24:58 +00001216 return;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001217 }
Denis Vlasenkob78d1c02008-02-24 19:18:18 +00001218 bracket = opp_bracket(bracket);
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001219 for (i = cur_fline + 1; i < max_fline; i++) {
Denis Vlasenkob78d1c02008-02-24 19:18:18 +00001220 if (strchr(flines[i], bracket) != NULL) {
1221 buffer_line(i);
1222 return;
Denis Vlasenkoe865e812006-12-21 13:24:58 +00001223 }
1224 }
Denis Vlasenkob78d1c02008-02-24 19:18:18 +00001225 print_statusline("No matching bracket found");
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001226}
1227
Mike Frysinger3a2b1032006-04-16 20:34:26 +00001228static void match_left_bracket(char bracket)
1229{
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001230 int i;
1231
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001232 if (strchr(flines[cur_fline + max_displayed_line], bracket) == NULL) {
Denis Vlasenkof1282a82006-12-21 17:03:20 +00001233 print_statusline("No bracket in bottom line");
Denis Vlasenkoe865e812006-12-21 13:24:58 +00001234 return;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001235 }
Denis Vlasenkoe865e812006-12-21 13:24:58 +00001236
Denis Vlasenkob78d1c02008-02-24 19:18:18 +00001237 bracket = opp_bracket(bracket);
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001238 for (i = cur_fline + max_displayed_line; i >= 0; i--) {
Denis Vlasenkob78d1c02008-02-24 19:18:18 +00001239 if (strchr(flines[i], bracket) != NULL) {
1240 buffer_line(i);
1241 return;
Denis Vlasenkoe865e812006-12-21 13:24:58 +00001242 }
1243 }
Denis Vlasenkob78d1c02008-02-24 19:18:18 +00001244 print_statusline("No matching bracket found");
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001245}
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001246#endif /* FEATURE_LESS_BRACKETS */
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001247
Mike Frysinger3a2b1032006-04-16 20:34:26 +00001248static void keypress_process(int keypress)
1249{
Rob Landley9200e792005-09-15 19:26:59 +00001250 switch (keypress) {
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001251 case KEY_DOWN: case 'e': case 'j': case 0x0d:
1252 buffer_down(1);
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001253 break;
1254 case KEY_UP: case 'y': case 'k':
1255 buffer_up(1);
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001256 break;
Denis Vlasenko78e2d4e2007-09-27 17:11:48 +00001257 case PAGE_DOWN: case ' ': case 'z': case 'f':
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001258 buffer_down(max_displayed_line + 1);
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001259 break;
1260 case PAGE_UP: case 'w': case 'b':
1261 buffer_up(max_displayed_line + 1);
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001262 break;
1263 case 'd':
1264 buffer_down((max_displayed_line + 1) / 2);
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001265 break;
1266 case 'u':
1267 buffer_up((max_displayed_line + 1) / 2);
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001268 break;
1269 case KEY_HOME: case 'g': case 'p': case '<': case '%':
1270 buffer_line(0);
1271 break;
1272 case KEY_END: case 'G': case '>':
1273 cur_fline = MAXLINES;
1274 read_lines();
1275 buffer_line(cur_fline);
1276 break;
1277 case 'q': case 'Q':
Bernhard Reutner-Fischer636a1f82008-05-19 09:29:47 +00001278 less_exit(EXIT_SUCCESS);
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001279 break;
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001280#if ENABLE_FEATURE_LESS_MARKS
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001281 case 'm':
1282 add_mark();
1283 buffer_print();
1284 break;
1285 case '\'':
1286 goto_mark();
1287 buffer_print();
1288 break;
Rob Landley9200e792005-09-15 19:26:59 +00001289#endif
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001290 case 'r': case 'R':
1291 buffer_print();
1292 break;
1293 /*case 'R':
1294 full_repaint();
1295 break;*/
1296 case 's':
1297 save_input_to_file();
1298 break;
1299 case 'E':
1300 examine_file();
1301 break;
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001302#if ENABLE_FEATURE_LESS_FLAGS
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001303 case '=':
1304 m_status_print();
1305 break;
Rob Landley9200e792005-09-15 19:26:59 +00001306#endif
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001307#if ENABLE_FEATURE_LESS_REGEXP
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001308 case '/':
1309 option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
1310 regex_process();
1311 break;
1312 case 'n':
1313 goto_match(match_pos + 1);
1314 break;
1315 case 'N':
1316 goto_match(match_pos - 1);
1317 break;
1318 case '?':
1319 option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
1320 regex_process();
1321 break;
Rob Landley9200e792005-09-15 19:26:59 +00001322#endif
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001323#if ENABLE_FEATURE_LESS_FLAGCS
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001324 case '-':
1325 flag_change();
1326 buffer_print();
1327 break;
1328 case '_':
1329 show_flag_status();
1330 break;
Rob Landley9200e792005-09-15 19:26:59 +00001331#endif
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001332#if ENABLE_FEATURE_LESS_BRACKETS
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001333 case '{': case '(': case '[':
1334 match_right_bracket(keypress);
1335 break;
1336 case '}': case ')': case ']':
1337 match_left_bracket(keypress);
1338 break;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001339#endif
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001340 case ':':
1341 colon_process();
1342 break;
Rob Landley9200e792005-09-15 19:26:59 +00001343 }
Rob Landleyd57ae8b2005-09-18 00:58:49 +00001344
Rob Landley9200e792005-09-15 19:26:59 +00001345 if (isdigit(keypress))
1346 number_process(keypress);
1347}
1348
Denis Vlasenko400d8bb2008-02-24 13:36:01 +00001349static void sig_catcher(int sig)
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001350{
Denis Vlasenko400d8bb2008-02-24 13:36:01 +00001351 less_exit(- sig);
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001352}
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001353
Denis Vlasenko9b49a5e2007-10-11 10:05:36 +00001354int less_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001355int less_main(int argc, char **argv)
1356{
Rob Landley9200e792005-09-15 19:26:59 +00001357 int keypress;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001358
Denis Vlasenkoa1d24a02007-05-31 15:55:03 +00001359 INIT_G();
1360
Denis Vlasenko95b30712006-12-31 19:23:31 +00001361 /* TODO: -x: do not interpret backspace, -xx: tab also */
1362 /* -xxx: newline also */
1363 /* -w N: assume width N (-xxx -w 32: hex viewer of sorts) */
Bernhard Reutner-Fischer48a67732008-09-26 14:10:17 +00001364 getopt32(argv, "EMmN~I");
Rob Landley9200e792005-09-15 19:26:59 +00001365 argc -= optind;
1366 argv += optind;
Rob Landley9200e792005-09-15 19:26:59 +00001367 num_files = argc;
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001368 files = argv;
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001369
Denis Vlasenkoe865e812006-12-21 13:24:58 +00001370 /* Another popular pager, most, detects when stdout
1371 * is not a tty and turns into cat. This makes sense. */
1372 if (!isatty(STDOUT_FILENO))
1373 return bb_cat(argv);
Denis Vlasenko4eb8b932007-03-10 16:32:14 +00001374 kbd_fd = open(CURRENT_TTY, O_RDONLY);
1375 if (kbd_fd < 0)
1376 return bb_cat(argv);
Denis Vlasenko33196372008-02-23 01:25:38 +00001377 ndelay_on(kbd_fd);
Denis Vlasenkoe865e812006-12-21 13:24:58 +00001378
Rob Landley9200e792005-09-15 19:26:59 +00001379 if (!num_files) {
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001380 if (isatty(STDIN_FILENO)) {
Denis Vlasenkoe865e812006-12-21 13:24:58 +00001381 /* Just "less"? No args and no redirection? */
Denis Vlasenkoe1a0d482006-10-20 13:28:22 +00001382 bb_error_msg("missing filename");
Rob Landley9200e792005-09-15 19:26:59 +00001383 bb_show_usage();
1384 }
Denis Vlasenko9a7cef92006-12-20 02:46:48 +00001385 } else
1386 filename = xstrdup(files[0]);
"Vladimir N. Oleynik"2b306e92005-09-16 12:32:22 +00001387
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001388 get_terminal_width_height(kbd_fd, &width, &max_displayed_line);
1389 /* 20: two tabstops + 4 */
1390 if (width < 20 || max_displayed_line < 3)
Denis Vlasenko107fe7c2008-03-17 08:38:45 +00001391 return bb_cat(argv);
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001392 max_displayed_line -= 2;
Denis Vlasenko3f3190e2006-12-21 00:22:03 +00001393
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001394 buffer = xmalloc((max_displayed_line+1) * sizeof(char *));
Denis Vlasenkoe865e812006-12-21 13:24:58 +00001395 if (option_mask32 & FLAG_TILDE)
1396 empty_line_marker = "";
Denis Vlasenko3f3190e2006-12-21 00:22:03 +00001397
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001398 tcgetattr(kbd_fd, &term_orig);
Denis Vlasenkoa1d24a02007-05-31 15:55:03 +00001399 term_less = term_orig;
1400 term_less.c_lflag &= ~(ICANON | ECHO);
1401 term_less.c_iflag &= ~(IXON | ICRNL);
1402 /*term_less.c_oflag &= ~ONLCR;*/
1403 term_less.c_cc[VMIN] = 1;
1404 term_less.c_cc[VTIME] = 0;
Denis Vlasenkoe865e812006-12-21 13:24:58 +00001405
Denis Vlasenkod553faf2008-02-23 12:22:17 +00001406 /* We want to restore term_orig on exit */
Denis Vlasenkocf7cf622008-03-19 19:38:46 +00001407 bb_signals(BB_FATAL_SIGS, sig_catcher);
Denis Vlasenkod553faf2008-02-23 12:22:17 +00001408
Denis Vlasenkof4dff772006-12-24 07:14:17 +00001409 reinitialize();
Rob Landley9200e792005-09-15 19:26:59 +00001410 while (1) {
Denis Vlasenkod2172c02008-02-23 11:54:37 +00001411 keypress = less_getch(-1); /* -1: do not position cursor */
Rob Landley9200e792005-09-15 19:26:59 +00001412 keypress_process(keypress);
1413 }
1414}