21 |
* redirected input has been read from stdin |
* redirected input has been read from stdin |
22 |
*/ |
*/ |
23 |
|
|
24 |
#include "busybox.h" |
#include <sched.h> /* sched_yield() */ |
25 |
|
|
26 |
|
#include "libbb.h" |
27 |
#if ENABLE_FEATURE_LESS_REGEXP |
#if ENABLE_FEATURE_LESS_REGEXP |
28 |
#include "xregex.h" |
#include "xregex.h" |
29 |
#endif |
#endif |
30 |
|
|
|
/* FIXME: currently doesn't work right */ |
|
|
#undef ENABLE_FEATURE_LESS_FLAGCS |
|
|
#define ENABLE_FEATURE_LESS_FLAGCS 0 |
|
|
|
|
31 |
/* The escape codes for highlighted and normal text */ |
/* The escape codes for highlighted and normal text */ |
32 |
#define HIGHLIGHT "\033[7m" |
#define HIGHLIGHT "\033[7m" |
33 |
#define NORMAL "\033[0m" |
#define NORMAL "\033[0m" |
36 |
/* The escape code to clear to end of line */ |
/* The escape code to clear to end of line */ |
37 |
#define CLEAR_2_EOL "\033[K" |
#define CLEAR_2_EOL "\033[K" |
38 |
|
|
|
/* These are the escape sequences corresponding to special keys */ |
|
39 |
enum { |
enum { |
|
REAL_KEY_UP = 'A', |
|
|
REAL_KEY_DOWN = 'B', |
|
|
REAL_KEY_RIGHT = 'C', |
|
|
REAL_KEY_LEFT = 'D', |
|
|
REAL_PAGE_UP = '5', |
|
|
REAL_PAGE_DOWN = '6', |
|
|
REAL_KEY_HOME = '7', |
|
|
REAL_KEY_END = '8', |
|
|
|
|
|
/* These are the special codes assigned by this program to the special keys */ |
|
|
KEY_UP = 20, |
|
|
KEY_DOWN = 21, |
|
|
KEY_RIGHT = 22, |
|
|
KEY_LEFT = 23, |
|
|
PAGE_UP = 24, |
|
|
PAGE_DOWN = 25, |
|
|
KEY_HOME = 26, |
|
|
KEY_END = 27, |
|
|
|
|
40 |
/* Absolute max of lines eaten */ |
/* Absolute max of lines eaten */ |
41 |
MAXLINES = CONFIG_FEATURE_LESS_MAXLINES, |
MAXLINES = CONFIG_FEATURE_LESS_MAXLINES, |
|
|
|
42 |
/* This many "after the end" lines we will show (at max) */ |
/* This many "after the end" lines we will show (at max) */ |
43 |
TILDES = 1, |
TILDES = 1, |
44 |
}; |
}; |
45 |
|
|
|
static unsigned max_displayed_line; |
|
|
static unsigned width; |
|
|
static const char *empty_line_marker = "~"; |
|
|
|
|
|
static char *filename; |
|
|
static char **files; |
|
|
static unsigned num_files = 1; |
|
|
static unsigned current_file = 1; |
|
|
static const char **buffer; |
|
|
static const char **flines; |
|
|
static int cur_fline; /* signed */ |
|
|
static unsigned max_fline; |
|
|
static unsigned max_lineno; /* this one tracks linewrap */ |
|
|
|
|
|
static ssize_t eof_error = 1; /* eof if 0, error if < 0 */ |
|
|
static char terminated = 1; |
|
|
static size_t readpos; |
|
|
static size_t readeof; |
|
|
/* last position in last line, taking into account tabs */ |
|
|
static size_t linepos; |
|
|
|
|
46 |
/* Command line options */ |
/* Command line options */ |
47 |
enum { |
enum { |
48 |
FLAG_E = 1, |
FLAG_E = 1 << 0, |
49 |
FLAG_M = 1 << 1, |
FLAG_M = 1 << 1, |
50 |
FLAG_m = 1 << 2, |
FLAG_m = 1 << 2, |
51 |
FLAG_N = 1 << 3, |
FLAG_N = 1 << 3, |
52 |
FLAG_TILDE = 1 << 4, |
FLAG_TILDE = 1 << 4, |
53 |
|
FLAG_I = 1 << 5, |
54 |
|
FLAG_S = (1 << 6) * ENABLE_FEATURE_LESS_DASHCMD, |
55 |
/* hijack command line options variable for internal state vars */ |
/* hijack command line options variable for internal state vars */ |
56 |
LESS_STATE_MATCH_BACKWARDS = 1 << 15, |
LESS_STATE_MATCH_BACKWARDS = 1 << 15, |
57 |
}; |
}; |
58 |
|
|
59 |
#if ENABLE_FEATURE_LESS_MARKS |
#if !ENABLE_FEATURE_LESS_REGEXP |
60 |
static unsigned mark_lines[15][2]; |
enum { pattern_valid = 0 }; |
|
static unsigned num_marks; |
|
61 |
#endif |
#endif |
62 |
|
|
63 |
|
struct globals { |
64 |
|
int cur_fline; /* signed */ |
65 |
|
int kbd_fd; /* fd to get input from */ |
66 |
|
int less_gets_pos; |
67 |
|
/* last position in last line, taking into account tabs */ |
68 |
|
size_t last_line_pos; |
69 |
|
unsigned max_fline; |
70 |
|
unsigned max_lineno; /* this one tracks linewrap */ |
71 |
|
unsigned max_displayed_line; |
72 |
|
unsigned width; |
73 |
|
#if ENABLE_FEATURE_LESS_WINCH |
74 |
|
unsigned winch_counter; |
75 |
|
#endif |
76 |
|
ssize_t eof_error; /* eof if 0, error if < 0 */ |
77 |
|
ssize_t readpos; |
78 |
|
ssize_t readeof; /* must be signed */ |
79 |
|
const char **buffer; |
80 |
|
const char **flines; |
81 |
|
const char *empty_line_marker; |
82 |
|
unsigned num_files; |
83 |
|
unsigned current_file; |
84 |
|
char *filename; |
85 |
|
char **files; |
86 |
|
#if ENABLE_FEATURE_LESS_MARKS |
87 |
|
unsigned num_marks; |
88 |
|
unsigned mark_lines[15][2]; |
89 |
|
#endif |
90 |
#if ENABLE_FEATURE_LESS_REGEXP |
#if ENABLE_FEATURE_LESS_REGEXP |
91 |
static unsigned *match_lines; |
unsigned *match_lines; |
92 |
static int match_pos; /* signed! */ |
int match_pos; /* signed! */ |
93 |
static unsigned num_matches; |
int wanted_match; /* signed! */ |
94 |
static regex_t pattern; |
int num_matches; |
95 |
static unsigned pattern_valid; |
regex_t pattern; |
96 |
#else |
smallint pattern_valid; |
|
enum { pattern_valid = 0 }; |
|
97 |
#endif |
#endif |
98 |
|
smallint terminated; |
99 |
|
smalluint kbd_input_size; |
100 |
|
struct termios term_orig, term_less; |
101 |
|
char kbd_input[KEYCODE_BUFFER_SIZE]; |
102 |
|
}; |
103 |
|
#define G (*ptr_to_globals) |
104 |
|
#define cur_fline (G.cur_fline ) |
105 |
|
#define kbd_fd (G.kbd_fd ) |
106 |
|
#define less_gets_pos (G.less_gets_pos ) |
107 |
|
#define last_line_pos (G.last_line_pos ) |
108 |
|
#define max_fline (G.max_fline ) |
109 |
|
#define max_lineno (G.max_lineno ) |
110 |
|
#define max_displayed_line (G.max_displayed_line) |
111 |
|
#define width (G.width ) |
112 |
|
#define winch_counter (G.winch_counter ) |
113 |
|
/* This one is 100% not cached by compiler on read access */ |
114 |
|
#define WINCH_COUNTER (*(volatile unsigned *)&winch_counter) |
115 |
|
#define eof_error (G.eof_error ) |
116 |
|
#define readpos (G.readpos ) |
117 |
|
#define readeof (G.readeof ) |
118 |
|
#define buffer (G.buffer ) |
119 |
|
#define flines (G.flines ) |
120 |
|
#define empty_line_marker (G.empty_line_marker ) |
121 |
|
#define num_files (G.num_files ) |
122 |
|
#define current_file (G.current_file ) |
123 |
|
#define filename (G.filename ) |
124 |
|
#define files (G.files ) |
125 |
|
#define num_marks (G.num_marks ) |
126 |
|
#define mark_lines (G.mark_lines ) |
127 |
|
#if ENABLE_FEATURE_LESS_REGEXP |
128 |
|
#define match_lines (G.match_lines ) |
129 |
|
#define match_pos (G.match_pos ) |
130 |
|
#define num_matches (G.num_matches ) |
131 |
|
#define wanted_match (G.wanted_match ) |
132 |
|
#define pattern (G.pattern ) |
133 |
|
#define pattern_valid (G.pattern_valid ) |
134 |
|
#endif |
135 |
|
#define terminated (G.terminated ) |
136 |
|
#define term_orig (G.term_orig ) |
137 |
|
#define term_less (G.term_less ) |
138 |
|
#define kbd_input_size (G.kbd_input_size ) |
139 |
|
#define kbd_input (G.kbd_input ) |
140 |
|
#define INIT_G() do { \ |
141 |
|
SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ |
142 |
|
less_gets_pos = -1; \ |
143 |
|
empty_line_marker = "~"; \ |
144 |
|
num_files = 1; \ |
145 |
|
current_file = 1; \ |
146 |
|
eof_error = 1; \ |
147 |
|
terminated = 1; \ |
148 |
|
USE_FEATURE_LESS_REGEXP(wanted_match = -1;) \ |
149 |
|
} while (0) |
150 |
|
|
151 |
|
/* flines[] are lines read from stdin, each in malloc'ed buffer. |
152 |
|
* Line numbers are stored as uint32_t prepended to each line. |
153 |
|
* Pointer is adjusted so that flines[i] points directly past |
154 |
|
* line number. Accesor: */ |
155 |
|
#define MEMPTR(p) ((char*)(p) - 4) |
156 |
|
#define LINENO(p) (*(uint32_t*)((p) - 4)) |
157 |
|
|
|
static struct termios term_orig, term_vi; |
|
|
|
|
|
/* File pointer to get input from */ |
|
|
static int kbd_fd; |
|
158 |
|
|
159 |
/* Reset terminal input to normal */ |
/* Reset terminal input to normal */ |
160 |
static void set_tty_cooked(void) |
static void set_tty_cooked(void) |
163 |
tcsetattr(kbd_fd, TCSANOW, &term_orig); |
tcsetattr(kbd_fd, TCSANOW, &term_orig); |
164 |
} |
} |
165 |
|
|
|
/* Exit the program gracefully */ |
|
|
static void less_exit(int code) |
|
|
{ |
|
|
/* TODO: We really should save the terminal state when we start, |
|
|
* and restore it when we exit. Less does this with the |
|
|
* "ti" and "te" termcap commands; can this be done with |
|
|
* only termios.h? */ |
|
|
putchar('\n'); |
|
|
fflush_stdout_and_exit(code); |
|
|
} |
|
|
|
|
166 |
/* Move the cursor to a position (x,y), where (0,0) is the |
/* Move the cursor to a position (x,y), where (0,0) is the |
167 |
top-left corner of the console */ |
top-left corner of the console */ |
168 |
static void move_cursor(int line, int row) |
static void move_cursor(int line, int row) |
186 |
printf(HIGHLIGHT"%.*s"NORMAL, width - 1, str); |
printf(HIGHLIGHT"%.*s"NORMAL, width - 1, str); |
187 |
} |
} |
188 |
|
|
189 |
|
/* Exit the program gracefully */ |
190 |
|
static void less_exit(int code) |
191 |
|
{ |
192 |
|
set_tty_cooked(); |
193 |
|
clear_line(); |
194 |
|
if (code < 0) |
195 |
|
kill_myself_with_sig(- code); /* does not return */ |
196 |
|
exit(code); |
197 |
|
} |
198 |
|
|
199 |
|
#if (ENABLE_FEATURE_LESS_DASHCMD && ENABLE_FEATURE_LESS_LINENUMS) \ |
200 |
|
|| ENABLE_FEATURE_LESS_WINCH |
201 |
|
static void re_wrap(void) |
202 |
|
{ |
203 |
|
int w = width; |
204 |
|
int new_line_pos; |
205 |
|
int src_idx; |
206 |
|
int dst_idx; |
207 |
|
int new_cur_fline = 0; |
208 |
|
uint32_t lineno; |
209 |
|
char linebuf[w + 1]; |
210 |
|
const char **old_flines = flines; |
211 |
|
const char *s; |
212 |
|
char **new_flines = NULL; |
213 |
|
char *d; |
214 |
|
|
215 |
|
if (option_mask32 & FLAG_N) |
216 |
|
w -= 8; |
217 |
|
|
218 |
|
src_idx = 0; |
219 |
|
dst_idx = 0; |
220 |
|
s = old_flines[0]; |
221 |
|
lineno = LINENO(s); |
222 |
|
d = linebuf; |
223 |
|
new_line_pos = 0; |
224 |
|
while (1) { |
225 |
|
*d = *s; |
226 |
|
if (*d != '\0') { |
227 |
|
new_line_pos++; |
228 |
|
if (*d == '\t') /* tab */ |
229 |
|
new_line_pos += 7; |
230 |
|
s++; |
231 |
|
d++; |
232 |
|
if (new_line_pos >= w) { |
233 |
|
int sz; |
234 |
|
/* new line is full, create next one */ |
235 |
|
*d = '\0'; |
236 |
|
next_new: |
237 |
|
sz = (d - linebuf) + 1; /* + 1: NUL */ |
238 |
|
d = ((char*)xmalloc(sz + 4)) + 4; |
239 |
|
LINENO(d) = lineno; |
240 |
|
memcpy(d, linebuf, sz); |
241 |
|
new_flines = xrealloc_vector(new_flines, 8, dst_idx); |
242 |
|
new_flines[dst_idx] = d; |
243 |
|
dst_idx++; |
244 |
|
if (new_line_pos < w) { |
245 |
|
/* if we came here thru "goto next_new" */ |
246 |
|
if (src_idx > max_fline) |
247 |
|
break; |
248 |
|
lineno = LINENO(s); |
249 |
|
} |
250 |
|
d = linebuf; |
251 |
|
new_line_pos = 0; |
252 |
|
} |
253 |
|
continue; |
254 |
|
} |
255 |
|
/* *d == NUL: old line ended, go to next old one */ |
256 |
|
free(MEMPTR(old_flines[src_idx])); |
257 |
|
/* btw, convert cur_fline... */ |
258 |
|
if (cur_fline == src_idx) |
259 |
|
new_cur_fline = dst_idx; |
260 |
|
src_idx++; |
261 |
|
/* no more lines? finish last new line (and exit the loop) */ |
262 |
|
if (src_idx > max_fline) |
263 |
|
goto next_new; |
264 |
|
s = old_flines[src_idx]; |
265 |
|
if (lineno != LINENO(s)) { |
266 |
|
/* this is not a continuation line! |
267 |
|
* create next _new_ line too */ |
268 |
|
goto next_new; |
269 |
|
} |
270 |
|
} |
271 |
|
|
272 |
|
free(old_flines); |
273 |
|
flines = (const char **)new_flines; |
274 |
|
|
275 |
|
max_fline = dst_idx - 1; |
276 |
|
last_line_pos = new_line_pos; |
277 |
|
cur_fline = new_cur_fline; |
278 |
|
/* max_lineno is screen-size independent */ |
279 |
|
#if ENABLE_FEATURE_LESS_REGEXP |
280 |
|
pattern_valid = 0; |
281 |
|
#endif |
282 |
|
} |
283 |
|
#endif |
284 |
|
|
285 |
#if ENABLE_FEATURE_LESS_REGEXP |
#if ENABLE_FEATURE_LESS_REGEXP |
286 |
static void fill_match_lines(unsigned pos); |
static void fill_match_lines(unsigned pos); |
287 |
#else |
#else |
288 |
#define fill_match_lines(pos) ((void)0) |
#define fill_match_lines(pos) ((void)0) |
289 |
#endif |
#endif |
290 |
|
|
291 |
|
/* Devilishly complex routine. |
292 |
|
* |
293 |
|
* Has to deal with EOF and EPIPE on input, |
294 |
|
* with line wrapping, with last line not ending in '\n' |
295 |
|
* (possibly not ending YET!), with backspace and tabs. |
296 |
|
* It reads input again if last time we got an EOF (thus supporting |
297 |
|
* growing files) or EPIPE (watching output of slow process like make). |
298 |
|
* |
299 |
|
* Variables used: |
300 |
|
* flines[] - array of lines already read. Linewrap may cause |
301 |
|
* one source file line to occupy several flines[n]. |
302 |
|
* flines[max_fline] - last line, possibly incomplete. |
303 |
|
* terminated - 1 if flines[max_fline] is 'terminated' |
304 |
|
* (if there was '\n' [which isn't stored itself, we just remember |
305 |
|
* that it was seen]) |
306 |
|
* max_lineno - last line's number, this one doesn't increment |
307 |
|
* on line wrap, only on "real" new lines. |
308 |
|
* readbuf[0..readeof-1] - small preliminary buffer. |
309 |
|
* readbuf[readpos] - next character to add to current line. |
310 |
|
* last_line_pos - screen line position of next char to be read |
311 |
|
* (takes into account tabs and backspaces) |
312 |
|
* eof_error - < 0 error, == 0 EOF, > 0 not EOF/error |
313 |
|
*/ |
314 |
static void read_lines(void) |
static void read_lines(void) |
315 |
{ |
{ |
316 |
#define readbuf bb_common_bufsiz1 |
#define readbuf bb_common_bufsiz1 |
317 |
char *current_line, *p; |
char *current_line, *p; |
|
USE_FEATURE_LESS_REGEXP(unsigned old_max_fline = max_fline;) |
|
318 |
int w = width; |
int w = width; |
319 |
char last_terminated = terminated; |
char last_terminated = terminated; |
320 |
|
#if ENABLE_FEATURE_LESS_REGEXP |
321 |
|
unsigned old_max_fline = max_fline; |
322 |
|
time_t last_time = 0; |
323 |
|
unsigned seconds_p1 = 3; /* seconds_to_loop + 1 */ |
324 |
|
#endif |
325 |
|
|
326 |
if (option_mask32 & FLAG_N) |
if (option_mask32 & FLAG_N) |
327 |
w -= 8; |
w -= 8; |
328 |
|
|
329 |
current_line = xmalloc(w); |
USE_FEATURE_LESS_REGEXP(again0:) |
330 |
p = current_line; |
|
331 |
|
p = current_line = ((char*)xmalloc(w + 4)) + 4; |
332 |
max_fline += last_terminated; |
max_fline += last_terminated; |
333 |
if (!last_terminated) { |
if (!last_terminated) { |
334 |
const char *cp = flines[max_fline]; |
const char *cp = flines[max_fline]; |
335 |
if (option_mask32 & FLAG_N) |
strcpy(p, cp); |
|
cp += 8; |
|
|
strcpy(current_line, cp); |
|
336 |
p += strlen(current_line); |
p += strlen(current_line); |
337 |
|
free(MEMPTR(flines[max_fline])); |
338 |
|
/* last_line_pos is still valid from previous read_lines() */ |
339 |
} else { |
} else { |
340 |
linepos = 0; |
last_line_pos = 0; |
341 |
} |
} |
342 |
|
|
343 |
while (1) { |
while (1) { /* read lines until we reach cur_fline or wanted_match */ |
|
again: |
|
344 |
*p = '\0'; |
*p = '\0'; |
345 |
terminated = 0; |
terminated = 0; |
346 |
while (1) { |
while (1) { /* read chars until we have a line */ |
347 |
char c; |
char c; |
348 |
|
/* if no unprocessed chars left, eat more */ |
349 |
if (readpos >= readeof) { |
if (readpos >= readeof) { |
350 |
ndelay_on(0); |
ndelay_on(0); |
351 |
eof_error = safe_read(0, readbuf, sizeof(readbuf)); |
eof_error = safe_read(STDIN_FILENO, readbuf, sizeof(readbuf)); |
352 |
ndelay_off(0); |
ndelay_off(0); |
353 |
readpos = 0; |
readpos = 0; |
354 |
readeof = eof_error; |
readeof = eof_error; |
355 |
if (eof_error < 0) { |
if (eof_error <= 0) |
|
readeof = 0; |
|
|
if (errno != EAGAIN) |
|
|
print_statusline("read error"); |
|
|
} |
|
|
if (eof_error <= 0) { |
|
356 |
goto reached_eof; |
goto reached_eof; |
|
} |
|
357 |
} |
} |
358 |
c = readbuf[readpos]; |
c = readbuf[readpos]; |
359 |
/* backspace? [needed for manpages] */ |
/* backspace? [needed for manpages] */ |
360 |
/* <tab><bs> is (a) insane and */ |
/* <tab><bs> is (a) insane and */ |
361 |
/* (b) harder to do correctly, so we refuse to do it */ |
/* (b) harder to do correctly, so we refuse to do it */ |
362 |
if (c == '\x8' && linepos && p[-1] != '\t') { |
if (c == '\x8' && last_line_pos && p[-1] != '\t') { |
363 |
readpos++; /* eat it */ |
readpos++; /* eat it */ |
364 |
linepos--; |
last_line_pos--; |
365 |
|
/* was buggy (p could end up <= current_line)... */ |
366 |
*--p = '\0'; |
*--p = '\0'; |
367 |
continue; |
continue; |
368 |
} |
} |
369 |
if (c == '\t') |
{ |
370 |
linepos += (linepos^7) & 7; |
size_t new_last_line_pos = last_line_pos + 1; |
371 |
linepos++; |
if (c == '\t') { |
372 |
if (linepos >= w) |
new_last_line_pos += 7; |
373 |
break; |
new_last_line_pos &= (~7); |
374 |
|
} |
375 |
|
if ((int)new_last_line_pos >= w) |
376 |
|
break; |
377 |
|
last_line_pos = new_last_line_pos; |
378 |
|
} |
379 |
/* ok, we will eat this char */ |
/* ok, we will eat this char */ |
380 |
readpos++; |
readpos++; |
381 |
if (c == '\n') { terminated = 1; break; } |
if (c == '\n') { |
382 |
|
terminated = 1; |
383 |
|
last_line_pos = 0; |
384 |
|
break; |
385 |
|
} |
386 |
/* NUL is substituted by '\n'! */ |
/* NUL is substituted by '\n'! */ |
387 |
if (c == '\0') c = '\n'; |
if (c == '\0') c = '\n'; |
388 |
*p++ = c; |
*p++ = c; |
389 |
*p = '\0'; |
*p = '\0'; |
390 |
} |
} /* end of "read chars until we have a line" loop */ |
391 |
/* Corner case: linewrap with only "" wrapping to next line */ |
/* Corner case: linewrap with only "" wrapping to next line */ |
392 |
/* Looks ugly on screen, so we do not store this empty line */ |
/* Looks ugly on screen, so we do not store this empty line */ |
393 |
if (!last_terminated && !current_line[0]) { |
if (!last_terminated && !current_line[0]) { |
394 |
last_terminated = 1; |
last_terminated = 1; |
395 |
max_lineno++; |
max_lineno++; |
396 |
goto again; |
continue; |
397 |
} |
} |
398 |
reached_eof: |
reached_eof: |
399 |
last_terminated = terminated; |
last_terminated = terminated; |
400 |
flines = xrealloc(flines, (max_fline+1) * sizeof(char *)); |
flines = xrealloc_vector(flines, 8, max_fline); |
401 |
if (option_mask32 & FLAG_N) { |
|
402 |
/* Width of 7 preserves tab spacing in the text */ |
flines[max_fline] = (char*)xrealloc(MEMPTR(current_line), strlen(current_line) + 1 + 4) + 4; |
403 |
flines[max_fline] = xasprintf( |
LINENO(flines[max_fline]) = max_lineno; |
404 |
(max_lineno <= 9999999) ? "%7u %s" : "%07u %s", |
if (terminated) |
405 |
max_lineno % 10000000, current_line); |
max_lineno++; |
406 |
free(current_line); |
|
407 |
if (terminated) |
if (max_fline >= MAXLINES) { |
408 |
max_lineno++; |
eof_error = 0; /* Pretend we saw EOF */ |
|
} else { |
|
|
flines[max_fline] = xrealloc(current_line, strlen(current_line)+1); |
|
|
} |
|
|
if (max_fline >= MAXLINES) |
|
409 |
break; |
break; |
410 |
if (max_fline > cur_fline + max_displayed_line) |
} |
411 |
|
if (!(option_mask32 & FLAG_S) |
412 |
|
? (max_fline > cur_fline + max_displayed_line) |
413 |
|
: (max_fline >= cur_fline |
414 |
|
&& max_lineno > LINENO(flines[cur_fline]) + max_displayed_line) |
415 |
|
) { |
416 |
|
#if !ENABLE_FEATURE_LESS_REGEXP |
417 |
break; |
break; |
418 |
|
#else |
419 |
|
if (wanted_match >= num_matches) { /* goto_match called us */ |
420 |
|
fill_match_lines(old_max_fline); |
421 |
|
old_max_fline = max_fline; |
422 |
|
} |
423 |
|
if (wanted_match < num_matches) |
424 |
|
break; |
425 |
|
#endif |
426 |
|
} |
427 |
if (eof_error <= 0) { |
if (eof_error <= 0) { |
428 |
if (eof_error < 0 && errno == EAGAIN) { |
if (eof_error < 0) { |
429 |
/* not yet eof or error, reset flag (or else |
if (errno == EAGAIN) { |
430 |
* we will hog CPU - select() will return |
/* not yet eof or error, reset flag (or else |
431 |
* immediately */ |
* we will hog CPU - select() will return |
432 |
eof_error = 1; |
* immediately */ |
433 |
|
eof_error = 1; |
434 |
|
} else { |
435 |
|
print_statusline("read error"); |
436 |
|
} |
437 |
} |
} |
438 |
|
#if !ENABLE_FEATURE_LESS_REGEXP |
439 |
break; |
break; |
440 |
|
#else |
441 |
|
if (wanted_match < num_matches) { |
442 |
|
break; |
443 |
|
} else { /* goto_match called us */ |
444 |
|
time_t t = time(NULL); |
445 |
|
if (t != last_time) { |
446 |
|
last_time = t; |
447 |
|
if (--seconds_p1 == 0) |
448 |
|
break; |
449 |
|
} |
450 |
|
sched_yield(); |
451 |
|
goto again0; /* go loop again (max 2 seconds) */ |
452 |
|
} |
453 |
|
#endif |
454 |
} |
} |
455 |
max_fline++; |
max_fline++; |
456 |
current_line = xmalloc(w); |
current_line = ((char*)xmalloc(w + 4)) + 4; |
457 |
p = current_line; |
p = current_line; |
458 |
linepos = 0; |
last_line_pos = 0; |
459 |
} |
} /* end of "read lines until we reach cur_fline" loop */ |
460 |
fill_match_lines(old_max_fline); |
fill_match_lines(old_max_fline); |
461 |
|
#if ENABLE_FEATURE_LESS_REGEXP |
462 |
|
/* prevent us from being stuck in search for a match */ |
463 |
|
wanted_match = -1; |
464 |
|
#endif |
465 |
#undef readbuf |
#undef readbuf |
466 |
} |
} |
467 |
|
|
479 |
{ |
{ |
480 |
int percentage; |
int percentage; |
481 |
|
|
482 |
|
if (less_gets_pos >= 0) /* don't touch statusline while input is done! */ |
483 |
|
return; |
484 |
|
|
485 |
clear_line(); |
clear_line(); |
486 |
printf(HIGHLIGHT"%s", filename); |
printf(HIGHLIGHT"%s", filename); |
487 |
if (num_files > 1) |
if (num_files > 1) |
489 |
printf(" lines %i-%i/%i ", |
printf(" lines %i-%i/%i ", |
490 |
cur_fline + 1, cur_fline + max_displayed_line + 1, |
cur_fline + 1, cur_fline + max_displayed_line + 1, |
491 |
max_fline + 1); |
max_fline + 1); |
492 |
if (cur_fline >= max_fline - max_displayed_line) { |
if (cur_fline >= (int)(max_fline - max_displayed_line)) { |
493 |
printf("(END)"NORMAL); |
printf("(END)"NORMAL); |
494 |
if (num_files > 1 && current_file != num_files) |
if (num_files > 1 && current_file != num_files) |
495 |
printf(HIGHLIGHT" - next: %s"NORMAL, files[current_file]); |
printf(HIGHLIGHT" - next: %s"NORMAL, files[current_file]); |
505 |
{ |
{ |
506 |
const char *p; |
const char *p; |
507 |
|
|
508 |
|
if (less_gets_pos >= 0) /* don't touch statusline while input is done! */ |
509 |
|
return; |
510 |
|
|
511 |
/* Change the status if flags have been set */ |
/* Change the status if flags have been set */ |
512 |
#if ENABLE_FEATURE_LESS_FLAGS |
#if ENABLE_FEATURE_LESS_FLAGS |
513 |
if (option_mask32 & (FLAG_M|FLAG_m)) { |
if (option_mask32 & (FLAG_M|FLAG_m)) { |
518 |
#endif |
#endif |
519 |
|
|
520 |
clear_line(); |
clear_line(); |
521 |
if (cur_fline && cur_fline < max_fline - max_displayed_line) { |
if (cur_fline && cur_fline < (int)(max_fline - max_displayed_line)) { |
522 |
putchar(':'); |
bb_putchar(':'); |
523 |
return; |
return; |
524 |
} |
} |
525 |
p = "(END)"; |
p = "(END)"; |
533 |
print_hilite(p); |
print_hilite(p); |
534 |
} |
} |
535 |
|
|
536 |
static char controls[] = |
static void cap_cur_fline(int nlines) |
537 |
|
{ |
538 |
|
int diff; |
539 |
|
if (cur_fline < 0) |
540 |
|
cur_fline = 0; |
541 |
|
if (cur_fline + max_displayed_line > max_fline + TILDES) { |
542 |
|
cur_fline -= nlines; |
543 |
|
if (cur_fline < 0) |
544 |
|
cur_fline = 0; |
545 |
|
diff = max_fline - (cur_fline + max_displayed_line) + TILDES; |
546 |
|
/* As the number of lines requested was too large, we just move |
547 |
|
to the end of the file */ |
548 |
|
if (diff > 0) |
549 |
|
cur_fline += diff; |
550 |
|
} |
551 |
|
} |
552 |
|
|
553 |
|
static const char controls[] ALIGN1 = |
554 |
/* NUL: never encountered; TAB: not converted */ |
/* NUL: never encountered; TAB: not converted */ |
555 |
/**/"\x01\x02\x03\x04\x05\x06\x07\x08" "\x0a\x0b\x0c\x0d\x0e\x0f" |
/**/"\x01\x02\x03\x04\x05\x06\x07\x08" "\x0a\x0b\x0c\x0d\x0e\x0f" |
556 |
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" |
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" |
557 |
"\x7f\x9b"; /* DEL and infamous Meta-ESC :( */ |
"\x7f\x9b"; /* DEL and infamous Meta-ESC :( */ |
558 |
static char ctrlconv[] = |
static const char ctrlconv[] ALIGN1 = |
559 |
/* '\n': it's a former NUL - subst with '@', not 'J' */ |
/* '\n': it's a former NUL - subst with '@', not 'J' */ |
560 |
"\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x40\x4b\x4c\x4d\x4e\x4f" |
"\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x40\x4b\x4c\x4d\x4e\x4f" |
561 |
"\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"; |
"\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"; |
562 |
|
|
563 |
|
static void lineno_str(char *nbuf9, const char *line) |
564 |
|
{ |
565 |
|
nbuf9[0] = '\0'; |
566 |
|
if (option_mask32 & FLAG_N) { |
567 |
|
const char *fmt; |
568 |
|
unsigned n; |
569 |
|
|
570 |
|
if (line == empty_line_marker) { |
571 |
|
memset(nbuf9, ' ', 8); |
572 |
|
nbuf9[8] = '\0'; |
573 |
|
return; |
574 |
|
} |
575 |
|
/* Width of 7 preserves tab spacing in the text */ |
576 |
|
fmt = "%7u "; |
577 |
|
n = LINENO(line) + 1; |
578 |
|
if (n > 9999999) { |
579 |
|
n %= 10000000; |
580 |
|
fmt = "%07u "; |
581 |
|
} |
582 |
|
sprintf(nbuf9, fmt, n); |
583 |
|
} |
584 |
|
} |
585 |
|
|
586 |
|
|
587 |
#if ENABLE_FEATURE_LESS_REGEXP |
#if ENABLE_FEATURE_LESS_REGEXP |
588 |
static void print_found(const char *line) |
static void print_found(const char *line) |
589 |
{ |
{ |
593 |
regmatch_t match_structs; |
regmatch_t match_structs; |
594 |
|
|
595 |
char buf[width]; |
char buf[width]; |
596 |
|
char nbuf9[9]; |
597 |
const char *str = line; |
const char *str = line; |
598 |
char *p = buf; |
char *p = buf; |
599 |
size_t n; |
size_t n; |
630 |
match_structs.rm_so, str, |
match_structs.rm_so, str, |
631 |
match_structs.rm_eo - match_structs.rm_so, |
match_structs.rm_eo - match_structs.rm_so, |
632 |
str + match_structs.rm_so); |
str + match_structs.rm_so); |
633 |
free(growline); growline = new; |
free(growline); |
634 |
|
growline = new; |
635 |
str += match_structs.rm_eo; |
str += match_structs.rm_eo; |
636 |
line += match_structs.rm_eo; |
line += match_structs.rm_eo; |
637 |
eflags = REG_NOTBOL; |
eflags = REG_NOTBOL; |
638 |
start: |
start: |
639 |
/* Most of the time doesn't find the regex, optimize for that */ |
/* Most of the time doesn't find the regex, optimize for that */ |
640 |
match_status = regexec(&pattern, line, 1, &match_structs, eflags); |
match_status = regexec(&pattern, line, 1, &match_structs, eflags); |
641 |
|
/* if even "" matches, treat it as "not a match" */ |
642 |
|
if (match_structs.rm_so >= match_structs.rm_eo) |
643 |
|
match_status = 1; |
644 |
} |
} |
645 |
|
|
646 |
|
lineno_str(nbuf9, line); |
647 |
if (!growline) { |
if (!growline) { |
648 |
printf(CLEAR_2_EOL"%s\n", str); |
printf(CLEAR_2_EOL"%s%s\n", nbuf9, str); |
649 |
return; |
return; |
650 |
} |
} |
651 |
printf(CLEAR_2_EOL"%s%s\n", growline, str); |
printf(CLEAR_2_EOL"%s%s%s\n", nbuf9, growline, str); |
652 |
free(growline); |
free(growline); |
653 |
} |
} |
654 |
#else |
#else |
658 |
static void print_ascii(const char *str) |
static void print_ascii(const char *str) |
659 |
{ |
{ |
660 |
char buf[width]; |
char buf[width]; |
661 |
|
char nbuf9[9]; |
662 |
char *p; |
char *p; |
663 |
size_t n; |
size_t n; |
664 |
|
|
665 |
printf(CLEAR_2_EOL); |
lineno_str(nbuf9, str); |
666 |
|
printf(CLEAR_2_EOL"%s", nbuf9); |
667 |
|
|
668 |
while (*str) { |
while (*str) { |
669 |
n = strcspn(str, controls); |
n = strcspn(str, controls); |
670 |
if (n) { |
if (n) { |
694 |
/* Print the buffer */ |
/* Print the buffer */ |
695 |
static void buffer_print(void) |
static void buffer_print(void) |
696 |
{ |
{ |
697 |
int i; |
unsigned i; |
698 |
|
|
699 |
move_cursor(0, 0); |
move_cursor(0, 0); |
700 |
for (i = 0; i <= max_displayed_line; i++) |
for (i = 0; i <= max_displayed_line; i++) |
707 |
|
|
708 |
static void buffer_fill_and_print(void) |
static void buffer_fill_and_print(void) |
709 |
{ |
{ |
710 |
int i; |
unsigned i; |
711 |
|
#if ENABLE_FEATURE_LESS_DASHCMD |
712 |
|
int fpos = cur_fline; |
713 |
|
|
714 |
|
if (option_mask32 & FLAG_S) { |
715 |
|
/* Go back to the beginning of this line */ |
716 |
|
while (fpos && LINENO(flines[fpos]) == LINENO(flines[fpos-1])) |
717 |
|
fpos--; |
718 |
|
} |
719 |
|
|
720 |
|
i = 0; |
721 |
|
while (i <= max_displayed_line && fpos <= max_fline) { |
722 |
|
int lineno = LINENO(flines[fpos]); |
723 |
|
buffer[i] = flines[fpos]; |
724 |
|
i++; |
725 |
|
do { |
726 |
|
fpos++; |
727 |
|
} while ((fpos <= max_fline) |
728 |
|
&& (option_mask32 & FLAG_S) |
729 |
|
&& lineno == LINENO(flines[fpos]) |
730 |
|
); |
731 |
|
} |
732 |
|
#else |
733 |
for (i = 0; i <= max_displayed_line && cur_fline + i <= max_fline; i++) { |
for (i = 0; i <= max_displayed_line && cur_fline + i <= max_fline; i++) { |
734 |
buffer[i] = flines[cur_fline + i]; |
buffer[i] = flines[cur_fline + i]; |
735 |
} |
} |
736 |
|
#endif |
737 |
for (; i <= max_displayed_line; i++) { |
for (; i <= max_displayed_line; i++) { |
738 |
buffer[i] = empty_line_marker; |
buffer[i] = empty_line_marker; |
739 |
} |
} |
743 |
/* Move the buffer up and down in the file in order to scroll */ |
/* Move the buffer up and down in the file in order to scroll */ |
744 |
static void buffer_down(int nlines) |
static void buffer_down(int nlines) |
745 |
{ |
{ |
|
int diff; |
|
746 |
cur_fline += nlines; |
cur_fline += nlines; |
747 |
read_lines(); |
read_lines(); |
748 |
|
cap_cur_fline(nlines); |
|
if (cur_fline + max_displayed_line > max_fline + TILDES) { |
|
|
cur_fline -= nlines; |
|
|
diff = max_fline - (cur_fline + max_displayed_line) + TILDES; |
|
|
/* As the number of lines requested was too large, we just move |
|
|
to the end of the file */ |
|
|
if (diff > 0) |
|
|
cur_fline += diff; |
|
|
} |
|
749 |
buffer_fill_and_print(); |
buffer_fill_and_print(); |
750 |
} |
} |
751 |
|
|
765 |
read_lines(); |
read_lines(); |
766 |
if (linenum + max_displayed_line > max_fline) |
if (linenum + max_displayed_line > max_fline) |
767 |
linenum = max_fline - max_displayed_line + TILDES; |
linenum = max_fline - max_displayed_line + TILDES; |
768 |
|
if (linenum < 0) |
769 |
|
linenum = 0; |
770 |
cur_fline = linenum; |
cur_fline = linenum; |
771 |
buffer_fill_and_print(); |
buffer_fill_and_print(); |
772 |
} |
} |
784 |
} |
} |
785 |
readpos = 0; |
readpos = 0; |
786 |
readeof = 0; |
readeof = 0; |
787 |
linepos = 0; |
last_line_pos = 0; |
788 |
terminated = 1; |
terminated = 1; |
789 |
read_lines(); |
read_lines(); |
790 |
} |
} |
792 |
/* Reinitialize everything for a new file - free the memory and start over */ |
/* Reinitialize everything for a new file - free the memory and start over */ |
793 |
static void reinitialize(void) |
static void reinitialize(void) |
794 |
{ |
{ |
795 |
int i; |
unsigned i; |
796 |
|
|
797 |
if (flines) { |
if (flines) { |
798 |
for (i = 0; i <= max_fline; i++) |
for (i = 0; i <= max_fline; i++) |
799 |
free((void*)(flines[i])); |
free(MEMPTR(flines[i])); |
800 |
free(flines); |
free(flines); |
801 |
flines = NULL; |
flines = NULL; |
802 |
} |
} |
808 |
buffer_fill_and_print(); |
buffer_fill_and_print(); |
809 |
} |
} |
810 |
|
|
811 |
static void getch_nowait(char* input, int sz) |
static ssize_t getch_nowait(void) |
812 |
{ |
{ |
813 |
ssize_t rd; |
int rd; |
814 |
fd_set readfds; |
struct pollfd pfd[2]; |
|
again: |
|
|
fflush(stdout); |
|
815 |
|
|
816 |
/* NB: select returns whenever read will not block. Therefore: |
pfd[0].fd = STDIN_FILENO; |
817 |
* (a) with O_NONBLOCK'ed fds select will return immediately |
pfd[0].events = POLLIN; |
818 |
* (b) if eof is reached, select will also return |
pfd[1].fd = kbd_fd; |
819 |
* because read will immediately return 0 bytes. |
pfd[1].events = POLLIN; |
820 |
* Even if select says that input is available, read CAN block |
again: |
821 |
|
tcsetattr(kbd_fd, TCSANOW, &term_less); |
822 |
|
/* NB: select/poll returns whenever read will not block. Therefore: |
823 |
|
* if eof is reached, select/poll will return immediately |
824 |
|
* because read will immediately return 0 bytes. |
825 |
|
* Even if select/poll says that input is available, read CAN block |
826 |
* (switch fd into O_NONBLOCK'ed mode to avoid it) |
* (switch fd into O_NONBLOCK'ed mode to avoid it) |
827 |
*/ |
*/ |
828 |
FD_ZERO(&readfds); |
rd = 1; |
829 |
if (max_fline <= cur_fline + max_displayed_line |
/* Are we interested in stdin? */ |
830 |
&& eof_error > 0 /* did NOT reach eof yet */ |
//TODO: reuse code for determining this |
831 |
|
if (!(option_mask32 & FLAG_S) |
832 |
|
? !(max_fline > cur_fline + max_displayed_line) |
833 |
|
: !(max_fline >= cur_fline |
834 |
|
&& max_lineno > LINENO(flines[cur_fline]) + max_displayed_line) |
835 |
) { |
) { |
836 |
/* We are interested in stdin */ |
if (eof_error > 0) /* did NOT reach eof yet */ |
837 |
FD_SET(0, &readfds); |
rd = 0; /* yes, we are interested in stdin */ |
838 |
} |
} |
839 |
FD_SET(kbd_fd, &readfds); |
/* Position cursor if line input is done */ |
840 |
tcsetattr(kbd_fd, TCSANOW, &term_vi); |
if (less_gets_pos >= 0) |
841 |
select(kbd_fd + 1, &readfds, NULL, NULL, NULL); |
move_cursor(max_displayed_line + 2, less_gets_pos + 1); |
842 |
|
fflush(stdout); |
843 |
|
|
844 |
input[0] = '\0'; |
if (kbd_input_size == 0) { |
845 |
ndelay_on(kbd_fd); |
#if ENABLE_FEATURE_LESS_WINCH |
846 |
rd = read(kbd_fd, input, sz); |
while (1) { |
847 |
ndelay_off(kbd_fd); |
int r; |
848 |
if (rd < 0) { |
/* NB: SIGWINCH interrupts poll() */ |
849 |
/* No keyboard input, but we have input on stdin! */ |
r = poll(pfd + rd, 2 - rd, -1); |
850 |
if (errno != EAGAIN) /* Huh?? */ |
if (/*r < 0 && errno == EINTR &&*/ winch_counter) |
851 |
return; |
return '\\'; /* anything which has no defined function */ |
852 |
read_lines(); |
if (r) break; |
853 |
buffer_fill_and_print(); |
} |
854 |
goto again; |
#else |
855 |
|
safe_poll(pfd + rd, 2 - rd, -1); |
856 |
|
#endif |
857 |
} |
} |
858 |
|
|
859 |
|
/* We have kbd_fd in O_NONBLOCK mode, read inside read_key() |
860 |
|
* would not block even if there is no input available */ |
861 |
|
rd = read_key(kbd_fd, &kbd_input_size, kbd_input); |
862 |
|
if (rd == -1) { |
863 |
|
if (errno == EAGAIN) { |
864 |
|
/* No keyboard input available. Since poll() did return, |
865 |
|
* we should have input on stdin */ |
866 |
|
read_lines(); |
867 |
|
buffer_fill_and_print(); |
868 |
|
goto again; |
869 |
|
} |
870 |
|
/* EOF/error (ssh session got killed etc) */ |
871 |
|
less_exit(0); |
872 |
|
} |
873 |
|
set_tty_cooked(); |
874 |
|
return rd; |
875 |
} |
} |
876 |
|
|
877 |
/* Grab a character from input without requiring the return key. If the |
/* Grab a character from input without requiring the return key. If the |
878 |
* character is ASCII \033, get more characters and assign certain sequences |
* character is ASCII \033, get more characters and assign certain sequences |
879 |
* special return codes. Note that this function works best with raw input. */ |
* special return codes. Note that this function works best with raw input. */ |
880 |
static int less_getch(void) |
static int less_getch(int pos) |
881 |
{ |
{ |
882 |
char input[16]; |
int i; |
883 |
unsigned i; |
|
884 |
again: |
again: |
885 |
getch_nowait(input, sizeof(input)); |
less_gets_pos = pos; |
886 |
/* Detect escape sequences (i.e. arrow keys) and handle |
i = getch_nowait(); |
887 |
* them accordingly */ |
less_gets_pos = -1; |
888 |
|
|
889 |
if (input[0] == '\033' && input[1] == '[') { |
/* Discard Ctrl-something chars */ |
890 |
set_tty_cooked(); |
if (i >= 0 && i < ' ' && i != 0x0d && i != 8) |
891 |
i = input[2] - REAL_KEY_UP; |
goto again; |
|
if (i < 4) |
|
|
return 20 + i; |
|
|
i = input[2] - REAL_PAGE_UP; |
|
|
if (i < 4) |
|
|
return 24 + i; |
|
|
return 0; |
|
|
} |
|
|
/* Reject almost all control chars */ |
|
|
i = input[0]; |
|
|
if (i < ' ' && i != 0x0d && i != 8) goto again; |
|
|
set_tty_cooked(); |
|
892 |
return i; |
return i; |
893 |
} |
} |
894 |
|
|
895 |
static char* less_gets(int sz) |
static char* less_gets(int sz) |
896 |
{ |
{ |
897 |
char c; |
int c; |
898 |
int i = 0; |
unsigned i = 0; |
899 |
char *result = xzalloc(1); |
char *result = xzalloc(1); |
|
while (1) { |
|
|
fflush(stdout); |
|
900 |
|
|
901 |
/* I be damned if I know why is it needed *repeatedly*, |
while (1) { |
902 |
* but it is needed. Is it because of stdio? */ |
c = '\0'; |
903 |
tcsetattr(kbd_fd, TCSANOW, &term_vi); |
less_gets_pos = sz + i; |
904 |
|
c = getch_nowait(); |
905 |
read(kbd_fd, &c, 1); |
if (c == 0x0d) { |
906 |
if (c == 0x0d) |
result[i] = '\0'; |
907 |
|
less_gets_pos = -1; |
908 |
return result; |
return result; |
909 |
|
} |
910 |
if (c == 0x7f) |
if (c == 0x7f) |
911 |
c = 8; |
c = 8; |
912 |
if (c == 8 && i) { |
if (c == 8 && i) { |
913 |
printf("\x8 \x8"); |
printf("\x8 \x8"); |
914 |
i--; |
i--; |
915 |
} |
} |
916 |
if (c < ' ') |
if (c < ' ') /* filters out KEYCODE_xxx too (<0) */ |
917 |
continue; |
continue; |
918 |
if (i >= width - sz - 1) |
if (i >= width - sz - 1) |
919 |
continue; /* len limit */ |
continue; /* len limit */ |
920 |
putchar(c); |
bb_putchar(c); |
921 |
result[i++] = c; |
result[i++] = c; |
922 |
result = xrealloc(result, i+1); |
result = xrealloc(result, i+1); |
|
result[i] = '\0'; |
|
923 |
} |
} |
924 |
} |
} |
925 |
|
|
926 |
static void examine_file(void) |
static void examine_file(void) |
927 |
{ |
{ |
928 |
|
char *new_fname; |
929 |
|
|
930 |
print_statusline("Examine: "); |
print_statusline("Examine: "); |
931 |
|
new_fname = less_gets(sizeof("Examine: ") - 1); |
932 |
|
if (!new_fname[0]) { |
933 |
|
status_print(); |
934 |
|
err: |
935 |
|
free(new_fname); |
936 |
|
return; |
937 |
|
} |
938 |
|
if (access(new_fname, R_OK) != 0) { |
939 |
|
print_statusline("Cannot read this file"); |
940 |
|
goto err; |
941 |
|
} |
942 |
free(filename); |
free(filename); |
943 |
filename = less_gets(sizeof("Examine: ")-1); |
filename = new_fname; |
944 |
/* files start by = argv. why we assume that argv is infinitely long?? |
/* files start by = argv. why we assume that argv is infinitely long?? |
945 |
files[num_files] = filename; |
files[num_files] = filename; |
946 |
current_file = num_files + 1; |
current_file = num_files + 1; |
968 |
|
|
969 |
static void remove_current_file(void) |
static void remove_current_file(void) |
970 |
{ |
{ |
971 |
int i; |
unsigned i; |
972 |
|
|
973 |
if (num_files < 2) |
if (num_files < 2) |
974 |
return; |
return; |
994 |
/* Clear the current line and print a prompt */ |
/* Clear the current line and print a prompt */ |
995 |
print_statusline(" :"); |
print_statusline(" :"); |
996 |
|
|
997 |
keypress = less_getch(); |
keypress = less_getch(2); |
998 |
switch (keypress) { |
switch (keypress) { |
999 |
case 'd': |
case 'd': |
1000 |
remove_current_file(); |
remove_current_file(); |
1014 |
change_file(-1); |
change_file(-1); |
1015 |
break; |
break; |
1016 |
case 'q': |
case 'q': |
1017 |
less_exit(0); |
less_exit(EXIT_SUCCESS); |
1018 |
break; |
break; |
1019 |
case 'x': |
case 'x': |
1020 |
change_file(0); |
change_file(0); |
1023 |
} |
} |
1024 |
|
|
1025 |
#if ENABLE_FEATURE_LESS_REGEXP |
#if ENABLE_FEATURE_LESS_REGEXP |
1026 |
static int normalize_match_pos(int match) |
static void normalize_match_pos(int match) |
1027 |
{ |
{ |
|
match_pos = match; |
|
1028 |
if (match >= num_matches) |
if (match >= num_matches) |
1029 |
match_pos = num_matches - 1; |
match = num_matches - 1; |
1030 |
if (match < 0) |
if (match < 0) |
1031 |
match_pos = 0; |
match = 0; |
1032 |
return match_pos; |
match_pos = match; |
1033 |
} |
} |
1034 |
|
|
1035 |
static void goto_match(int match) |
static void goto_match(int match) |
1036 |
{ |
{ |
1037 |
if (num_matches) |
if (!pattern_valid) |
1038 |
buffer_line(match_lines[normalize_match_pos(match)]); |
return; |
1039 |
|
if (match < 0) |
1040 |
|
match = 0; |
1041 |
|
/* Try to find next match if eof isn't reached yet */ |
1042 |
|
if (match >= num_matches && eof_error > 0) { |
1043 |
|
wanted_match = match; /* "I want to read until I see N'th match" */ |
1044 |
|
read_lines(); |
1045 |
|
} |
1046 |
|
if (num_matches) { |
1047 |
|
normalize_match_pos(match); |
1048 |
|
buffer_line(match_lines[match_pos]); |
1049 |
|
} else { |
1050 |
|
print_statusline("No matches found"); |
1051 |
|
} |
1052 |
} |
} |
1053 |
|
|
1054 |
static void fill_match_lines(unsigned pos) |
static void fill_match_lines(unsigned pos) |
1062 |
/* and we didn't match it last time */ |
/* and we didn't match it last time */ |
1063 |
&& !(num_matches && match_lines[num_matches-1] == pos) |
&& !(num_matches && match_lines[num_matches-1] == pos) |
1064 |
) { |
) { |
1065 |
match_lines = xrealloc(match_lines, (num_matches+1) * sizeof(int)); |
match_lines = xrealloc_vector(match_lines, 4, num_matches); |
1066 |
match_lines[num_matches++] = pos; |
match_lines[num_matches++] = pos; |
1067 |
} |
} |
1068 |
pos++; |
pos++; |
1085 |
|
|
1086 |
/* Get the uncompiled regular expression from the user */ |
/* Get the uncompiled regular expression from the user */ |
1087 |
clear_line(); |
clear_line(); |
1088 |
putchar((option_mask32 & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/'); |
bb_putchar((option_mask32 & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/'); |
1089 |
uncomp_regex = less_gets(1); |
uncomp_regex = less_gets(1); |
1090 |
if (!uncomp_regex[0]) { |
if (!uncomp_regex[0]) { |
1091 |
free(uncomp_regex); |
free(uncomp_regex); |
1094 |
} |
} |
1095 |
|
|
1096 |
/* Compile the regex and check for errors */ |
/* Compile the regex and check for errors */ |
1097 |
err = regcomp_or_errmsg(&pattern, uncomp_regex, 0); |
err = regcomp_or_errmsg(&pattern, uncomp_regex, |
1098 |
|
(option_mask32 & FLAG_I) ? REG_ICASE : 0); |
1099 |
free(uncomp_regex); |
free(uncomp_regex); |
1100 |
if (err) { |
if (err) { |
1101 |
print_statusline(err); |
print_statusline(err); |
1102 |
free(err); |
free(err); |
1103 |
return; |
return; |
1104 |
} |
} |
1105 |
|
|
1106 |
pattern_valid = 1; |
pattern_valid = 1; |
1107 |
match_pos = 0; |
match_pos = 0; |
|
|
|
1108 |
fill_match_lines(0); |
fill_match_lines(0); |
|
|
|
|
if (num_matches == 0 || max_fline <= max_displayed_line) { |
|
|
buffer_print(); |
|
|
return; |
|
|
} |
|
1109 |
while (match_pos < num_matches) { |
while (match_pos < num_matches) { |
1110 |
if (match_lines[match_pos] > cur_fline) |
if ((int)match_lines[match_pos] > cur_fline) |
1111 |
break; |
break; |
1112 |
match_pos++; |
match_pos++; |
1113 |
} |
} |
1114 |
if (option_mask32 & LESS_STATE_MATCH_BACKWARDS) |
if (option_mask32 & LESS_STATE_MATCH_BACKWARDS) |
1115 |
match_pos--; |
match_pos--; |
1116 |
buffer_line(match_lines[normalize_match_pos(match_pos)]); |
|
1117 |
|
/* It's possible that no matches are found yet. |
1118 |
|
* goto_match() will read input looking for match, |
1119 |
|
* if needed */ |
1120 |
|
goto_match(match_pos); |
1121 |
} |
} |
1122 |
#endif |
#endif |
1123 |
|
|
1124 |
static void number_process(int first_digit) |
static void number_process(int first_digit) |
1125 |
{ |
{ |
1126 |
int i = 1; |
unsigned i; |
1127 |
int num; |
int num; |
1128 |
|
int keypress; |
1129 |
char num_input[sizeof(int)*4]; /* more than enough */ |
char num_input[sizeof(int)*4]; /* more than enough */ |
|
char keypress; |
|
1130 |
|
|
1131 |
num_input[0] = first_digit; |
num_input[0] = first_digit; |
1132 |
|
|
1135 |
printf(":%c", first_digit); |
printf(":%c", first_digit); |
1136 |
|
|
1137 |
/* Receive input until a letter is given */ |
/* Receive input until a letter is given */ |
1138 |
|
i = 1; |
1139 |
while (i < sizeof(num_input)-1) { |
while (i < sizeof(num_input)-1) { |
1140 |
num_input[i] = less_getch(); |
keypress = less_getch(i + 1); |
1141 |
if (!num_input[i] || !isdigit(num_input[i])) |
if ((unsigned)keypress > 255 || !isdigit(num_input[i])) |
1142 |
break; |
break; |
1143 |
putchar(num_input[i]); |
num_input[i] = keypress; |
1144 |
|
bb_putchar(keypress); |
1145 |
i++; |
i++; |
1146 |
} |
} |
1147 |
|
|
|
/* Take the final letter out of the digits string */ |
|
|
keypress = num_input[i]; |
|
1148 |
num_input[i] = '\0'; |
num_input[i] = '\0'; |
1149 |
num = bb_strtou(num_input, NULL, 10); |
num = bb_strtou(num_input, NULL, 10); |
1150 |
/* on format error, num == -1 */ |
/* on format error, num == -1 */ |
1155 |
|
|
1156 |
/* We now know the number and the letter entered, so we process them */ |
/* We now know the number and the letter entered, so we process them */ |
1157 |
switch (keypress) { |
switch (keypress) { |
1158 |
case KEY_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015': |
case KEYCODE_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015': |
1159 |
buffer_down(num); |
buffer_down(num); |
1160 |
break; |
break; |
1161 |
case KEY_UP: case 'b': case 'w': case 'y': case 'u': |
case KEYCODE_UP: case 'b': case 'w': case 'y': case 'u': |
1162 |
buffer_up(num); |
buffer_up(num); |
1163 |
break; |
break; |
1164 |
case 'g': case '<': case 'G': case '>': |
case 'g': case '<': case 'G': case '>': |
1165 |
cur_fline = num + max_displayed_line; |
cur_fline = num + max_displayed_line; |
1166 |
read_lines(); |
read_lines(); |
1167 |
buffer_line(num - 1); |
buffer_line(num - 1); |
1168 |
break; |
break; |
1169 |
case 'p': case '%': |
case 'p': case '%': |
1170 |
num = num * (max_fline / 100); /* + max_fline / 2; */ |
num = num * (max_fline / 100); /* + max_fline / 2; */ |
1171 |
cur_fline = num + max_displayed_line; |
cur_fline = num + max_displayed_line; |
1172 |
read_lines(); |
read_lines(); |
1173 |
buffer_line(num); |
buffer_line(num); |
1174 |
break; |
break; |
1175 |
#if ENABLE_FEATURE_LESS_REGEXP |
#if ENABLE_FEATURE_LESS_REGEXP |
1188 |
} |
} |
1189 |
} |
} |
1190 |
|
|
1191 |
#if ENABLE_FEATURE_LESS_FLAGCS |
#if ENABLE_FEATURE_LESS_DASHCMD |
1192 |
static void flag_change(void) |
static void flag_change(void) |
1193 |
{ |
{ |
1194 |
int keypress; |
int keypress; |
1195 |
|
|
1196 |
clear_line(); |
clear_line(); |
1197 |
putchar('-'); |
bb_putchar('-'); |
1198 |
keypress = less_getch(); |
keypress = less_getch(1); |
1199 |
|
|
1200 |
switch (keypress) { |
switch (keypress) { |
1201 |
case 'M': |
case 'M': |
1210 |
case '~': |
case '~': |
1211 |
option_mask32 ^= FLAG_TILDE; |
option_mask32 ^= FLAG_TILDE; |
1212 |
break; |
break; |
1213 |
|
case 'S': |
1214 |
|
option_mask32 ^= FLAG_S; |
1215 |
|
buffer_fill_and_print(); |
1216 |
|
break; |
1217 |
|
#if ENABLE_FEATURE_LESS_LINENUMS |
1218 |
|
case 'N': |
1219 |
|
option_mask32 ^= FLAG_N; |
1220 |
|
re_wrap(); |
1221 |
|
buffer_fill_and_print(); |
1222 |
|
break; |
1223 |
|
#endif |
1224 |
} |
} |
1225 |
} |
} |
1226 |
|
|
1227 |
|
#ifdef BLOAT |
1228 |
static void show_flag_status(void) |
static void show_flag_status(void) |
1229 |
{ |
{ |
1230 |
int keypress; |
int keypress; |
1231 |
int flag_val; |
int flag_val; |
1232 |
|
|
1233 |
clear_line(); |
clear_line(); |
1234 |
putchar('_'); |
bb_putchar('_'); |
1235 |
keypress = less_getch(); |
keypress = less_getch(1); |
1236 |
|
|
1237 |
switch (keypress) { |
switch (keypress) { |
1238 |
case 'M': |
case 'M': |
1260 |
} |
} |
1261 |
#endif |
#endif |
1262 |
|
|
1263 |
|
#endif /* ENABLE_FEATURE_LESS_DASHCMD */ |
1264 |
|
|
1265 |
static void save_input_to_file(void) |
static void save_input_to_file(void) |
1266 |
{ |
{ |
1267 |
const char *msg = ""; |
const char *msg = ""; |
1268 |
char *current_line; |
char *current_line; |
1269 |
int i; |
unsigned i; |
1270 |
FILE *fp; |
FILE *fp; |
1271 |
|
|
1272 |
print_statusline("Log file: "); |
print_statusline("Log file: "); |
1273 |
current_line = less_gets(sizeof("Log file: ")-1); |
current_line = less_gets(sizeof("Log file: ")-1); |
1274 |
if (strlen(current_line) > 0) { |
if (current_line[0]) { |
1275 |
fp = fopen(current_line, "w"); |
fp = fopen_for_write(current_line); |
1276 |
if (!fp) { |
if (!fp) { |
1277 |
msg = "Error opening log file"; |
msg = "Error opening log file"; |
1278 |
goto ret; |
goto ret; |
1293 |
int letter; |
int letter; |
1294 |
|
|
1295 |
print_statusline("Mark: "); |
print_statusline("Mark: "); |
1296 |
letter = less_getch(); |
letter = less_getch(sizeof("Mark: ") - 1); |
1297 |
|
|
1298 |
if (isalpha(letter)) { |
if (isalpha(letter)) { |
1299 |
/* If we exceed 15 marks, start overwriting previous ones */ |
/* If we exceed 15 marks, start overwriting previous ones */ |
1314 |
int i; |
int i; |
1315 |
|
|
1316 |
print_statusline("Go to mark: "); |
print_statusline("Go to mark: "); |
1317 |
letter = less_getch(); |
letter = less_getch(sizeof("Go to mark: ") - 1); |
1318 |
clear_line(); |
clear_line(); |
1319 |
|
|
1320 |
if (isalpha(letter)) { |
if (isalpha(letter)) { |
1334 |
static char opp_bracket(char bracket) |
static char opp_bracket(char bracket) |
1335 |
{ |
{ |
1336 |
switch (bracket) { |
switch (bracket) { |
1337 |
case '{': case '[': |
case '{': case '[': /* '}' == '{' + 2. Same for '[' */ |
1338 |
return bracket + 2; |
bracket++; |
1339 |
case '(': |
case '(': /* ')' == '(' + 1 */ |
1340 |
return ')'; |
bracket++; |
1341 |
case '}': case ']': |
break; |
1342 |
return bracket - 2; |
case '}': case ']': |
1343 |
case ')': |
bracket--; |
1344 |
return '('; |
case ')': |
1345 |
} |
bracket--; |
1346 |
return 0; |
break; |
1347 |
|
}; |
1348 |
|
return bracket; |
1349 |
} |
} |
1350 |
|
|
1351 |
static void match_right_bracket(char bracket) |
static void match_right_bracket(char bracket) |
1352 |
{ |
{ |
1353 |
int bracket_line = -1; |
unsigned i; |
|
int i; |
|
1354 |
|
|
1355 |
if (strchr(flines[cur_fline], bracket) == NULL) { |
if (strchr(flines[cur_fline], bracket) == NULL) { |
1356 |
print_statusline("No bracket in top line"); |
print_statusline("No bracket in top line"); |
1357 |
return; |
return; |
1358 |
} |
} |
1359 |
|
bracket = opp_bracket(bracket); |
1360 |
for (i = cur_fline + 1; i < max_fline; i++) { |
for (i = cur_fline + 1; i < max_fline; i++) { |
1361 |
if (strchr(flines[i], opp_bracket(bracket)) != NULL) { |
if (strchr(flines[i], bracket) != NULL) { |
1362 |
bracket_line = i; |
buffer_line(i); |
1363 |
break; |
return; |
1364 |
} |
} |
1365 |
} |
} |
1366 |
if (bracket_line == -1) |
print_statusline("No matching bracket found"); |
|
print_statusline("No matching bracket found"); |
|
|
buffer_line(bracket_line - max_displayed_line); |
|
1367 |
} |
} |
1368 |
|
|
1369 |
static void match_left_bracket(char bracket) |
static void match_left_bracket(char bracket) |
1370 |
{ |
{ |
|
int bracket_line = -1; |
|
1371 |
int i; |
int i; |
1372 |
|
|
1373 |
if (strchr(flines[cur_fline + max_displayed_line], bracket) == NULL) { |
if (strchr(flines[cur_fline + max_displayed_line], bracket) == NULL) { |
1375 |
return; |
return; |
1376 |
} |
} |
1377 |
|
|
1378 |
|
bracket = opp_bracket(bracket); |
1379 |
for (i = cur_fline + max_displayed_line; i >= 0; i--) { |
for (i = cur_fline + max_displayed_line; i >= 0; i--) { |
1380 |
if (strchr(flines[i], opp_bracket(bracket)) != NULL) { |
if (strchr(flines[i], bracket) != NULL) { |
1381 |
bracket_line = i; |
buffer_line(i); |
1382 |
break; |
return; |
1383 |
} |
} |
1384 |
} |
} |
1385 |
if (bracket_line == -1) |
print_statusline("No matching bracket found"); |
|
print_statusline("No matching bracket found"); |
|
|
buffer_line(bracket_line); |
|
1386 |
} |
} |
1387 |
#endif /* FEATURE_LESS_BRACKETS */ |
#endif /* FEATURE_LESS_BRACKETS */ |
1388 |
|
|
1389 |
static void keypress_process(int keypress) |
static void keypress_process(int keypress) |
1390 |
{ |
{ |
1391 |
switch (keypress) { |
switch (keypress) { |
1392 |
case KEY_DOWN: case 'e': case 'j': case 0x0d: |
case KEYCODE_DOWN: case 'e': case 'j': case 0x0d: |
1393 |
buffer_down(1); |
buffer_down(1); |
1394 |
break; |
break; |
1395 |
case KEY_UP: case 'y': case 'k': |
case KEYCODE_UP: case 'y': case 'k': |
1396 |
buffer_up(1); |
buffer_up(1); |
1397 |
break; |
break; |
1398 |
case PAGE_DOWN: case ' ': case 'z': |
case KEYCODE_PAGEDOWN: case ' ': case 'z': case 'f': |
1399 |
buffer_down(max_displayed_line + 1); |
buffer_down(max_displayed_line + 1); |
1400 |
break; |
break; |
1401 |
case PAGE_UP: case 'w': case 'b': |
case KEYCODE_PAGEUP: case 'w': case 'b': |
1402 |
buffer_up(max_displayed_line + 1); |
buffer_up(max_displayed_line + 1); |
1403 |
break; |
break; |
1404 |
case 'd': |
case 'd': |
1407 |
case 'u': |
case 'u': |
1408 |
buffer_up((max_displayed_line + 1) / 2); |
buffer_up((max_displayed_line + 1) / 2); |
1409 |
break; |
break; |
1410 |
case KEY_HOME: case 'g': case 'p': case '<': case '%': |
case KEYCODE_HOME: case 'g': case 'p': case '<': case '%': |
1411 |
buffer_line(0); |
buffer_line(0); |
1412 |
break; |
break; |
1413 |
case KEY_END: case 'G': case '>': |
case KEYCODE_END: case 'G': case '>': |
1414 |
cur_fline = MAXLINES; |
cur_fline = MAXLINES; |
1415 |
read_lines(); |
read_lines(); |
1416 |
buffer_line(cur_fline); |
buffer_line(cur_fline); |
1417 |
break; |
break; |
1418 |
case 'q': case 'Q': |
case 'q': case 'Q': |
1419 |
less_exit(0); |
less_exit(EXIT_SUCCESS); |
1420 |
break; |
break; |
1421 |
#if ENABLE_FEATURE_LESS_MARKS |
#if ENABLE_FEATURE_LESS_MARKS |
1422 |
case 'm': |
case 'm': |
1461 |
regex_process(); |
regex_process(); |
1462 |
break; |
break; |
1463 |
#endif |
#endif |
1464 |
#if ENABLE_FEATURE_LESS_FLAGCS |
#if ENABLE_FEATURE_LESS_DASHCMD |
1465 |
case '-': |
case '-': |
1466 |
flag_change(); |
flag_change(); |
1467 |
buffer_print(); |
buffer_print(); |
1468 |
break; |
break; |
1469 |
|
#ifdef BLOAT |
1470 |
case '_': |
case '_': |
1471 |
show_flag_status(); |
show_flag_status(); |
1472 |
break; |
break; |
1473 |
#endif |
#endif |
1474 |
|
#endif |
1475 |
#if ENABLE_FEATURE_LESS_BRACKETS |
#if ENABLE_FEATURE_LESS_BRACKETS |
1476 |
case '{': case '(': case '[': |
case '{': case '(': case '[': |
1477 |
match_right_bracket(keypress); |
match_right_bracket(keypress); |
1489 |
number_process(keypress); |
number_process(keypress); |
1490 |
} |
} |
1491 |
|
|
1492 |
static void sig_catcher(int sig ATTRIBUTE_UNUSED) |
static void sig_catcher(int sig) |
1493 |
{ |
{ |
1494 |
set_tty_cooked(); |
less_exit(- sig); |
1495 |
exit(1); |
} |
1496 |
|
|
1497 |
|
#if ENABLE_FEATURE_LESS_WINCH |
1498 |
|
static void sigwinch_handler(int sig UNUSED_PARAM) |
1499 |
|
{ |
1500 |
|
winch_counter++; |
1501 |
} |
} |
1502 |
|
#endif |
1503 |
|
|
1504 |
|
int less_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
1505 |
int less_main(int argc, char **argv) |
int less_main(int argc, char **argv) |
1506 |
{ |
{ |
1507 |
int keypress; |
int keypress; |
1508 |
|
|
1509 |
|
INIT_G(); |
1510 |
|
|
1511 |
/* TODO: -x: do not interpret backspace, -xx: tab also */ |
/* TODO: -x: do not interpret backspace, -xx: tab also */ |
1512 |
/* -xxx: newline also */ |
/* -xxx: newline also */ |
1513 |
/* -w N: assume width N (-xxx -w 32: hex viewer of sorts) */ |
/* -w N: assume width N (-xxx -w 32: hex viewer of sorts) */ |
1514 |
getopt32(argc, argv, "EMmN~"); |
getopt32(argv, "EMmN~I" USE_FEATURE_LESS_DASHCMD("S")); |
1515 |
argc -= optind; |
argc -= optind; |
1516 |
argv += optind; |
argv += optind; |
1517 |
num_files = argc; |
num_files = argc; |
1528 |
bb_error_msg("missing filename"); |
bb_error_msg("missing filename"); |
1529 |
bb_show_usage(); |
bb_show_usage(); |
1530 |
} |
} |
1531 |
} else |
} else { |
1532 |
filename = xstrdup(files[0]); |
filename = xstrdup(files[0]); |
1533 |
|
} |
1534 |
|
|
1535 |
|
if (option_mask32 & FLAG_TILDE) |
1536 |
|
empty_line_marker = ""; |
1537 |
|
|
1538 |
|
kbd_fd = open(CURRENT_TTY, O_RDONLY); |
1539 |
|
if (kbd_fd < 0) |
1540 |
|
return bb_cat(argv); |
1541 |
|
ndelay_on(kbd_fd); |
1542 |
|
|
1543 |
kbd_fd = xopen(CURRENT_TTY, O_RDONLY); |
tcgetattr(kbd_fd, &term_orig); |
1544 |
|
term_less = term_orig; |
1545 |
|
term_less.c_lflag &= ~(ICANON | ECHO); |
1546 |
|
term_less.c_iflag &= ~(IXON | ICRNL); |
1547 |
|
/*term_less.c_oflag &= ~ONLCR;*/ |
1548 |
|
term_less.c_cc[VMIN] = 1; |
1549 |
|
term_less.c_cc[VTIME] = 0; |
1550 |
|
|
1551 |
get_terminal_width_height(kbd_fd, &width, &max_displayed_line); |
get_terminal_width_height(kbd_fd, &width, &max_displayed_line); |
1552 |
/* 20: two tabstops + 4 */ |
/* 20: two tabstops + 4 */ |
1553 |
if (width < 20 || max_displayed_line < 3) |
if (width < 20 || max_displayed_line < 3) |
1554 |
bb_error_msg_and_die("too narrow here"); |
return bb_cat(argv); |
1555 |
max_displayed_line -= 2; |
max_displayed_line -= 2; |
1556 |
|
|
1557 |
buffer = xmalloc((max_displayed_line+1) * sizeof(char *)); |
/* We want to restore term_orig on exit */ |
1558 |
if (option_mask32 & FLAG_TILDE) |
bb_signals(BB_FATAL_SIGS, sig_catcher); |
1559 |
empty_line_marker = ""; |
#if ENABLE_FEATURE_LESS_WINCH |
1560 |
|
signal(SIGWINCH, sigwinch_handler); |
1561 |
tcgetattr(kbd_fd, &term_orig); |
#endif |
|
signal(SIGTERM, sig_catcher); |
|
|
signal(SIGINT, sig_catcher); |
|
|
term_vi = term_orig; |
|
|
term_vi.c_lflag &= ~(ICANON | ECHO); |
|
|
term_vi.c_iflag &= ~(IXON | ICRNL); |
|
|
/*term_vi.c_oflag &= ~ONLCR;*/ |
|
|
term_vi.c_cc[VMIN] = 1; |
|
|
term_vi.c_cc[VTIME] = 0; |
|
|
|
|
|
/* Want to do it just once, but it doesn't work, */ |
|
|
/* so we are redoing it (see code above). Mystery... */ |
|
|
/*tcsetattr(kbd_fd, TCSANOW, &term_vi);*/ |
|
1562 |
|
|
1563 |
|
buffer = xmalloc((max_displayed_line+1) * sizeof(char *)); |
1564 |
reinitialize(); |
reinitialize(); |
1565 |
while (1) { |
while (1) { |
1566 |
keypress = less_getch(); |
#if ENABLE_FEATURE_LESS_WINCH |
1567 |
|
while (WINCH_COUNTER) { |
1568 |
|
again: |
1569 |
|
winch_counter--; |
1570 |
|
get_terminal_width_height(kbd_fd, &width, &max_displayed_line); |
1571 |
|
/* 20: two tabstops + 4 */ |
1572 |
|
if (width < 20) |
1573 |
|
width = 20; |
1574 |
|
if (max_displayed_line < 3) |
1575 |
|
max_displayed_line = 3; |
1576 |
|
max_displayed_line -= 2; |
1577 |
|
free(buffer); |
1578 |
|
buffer = xmalloc((max_displayed_line+1) * sizeof(char *)); |
1579 |
|
/* Avoid re-wrap and/or redraw if we already know |
1580 |
|
* we need to do it again. These ops are expensive */ |
1581 |
|
if (WINCH_COUNTER) |
1582 |
|
goto again; |
1583 |
|
re_wrap(); |
1584 |
|
if (WINCH_COUNTER) |
1585 |
|
goto again; |
1586 |
|
buffer_fill_and_print(); |
1587 |
|
/* This took some time. Loop back and check, |
1588 |
|
* were there another SIGWINCH? */ |
1589 |
|
} |
1590 |
|
#endif |
1591 |
|
keypress = less_getch(-1); /* -1: do not position cursor */ |
1592 |
keypress_process(keypress); |
keypress_process(keypress); |
1593 |
} |
} |
1594 |
} |
} |
1595 |
|
|
1596 |
|
/* |
1597 |
|
Help text of less version 418 is below. |
1598 |
|
If you are implementing something, keeping |
1599 |
|
key and/or command line switch compatibility is a good idea: |
1600 |
|
|
1601 |
|
|
1602 |
|
SUMMARY OF LESS COMMANDS |
1603 |
|
|
1604 |
|
Commands marked with * may be preceded by a number, N. |
1605 |
|
Notes in parentheses indicate the behavior if N is given. |
1606 |
|
h H Display this help. |
1607 |
|
q :q Q :Q ZZ Exit. |
1608 |
|
--------------------------------------------------------------------------- |
1609 |
|
MOVING |
1610 |
|
e ^E j ^N CR * Forward one line (or N lines). |
1611 |
|
y ^Y k ^K ^P * Backward one line (or N lines). |
1612 |
|
f ^F ^V SPACE * Forward one window (or N lines). |
1613 |
|
b ^B ESC-v * Backward one window (or N lines). |
1614 |
|
z * Forward one window (and set window to N). |
1615 |
|
w * Backward one window (and set window to N). |
1616 |
|
ESC-SPACE * Forward one window, but don't stop at end-of-file. |
1617 |
|
d ^D * Forward one half-window (and set half-window to N). |
1618 |
|
u ^U * Backward one half-window (and set half-window to N). |
1619 |
|
ESC-) RightArrow * Left one half screen width (or N positions). |
1620 |
|
ESC-( LeftArrow * Right one half screen width (or N positions). |
1621 |
|
F Forward forever; like "tail -f". |
1622 |
|
r ^R ^L Repaint screen. |
1623 |
|
R Repaint screen, discarding buffered input. |
1624 |
|
--------------------------------------------------- |
1625 |
|
Default "window" is the screen height. |
1626 |
|
Default "half-window" is half of the screen height. |
1627 |
|
--------------------------------------------------------------------------- |
1628 |
|
SEARCHING |
1629 |
|
/pattern * Search forward for (N-th) matching line. |
1630 |
|
?pattern * Search backward for (N-th) matching line. |
1631 |
|
n * Repeat previous search (for N-th occurrence). |
1632 |
|
N * Repeat previous search in reverse direction. |
1633 |
|
ESC-n * Repeat previous search, spanning files. |
1634 |
|
ESC-N * Repeat previous search, reverse dir. & spanning files. |
1635 |
|
ESC-u Undo (toggle) search highlighting. |
1636 |
|
--------------------------------------------------- |
1637 |
|
Search patterns may be modified by one or more of: |
1638 |
|
^N or ! Search for NON-matching lines. |
1639 |
|
^E or * Search multiple files (pass thru END OF FILE). |
1640 |
|
^F or @ Start search at FIRST file (for /) or last file (for ?). |
1641 |
|
^K Highlight matches, but don't move (KEEP position). |
1642 |
|
^R Don't use REGULAR EXPRESSIONS. |
1643 |
|
--------------------------------------------------------------------------- |
1644 |
|
JUMPING |
1645 |
|
g < ESC-< * Go to first line in file (or line N). |
1646 |
|
G > ESC-> * Go to last line in file (or line N). |
1647 |
|
p % * Go to beginning of file (or N percent into file). |
1648 |
|
t * Go to the (N-th) next tag. |
1649 |
|
T * Go to the (N-th) previous tag. |
1650 |
|
{ ( [ * Find close bracket } ) ]. |
1651 |
|
} ) ] * Find open bracket { ( [. |
1652 |
|
ESC-^F <c1> <c2> * Find close bracket <c2>. |
1653 |
|
ESC-^B <c1> <c2> * Find open bracket <c1> |
1654 |
|
--------------------------------------------------- |
1655 |
|
Each "find close bracket" command goes forward to the close bracket |
1656 |
|
matching the (N-th) open bracket in the top line. |
1657 |
|
Each "find open bracket" command goes backward to the open bracket |
1658 |
|
matching the (N-th) close bracket in the bottom line. |
1659 |
|
m<letter> Mark the current position with <letter>. |
1660 |
|
'<letter> Go to a previously marked position. |
1661 |
|
'' Go to the previous position. |
1662 |
|
^X^X Same as '. |
1663 |
|
--------------------------------------------------- |
1664 |
|
A mark is any upper-case or lower-case letter. |
1665 |
|
Certain marks are predefined: |
1666 |
|
^ means beginning of the file |
1667 |
|
$ means end of the file |
1668 |
|
--------------------------------------------------------------------------- |
1669 |
|
CHANGING FILES |
1670 |
|
:e [file] Examine a new file. |
1671 |
|
^X^V Same as :e. |
1672 |
|
:n * Examine the (N-th) next file from the command line. |
1673 |
|
:p * Examine the (N-th) previous file from the command line. |
1674 |
|
:x * Examine the first (or N-th) file from the command line. |
1675 |
|
:d Delete the current file from the command line list. |
1676 |
|
= ^G :f Print current file name. |
1677 |
|
--------------------------------------------------------------------------- |
1678 |
|
MISCELLANEOUS COMMANDS |
1679 |
|
-<flag> Toggle a command line option [see OPTIONS below]. |
1680 |
|
--<name> Toggle a command line option, by name. |
1681 |
|
_<flag> Display the setting of a command line option. |
1682 |
|
__<name> Display the setting of an option, by name. |
1683 |
|
+cmd Execute the less cmd each time a new file is examined. |
1684 |
|
!command Execute the shell command with $SHELL. |
1685 |
|
|Xcommand Pipe file between current pos & mark X to shell command. |
1686 |
|
v Edit the current file with $VISUAL or $EDITOR. |
1687 |
|
V Print version number of "less". |
1688 |
|
--------------------------------------------------------------------------- |
1689 |
|
OPTIONS |
1690 |
|
Most options may be changed either on the command line, |
1691 |
|
or from within less by using the - or -- command. |
1692 |
|
Options may be given in one of two forms: either a single |
1693 |
|
character preceded by a -, or a name preceeded by --. |
1694 |
|
-? ........ --help |
1695 |
|
Display help (from command line). |
1696 |
|
-a ........ --search-skip-screen |
1697 |
|
Forward search skips current screen. |
1698 |
|
-b [N] .... --buffers=[N] |
1699 |
|
Number of buffers. |
1700 |
|
-B ........ --auto-buffers |
1701 |
|
Don't automatically allocate buffers for pipes. |
1702 |
|
-c ........ --clear-screen |
1703 |
|
Repaint by clearing rather than scrolling. |
1704 |
|
-d ........ --dumb |
1705 |
|
Dumb terminal. |
1706 |
|
-D [xn.n] . --color=xn.n |
1707 |
|
Set screen colors. (MS-DOS only) |
1708 |
|
-e -E .... --quit-at-eof --QUIT-AT-EOF |
1709 |
|
Quit at end of file. |
1710 |
|
-f ........ --force |
1711 |
|
Force open non-regular files. |
1712 |
|
-F ........ --quit-if-one-screen |
1713 |
|
Quit if entire file fits on first screen. |
1714 |
|
-g ........ --hilite-search |
1715 |
|
Highlight only last match for searches. |
1716 |
|
-G ........ --HILITE-SEARCH |
1717 |
|
Don't highlight any matches for searches. |
1718 |
|
-h [N] .... --max-back-scroll=[N] |
1719 |
|
Backward scroll limit. |
1720 |
|
-i ........ --ignore-case |
1721 |
|
Ignore case in searches that do not contain uppercase. |
1722 |
|
-I ........ --IGNORE-CASE |
1723 |
|
Ignore case in all searches. |
1724 |
|
-j [N] .... --jump-target=[N] |
1725 |
|
Screen position of target lines. |
1726 |
|
-J ........ --status-column |
1727 |
|
Display a status column at left edge of screen. |
1728 |
|
-k [file] . --lesskey-file=[file] |
1729 |
|
Use a lesskey file. |
1730 |
|
-L ........ --no-lessopen |
1731 |
|
Ignore the LESSOPEN environment variable. |
1732 |
|
-m -M .... --long-prompt --LONG-PROMPT |
1733 |
|
Set prompt style. |
1734 |
|
-n -N .... --line-numbers --LINE-NUMBERS |
1735 |
|
Don't use line numbers. |
1736 |
|
-o [file] . --log-file=[file] |
1737 |
|
Copy to log file (standard input only). |
1738 |
|
-O [file] . --LOG-FILE=[file] |
1739 |
|
Copy to log file (unconditionally overwrite). |
1740 |
|
-p [pattern] --pattern=[pattern] |
1741 |
|
Start at pattern (from command line). |
1742 |
|
-P [prompt] --prompt=[prompt] |
1743 |
|
Define new prompt. |
1744 |
|
-q -Q .... --quiet --QUIET --silent --SILENT |
1745 |
|
Quiet the terminal bell. |
1746 |
|
-r -R .... --raw-control-chars --RAW-CONTROL-CHARS |
1747 |
|
Output "raw" control characters. |
1748 |
|
-s ........ --squeeze-blank-lines |
1749 |
|
Squeeze multiple blank lines. |
1750 |
|
-S ........ --chop-long-lines |
1751 |
|
Chop long lines. |
1752 |
|
-t [tag] .. --tag=[tag] |
1753 |
|
Find a tag. |
1754 |
|
-T [tagsfile] --tag-file=[tagsfile] |
1755 |
|
Use an alternate tags file. |
1756 |
|
-u -U .... --underline-special --UNDERLINE-SPECIAL |
1757 |
|
Change handling of backspaces. |
1758 |
|
-V ........ --version |
1759 |
|
Display the version number of "less". |
1760 |
|
-w ........ --hilite-unread |
1761 |
|
Highlight first new line after forward-screen. |
1762 |
|
-W ........ --HILITE-UNREAD |
1763 |
|
Highlight first new line after any forward movement. |
1764 |
|
-x [N[,...]] --tabs=[N[,...]] |
1765 |
|
Set tab stops. |
1766 |
|
-X ........ --no-init |
1767 |
|
Don't use termcap init/deinit strings. |
1768 |
|
--no-keypad |
1769 |
|
Don't use termcap keypad init/deinit strings. |
1770 |
|
-y [N] .... --max-forw-scroll=[N] |
1771 |
|
Forward scroll limit. |
1772 |
|
-z [N] .... --window=[N] |
1773 |
|
Set size of window. |
1774 |
|
-" [c[c]] . --quotes=[c[c]] |
1775 |
|
Set shell quote characters. |
1776 |
|
-~ ........ --tilde |
1777 |
|
Don't display tildes after end of file. |
1778 |
|
-# [N] .... --shift=[N] |
1779 |
|
Horizontal scroll amount (0 = one half screen width) |
1780 |
|
|
1781 |
|
--------------------------------------------------------------------------- |
1782 |
|
LINE EDITING |
1783 |
|
These keys can be used to edit text being entered |
1784 |
|
on the "command line" at the bottom of the screen. |
1785 |
|
RightArrow ESC-l Move cursor right one character. |
1786 |
|
LeftArrow ESC-h Move cursor left one character. |
1787 |
|
CNTL-RightArrow ESC-RightArrow ESC-w Move cursor right one word. |
1788 |
|
CNTL-LeftArrow ESC-LeftArrow ESC-b Move cursor left one word. |
1789 |
|
HOME ESC-0 Move cursor to start of line. |
1790 |
|
END ESC-$ Move cursor to end of line. |
1791 |
|
BACKSPACE Delete char to left of cursor. |
1792 |
|
DELETE ESC-x Delete char under cursor. |
1793 |
|
CNTL-BACKSPACE ESC-BACKSPACE Delete word to left of cursor. |
1794 |
|
CNTL-DELETE ESC-DELETE ESC-X Delete word under cursor. |
1795 |
|
CNTL-U ESC (MS-DOS only) Delete entire line. |
1796 |
|
UpArrow ESC-k Retrieve previous command line. |
1797 |
|
DownArrow ESC-j Retrieve next command line. |
1798 |
|
TAB Complete filename & cycle. |
1799 |
|
SHIFT-TAB ESC-TAB Complete filename & reverse cycle. |
1800 |
|
CNTL-L Complete filename, list all. |
1801 |
|
*/ |