Annotation of /tags/mkinitrd-6_3_1/busybox/miscutils/less.c
Parent Directory | Revision Log
Revision 1123 -
(hide annotations)
(download)
Wed Aug 18 21:56:57 2010 UTC (14 years, 1 month ago) by niro
Original Path: trunk/mkinitrd-magellan/busybox/miscutils/less.c
File MIME type: text/plain
File size: 47631 byte(s)
Wed Aug 18 21:56:57 2010 UTC (14 years, 1 month ago) by niro
Original Path: trunk/mkinitrd-magellan/busybox/miscutils/less.c
File MIME type: text/plain
File size: 47631 byte(s)
-updated to busybox-1.17.1
1 | niro | 532 | /* vi: set sw=4 ts=4: */ |
2 | /* | ||
3 | * Mini less implementation for busybox | ||
4 | * | ||
5 | * Copyright (C) 2005 by Rob Sullivan <cogito.ergo.cogito@gmail.com> | ||
6 | * | ||
7 | * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. | ||
8 | */ | ||
9 | |||
10 | /* | ||
11 | * TODO: | ||
12 | * - Add more regular expression support - search modifiers, certain matches, etc. | ||
13 | * - Add more complex bracket searching - currently, nested brackets are | ||
14 | * not considered. | ||
15 | * - Add support for "F" as an input. This causes less to act in | ||
16 | * a similar way to tail -f. | ||
17 | * - Allow horizontal scrolling. | ||
18 | * | ||
19 | * Notes: | ||
20 | * - the inp file pointer is used so that keyboard input works after | ||
21 | * redirected input has been read from stdin | ||
22 | */ | ||
23 | |||
24 | niro | 816 | #include <sched.h> /* sched_yield() */ |
25 | |||
26 | #include "libbb.h" | ||
27 | niro | 532 | #if ENABLE_FEATURE_LESS_REGEXP |
28 | #include "xregex.h" | ||
29 | #endif | ||
30 | |||
31 | /* The escape codes for highlighted and normal text */ | ||
32 | niro | 1123 | #define HIGHLIGHT "\033[7m" |
33 | #define NORMAL "\033[0m" | ||
34 | /* The escape code to home and clear to the end of screen */ | ||
35 | #define CLEAR "\033[H\033[J" | ||
36 | /* The escape code to clear to the end of line */ | ||
37 | niro | 532 | #define CLEAR_2_EOL "\033[K" |
38 | |||
39 | enum { | ||
40 | /* Absolute max of lines eaten */ | ||
41 | MAXLINES = CONFIG_FEATURE_LESS_MAXLINES, | ||
42 | /* This many "after the end" lines we will show (at max) */ | ||
43 | TILDES = 1, | ||
44 | }; | ||
45 | |||
46 | /* Command line options */ | ||
47 | enum { | ||
48 | niro | 816 | FLAG_E = 1 << 0, |
49 | niro | 532 | FLAG_M = 1 << 1, |
50 | FLAG_m = 1 << 2, | ||
51 | FLAG_N = 1 << 3, | ||
52 | FLAG_TILDE = 1 << 4, | ||
53 | niro | 816 | FLAG_I = 1 << 5, |
54 | FLAG_S = (1 << 6) * ENABLE_FEATURE_LESS_DASHCMD, | ||
55 | niro | 532 | /* hijack command line options variable for internal state vars */ |
56 | LESS_STATE_MATCH_BACKWARDS = 1 << 15, | ||
57 | }; | ||
58 | |||
59 | niro | 816 | #if !ENABLE_FEATURE_LESS_REGEXP |
60 | enum { pattern_valid = 0 }; | ||
61 | #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 | niro | 532 | #if ENABLE_FEATURE_LESS_MARKS |
87 | niro | 816 | unsigned num_marks; |
88 | unsigned mark_lines[15][2]; | ||
89 | niro | 532 | #endif |
90 | #if ENABLE_FEATURE_LESS_REGEXP | ||
91 | niro | 816 | unsigned *match_lines; |
92 | int match_pos; /* signed! */ | ||
93 | int wanted_match; /* signed! */ | ||
94 | int num_matches; | ||
95 | regex_t pattern; | ||
96 | smallint pattern_valid; | ||
97 | niro | 532 | #endif |
98 | niro | 816 | smallint terminated; |
99 | struct termios term_orig, term_less; | ||
100 | char kbd_input[KEYCODE_BUFFER_SIZE]; | ||
101 | }; | ||
102 | #define G (*ptr_to_globals) | ||
103 | #define cur_fline (G.cur_fline ) | ||
104 | #define kbd_fd (G.kbd_fd ) | ||
105 | #define less_gets_pos (G.less_gets_pos ) | ||
106 | #define last_line_pos (G.last_line_pos ) | ||
107 | #define max_fline (G.max_fline ) | ||
108 | #define max_lineno (G.max_lineno ) | ||
109 | #define max_displayed_line (G.max_displayed_line) | ||
110 | #define width (G.width ) | ||
111 | #define winch_counter (G.winch_counter ) | ||
112 | /* This one is 100% not cached by compiler on read access */ | ||
113 | #define WINCH_COUNTER (*(volatile unsigned *)&winch_counter) | ||
114 | #define eof_error (G.eof_error ) | ||
115 | #define readpos (G.readpos ) | ||
116 | #define readeof (G.readeof ) | ||
117 | #define buffer (G.buffer ) | ||
118 | #define flines (G.flines ) | ||
119 | #define empty_line_marker (G.empty_line_marker ) | ||
120 | #define num_files (G.num_files ) | ||
121 | #define current_file (G.current_file ) | ||
122 | #define filename (G.filename ) | ||
123 | #define files (G.files ) | ||
124 | #define num_marks (G.num_marks ) | ||
125 | #define mark_lines (G.mark_lines ) | ||
126 | #if ENABLE_FEATURE_LESS_REGEXP | ||
127 | #define match_lines (G.match_lines ) | ||
128 | #define match_pos (G.match_pos ) | ||
129 | #define num_matches (G.num_matches ) | ||
130 | #define wanted_match (G.wanted_match ) | ||
131 | #define pattern (G.pattern ) | ||
132 | #define pattern_valid (G.pattern_valid ) | ||
133 | #endif | ||
134 | #define terminated (G.terminated ) | ||
135 | #define term_orig (G.term_orig ) | ||
136 | #define term_less (G.term_less ) | ||
137 | #define kbd_input (G.kbd_input ) | ||
138 | #define INIT_G() do { \ | ||
139 | SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ | ||
140 | less_gets_pos = -1; \ | ||
141 | empty_line_marker = "~"; \ | ||
142 | num_files = 1; \ | ||
143 | current_file = 1; \ | ||
144 | eof_error = 1; \ | ||
145 | terminated = 1; \ | ||
146 | niro | 984 | IF_FEATURE_LESS_REGEXP(wanted_match = -1;) \ |
147 | niro | 816 | } while (0) |
148 | niro | 532 | |
149 | niro | 816 | /* flines[] are lines read from stdin, each in malloc'ed buffer. |
150 | * Line numbers are stored as uint32_t prepended to each line. | ||
151 | * Pointer is adjusted so that flines[i] points directly past | ||
152 | * line number. Accesor: */ | ||
153 | #define MEMPTR(p) ((char*)(p) - 4) | ||
154 | #define LINENO(p) (*(uint32_t*)((p) - 4)) | ||
155 | niro | 532 | |
156 | |||
157 | /* Reset terminal input to normal */ | ||
158 | static void set_tty_cooked(void) | ||
159 | { | ||
160 | niro | 984 | fflush_all(); |
161 | niro | 532 | tcsetattr(kbd_fd, TCSANOW, &term_orig); |
162 | } | ||
163 | |||
164 | /* Move the cursor to a position (x,y), where (0,0) is the | ||
165 | top-left corner of the console */ | ||
166 | static void move_cursor(int line, int row) | ||
167 | { | ||
168 | printf("\033[%u;%uH", line, row); | ||
169 | } | ||
170 | |||
171 | static void clear_line(void) | ||
172 | { | ||
173 | printf("\033[%u;0H" CLEAR_2_EOL, max_displayed_line + 2); | ||
174 | } | ||
175 | |||
176 | static void print_hilite(const char *str) | ||
177 | { | ||
178 | printf(HIGHLIGHT"%s"NORMAL, str); | ||
179 | } | ||
180 | |||
181 | static void print_statusline(const char *str) | ||
182 | { | ||
183 | clear_line(); | ||
184 | printf(HIGHLIGHT"%.*s"NORMAL, width - 1, str); | ||
185 | } | ||
186 | |||
187 | niro | 816 | /* Exit the program gracefully */ |
188 | static void less_exit(int code) | ||
189 | { | ||
190 | set_tty_cooked(); | ||
191 | clear_line(); | ||
192 | if (code < 0) | ||
193 | kill_myself_with_sig(- code); /* does not return */ | ||
194 | exit(code); | ||
195 | } | ||
196 | |||
197 | #if (ENABLE_FEATURE_LESS_DASHCMD && ENABLE_FEATURE_LESS_LINENUMS) \ | ||
198 | || ENABLE_FEATURE_LESS_WINCH | ||
199 | static void re_wrap(void) | ||
200 | { | ||
201 | int w = width; | ||
202 | int new_line_pos; | ||
203 | int src_idx; | ||
204 | int dst_idx; | ||
205 | int new_cur_fline = 0; | ||
206 | uint32_t lineno; | ||
207 | char linebuf[w + 1]; | ||
208 | const char **old_flines = flines; | ||
209 | const char *s; | ||
210 | char **new_flines = NULL; | ||
211 | char *d; | ||
212 | |||
213 | if (option_mask32 & FLAG_N) | ||
214 | w -= 8; | ||
215 | |||
216 | src_idx = 0; | ||
217 | dst_idx = 0; | ||
218 | s = old_flines[0]; | ||
219 | lineno = LINENO(s); | ||
220 | d = linebuf; | ||
221 | new_line_pos = 0; | ||
222 | while (1) { | ||
223 | *d = *s; | ||
224 | if (*d != '\0') { | ||
225 | new_line_pos++; | ||
226 | if (*d == '\t') /* tab */ | ||
227 | new_line_pos += 7; | ||
228 | s++; | ||
229 | d++; | ||
230 | if (new_line_pos >= w) { | ||
231 | int sz; | ||
232 | /* new line is full, create next one */ | ||
233 | *d = '\0'; | ||
234 | next_new: | ||
235 | sz = (d - linebuf) + 1; /* + 1: NUL */ | ||
236 | d = ((char*)xmalloc(sz + 4)) + 4; | ||
237 | LINENO(d) = lineno; | ||
238 | memcpy(d, linebuf, sz); | ||
239 | new_flines = xrealloc_vector(new_flines, 8, dst_idx); | ||
240 | new_flines[dst_idx] = d; | ||
241 | dst_idx++; | ||
242 | if (new_line_pos < w) { | ||
243 | /* if we came here thru "goto next_new" */ | ||
244 | if (src_idx > max_fline) | ||
245 | break; | ||
246 | lineno = LINENO(s); | ||
247 | } | ||
248 | d = linebuf; | ||
249 | new_line_pos = 0; | ||
250 | } | ||
251 | continue; | ||
252 | } | ||
253 | /* *d == NUL: old line ended, go to next old one */ | ||
254 | free(MEMPTR(old_flines[src_idx])); | ||
255 | /* btw, convert cur_fline... */ | ||
256 | if (cur_fline == src_idx) | ||
257 | new_cur_fline = dst_idx; | ||
258 | src_idx++; | ||
259 | /* no more lines? finish last new line (and exit the loop) */ | ||
260 | if (src_idx > max_fline) | ||
261 | goto next_new; | ||
262 | s = old_flines[src_idx]; | ||
263 | if (lineno != LINENO(s)) { | ||
264 | /* this is not a continuation line! | ||
265 | * create next _new_ line too */ | ||
266 | goto next_new; | ||
267 | } | ||
268 | } | ||
269 | |||
270 | free(old_flines); | ||
271 | flines = (const char **)new_flines; | ||
272 | |||
273 | max_fline = dst_idx - 1; | ||
274 | last_line_pos = new_line_pos; | ||
275 | cur_fline = new_cur_fline; | ||
276 | /* max_lineno is screen-size independent */ | ||
277 | niro | 532 | #if ENABLE_FEATURE_LESS_REGEXP |
278 | niro | 816 | pattern_valid = 0; |
279 | #endif | ||
280 | } | ||
281 | #endif | ||
282 | |||
283 | #if ENABLE_FEATURE_LESS_REGEXP | ||
284 | niro | 532 | static void fill_match_lines(unsigned pos); |
285 | #else | ||
286 | #define fill_match_lines(pos) ((void)0) | ||
287 | #endif | ||
288 | |||
289 | niro | 816 | /* Devilishly complex routine. |
290 | * | ||
291 | * Has to deal with EOF and EPIPE on input, | ||
292 | * with line wrapping, with last line not ending in '\n' | ||
293 | * (possibly not ending YET!), with backspace and tabs. | ||
294 | * It reads input again if last time we got an EOF (thus supporting | ||
295 | * growing files) or EPIPE (watching output of slow process like make). | ||
296 | * | ||
297 | * Variables used: | ||
298 | * flines[] - array of lines already read. Linewrap may cause | ||
299 | * one source file line to occupy several flines[n]. | ||
300 | * flines[max_fline] - last line, possibly incomplete. | ||
301 | * terminated - 1 if flines[max_fline] is 'terminated' | ||
302 | * (if there was '\n' [which isn't stored itself, we just remember | ||
303 | * that it was seen]) | ||
304 | * max_lineno - last line's number, this one doesn't increment | ||
305 | * on line wrap, only on "real" new lines. | ||
306 | * readbuf[0..readeof-1] - small preliminary buffer. | ||
307 | * readbuf[readpos] - next character to add to current line. | ||
308 | * last_line_pos - screen line position of next char to be read | ||
309 | * (takes into account tabs and backspaces) | ||
310 | * eof_error - < 0 error, == 0 EOF, > 0 not EOF/error | ||
311 | */ | ||
312 | niro | 532 | static void read_lines(void) |
313 | { | ||
314 | #define readbuf bb_common_bufsiz1 | ||
315 | char *current_line, *p; | ||
316 | int w = width; | ||
317 | char last_terminated = terminated; | ||
318 | niro | 816 | #if ENABLE_FEATURE_LESS_REGEXP |
319 | unsigned old_max_fline = max_fline; | ||
320 | time_t last_time = 0; | ||
321 | unsigned seconds_p1 = 3; /* seconds_to_loop + 1 */ | ||
322 | #endif | ||
323 | niro | 532 | |
324 | if (option_mask32 & FLAG_N) | ||
325 | w -= 8; | ||
326 | |||
327 | niro | 984 | IF_FEATURE_LESS_REGEXP(again0:) |
328 | niro | 816 | |
329 | p = current_line = ((char*)xmalloc(w + 4)) + 4; | ||
330 | niro | 532 | max_fline += last_terminated; |
331 | if (!last_terminated) { | ||
332 | const char *cp = flines[max_fline]; | ||
333 | niro | 816 | strcpy(p, cp); |
334 | niro | 532 | p += strlen(current_line); |
335 | niro | 816 | free(MEMPTR(flines[max_fline])); |
336 | /* last_line_pos is still valid from previous read_lines() */ | ||
337 | niro | 532 | } else { |
338 | niro | 816 | last_line_pos = 0; |
339 | niro | 532 | } |
340 | |||
341 | niro | 816 | while (1) { /* read lines until we reach cur_fline or wanted_match */ |
342 | niro | 532 | *p = '\0'; |
343 | terminated = 0; | ||
344 | niro | 816 | while (1) { /* read chars until we have a line */ |
345 | niro | 532 | char c; |
346 | niro | 816 | /* if no unprocessed chars left, eat more */ |
347 | niro | 532 | if (readpos >= readeof) { |
348 | ndelay_on(0); | ||
349 | niro | 816 | eof_error = safe_read(STDIN_FILENO, readbuf, sizeof(readbuf)); |
350 | niro | 532 | ndelay_off(0); |
351 | readpos = 0; | ||
352 | readeof = eof_error; | ||
353 | niro | 816 | if (eof_error <= 0) |
354 | niro | 532 | goto reached_eof; |
355 | } | ||
356 | c = readbuf[readpos]; | ||
357 | /* backspace? [needed for manpages] */ | ||
358 | /* <tab><bs> is (a) insane and */ | ||
359 | /* (b) harder to do correctly, so we refuse to do it */ | ||
360 | niro | 816 | if (c == '\x8' && last_line_pos && p[-1] != '\t') { |
361 | niro | 532 | readpos++; /* eat it */ |
362 | niro | 816 | last_line_pos--; |
363 | /* was buggy (p could end up <= current_line)... */ | ||
364 | niro | 532 | *--p = '\0'; |
365 | continue; | ||
366 | } | ||
367 | niro | 816 | { |
368 | size_t new_last_line_pos = last_line_pos + 1; | ||
369 | if (c == '\t') { | ||
370 | new_last_line_pos += 7; | ||
371 | new_last_line_pos &= (~7); | ||
372 | } | ||
373 | if ((int)new_last_line_pos >= w) | ||
374 | break; | ||
375 | last_line_pos = new_last_line_pos; | ||
376 | } | ||
377 | niro | 532 | /* ok, we will eat this char */ |
378 | readpos++; | ||
379 | niro | 816 | if (c == '\n') { |
380 | terminated = 1; | ||
381 | last_line_pos = 0; | ||
382 | break; | ||
383 | } | ||
384 | niro | 532 | /* NUL is substituted by '\n'! */ |
385 | if (c == '\0') c = '\n'; | ||
386 | *p++ = c; | ||
387 | *p = '\0'; | ||
388 | niro | 816 | } /* end of "read chars until we have a line" loop */ |
389 | niro | 532 | /* Corner case: linewrap with only "" wrapping to next line */ |
390 | /* Looks ugly on screen, so we do not store this empty line */ | ||
391 | if (!last_terminated && !current_line[0]) { | ||
392 | last_terminated = 1; | ||
393 | max_lineno++; | ||
394 | niro | 816 | continue; |
395 | niro | 532 | } |
396 | reached_eof: | ||
397 | last_terminated = terminated; | ||
398 | niro | 816 | flines = xrealloc_vector(flines, 8, max_fline); |
399 | |||
400 | flines[max_fline] = (char*)xrealloc(MEMPTR(current_line), strlen(current_line) + 1 + 4) + 4; | ||
401 | LINENO(flines[max_fline]) = max_lineno; | ||
402 | if (terminated) | ||
403 | max_lineno++; | ||
404 | |||
405 | if (max_fline >= MAXLINES) { | ||
406 | eof_error = 0; /* Pretend we saw EOF */ | ||
407 | break; | ||
408 | niro | 532 | } |
409 | niro | 816 | if (!(option_mask32 & FLAG_S) |
410 | ? (max_fline > cur_fline + max_displayed_line) | ||
411 | : (max_fline >= cur_fline | ||
412 | && max_lineno > LINENO(flines[cur_fline]) + max_displayed_line) | ||
413 | ) { | ||
414 | #if !ENABLE_FEATURE_LESS_REGEXP | ||
415 | niro | 532 | break; |
416 | niro | 816 | #else |
417 | if (wanted_match >= num_matches) { /* goto_match called us */ | ||
418 | fill_match_lines(old_max_fline); | ||
419 | old_max_fline = max_fline; | ||
420 | } | ||
421 | if (wanted_match < num_matches) | ||
422 | break; | ||
423 | #endif | ||
424 | } | ||
425 | niro | 532 | if (eof_error <= 0) { |
426 | niro | 816 | if (eof_error < 0) { |
427 | if (errno == EAGAIN) { | ||
428 | /* not yet eof or error, reset flag (or else | ||
429 | * we will hog CPU - select() will return | ||
430 | * immediately */ | ||
431 | eof_error = 1; | ||
432 | } else { | ||
433 | niro | 1123 | print_statusline(bb_msg_read_error); |
434 | niro | 816 | } |
435 | niro | 532 | } |
436 | niro | 816 | #if !ENABLE_FEATURE_LESS_REGEXP |
437 | niro | 532 | break; |
438 | niro | 816 | #else |
439 | if (wanted_match < num_matches) { | ||
440 | break; | ||
441 | } else { /* goto_match called us */ | ||
442 | time_t t = time(NULL); | ||
443 | if (t != last_time) { | ||
444 | last_time = t; | ||
445 | if (--seconds_p1 == 0) | ||
446 | break; | ||
447 | } | ||
448 | sched_yield(); | ||
449 | goto again0; /* go loop again (max 2 seconds) */ | ||
450 | } | ||
451 | #endif | ||
452 | niro | 532 | } |
453 | max_fline++; | ||
454 | niro | 816 | current_line = ((char*)xmalloc(w + 4)) + 4; |
455 | niro | 532 | p = current_line; |
456 | niro | 816 | last_line_pos = 0; |
457 | } /* end of "read lines until we reach cur_fline" loop */ | ||
458 | niro | 532 | fill_match_lines(old_max_fline); |
459 | niro | 816 | #if ENABLE_FEATURE_LESS_REGEXP |
460 | /* prevent us from being stuck in search for a match */ | ||
461 | wanted_match = -1; | ||
462 | #endif | ||
463 | niro | 532 | #undef readbuf |
464 | } | ||
465 | |||
466 | #if ENABLE_FEATURE_LESS_FLAGS | ||
467 | /* Interestingly, writing calc_percent as a function saves around 32 bytes | ||
468 | * on my build. */ | ||
469 | static int calc_percent(void) | ||
470 | { | ||
471 | unsigned p = (100 * (cur_fline+max_displayed_line+1) + max_fline/2) / (max_fline+1); | ||
472 | return p <= 100 ? p : 100; | ||
473 | } | ||
474 | |||
475 | /* Print a status line if -M was specified */ | ||
476 | static void m_status_print(void) | ||
477 | { | ||
478 | int percentage; | ||
479 | |||
480 | niro | 816 | if (less_gets_pos >= 0) /* don't touch statusline while input is done! */ |
481 | return; | ||
482 | |||
483 | niro | 532 | clear_line(); |
484 | printf(HIGHLIGHT"%s", filename); | ||
485 | if (num_files > 1) | ||
486 | printf(" (file %i of %i)", current_file, num_files); | ||
487 | printf(" lines %i-%i/%i ", | ||
488 | cur_fline + 1, cur_fline + max_displayed_line + 1, | ||
489 | max_fline + 1); | ||
490 | niro | 816 | if (cur_fline >= (int)(max_fline - max_displayed_line)) { |
491 | niro | 532 | printf("(END)"NORMAL); |
492 | if (num_files > 1 && current_file != num_files) | ||
493 | printf(HIGHLIGHT" - next: %s"NORMAL, files[current_file]); | ||
494 | return; | ||
495 | } | ||
496 | percentage = calc_percent(); | ||
497 | printf("%i%%"NORMAL, percentage); | ||
498 | } | ||
499 | #endif | ||
500 | |||
501 | /* Print the status line */ | ||
502 | static void status_print(void) | ||
503 | { | ||
504 | const char *p; | ||
505 | |||
506 | niro | 816 | if (less_gets_pos >= 0) /* don't touch statusline while input is done! */ |
507 | return; | ||
508 | |||
509 | niro | 532 | /* Change the status if flags have been set */ |
510 | #if ENABLE_FEATURE_LESS_FLAGS | ||
511 | if (option_mask32 & (FLAG_M|FLAG_m)) { | ||
512 | m_status_print(); | ||
513 | return; | ||
514 | } | ||
515 | /* No flags set */ | ||
516 | #endif | ||
517 | |||
518 | clear_line(); | ||
519 | niro | 816 | if (cur_fline && cur_fline < (int)(max_fline - max_displayed_line)) { |
520 | bb_putchar(':'); | ||
521 | niro | 532 | return; |
522 | } | ||
523 | p = "(END)"; | ||
524 | if (!cur_fline) | ||
525 | p = filename; | ||
526 | if (num_files > 1) { | ||
527 | printf(HIGHLIGHT"%s (file %i of %i)"NORMAL, | ||
528 | p, current_file, num_files); | ||
529 | return; | ||
530 | } | ||
531 | print_hilite(p); | ||
532 | } | ||
533 | |||
534 | niro | 816 | static void cap_cur_fline(int nlines) |
535 | { | ||
536 | int diff; | ||
537 | if (cur_fline < 0) | ||
538 | cur_fline = 0; | ||
539 | if (cur_fline + max_displayed_line > max_fline + TILDES) { | ||
540 | cur_fline -= nlines; | ||
541 | if (cur_fline < 0) | ||
542 | cur_fline = 0; | ||
543 | diff = max_fline - (cur_fline + max_displayed_line) + TILDES; | ||
544 | /* As the number of lines requested was too large, we just move | ||
545 | niro | 1123 | * to the end of the file */ |
546 | niro | 816 | if (diff > 0) |
547 | cur_fline += diff; | ||
548 | } | ||
549 | } | ||
550 | |||
551 | static const char controls[] ALIGN1 = | ||
552 | niro | 532 | /* NUL: never encountered; TAB: not converted */ |
553 | /**/"\x01\x02\x03\x04\x05\x06\x07\x08" "\x0a\x0b\x0c\x0d\x0e\x0f" | ||
554 | "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" | ||
555 | "\x7f\x9b"; /* DEL and infamous Meta-ESC :( */ | ||
556 | niro | 816 | static const char ctrlconv[] ALIGN1 = |
557 | niro | 1123 | /* why 40 instead of 4a below? - it is a replacement for '\n'. |
558 | * '\n' is a former NUL - we subst it with @, not J */ | ||
559 | niro | 532 | "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x40\x4b\x4c\x4d\x4e\x4f" |
560 | "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"; | ||
561 | |||
562 | niro | 816 | static void lineno_str(char *nbuf9, const char *line) |
563 | { | ||
564 | nbuf9[0] = '\0'; | ||
565 | if (option_mask32 & FLAG_N) { | ||
566 | const char *fmt; | ||
567 | unsigned n; | ||
568 | |||
569 | if (line == empty_line_marker) { | ||
570 | memset(nbuf9, ' ', 8); | ||
571 | nbuf9[8] = '\0'; | ||
572 | return; | ||
573 | } | ||
574 | /* Width of 7 preserves tab spacing in the text */ | ||
575 | fmt = "%7u "; | ||
576 | n = LINENO(line) + 1; | ||
577 | if (n > 9999999) { | ||
578 | n %= 10000000; | ||
579 | fmt = "%07u "; | ||
580 | } | ||
581 | sprintf(nbuf9, fmt, n); | ||
582 | } | ||
583 | } | ||
584 | |||
585 | |||
586 | niro | 532 | #if ENABLE_FEATURE_LESS_REGEXP |
587 | static void print_found(const char *line) | ||
588 | { | ||
589 | int match_status; | ||
590 | int eflags; | ||
591 | char *growline; | ||
592 | regmatch_t match_structs; | ||
593 | |||
594 | char buf[width]; | ||
595 | niro | 816 | char nbuf9[9]; |
596 | niro | 532 | const char *str = line; |
597 | char *p = buf; | ||
598 | size_t n; | ||
599 | |||
600 | while (*str) { | ||
601 | n = strcspn(str, controls); | ||
602 | if (n) { | ||
603 | if (!str[n]) break; | ||
604 | memcpy(p, str, n); | ||
605 | p += n; | ||
606 | str += n; | ||
607 | } | ||
608 | n = strspn(str, controls); | ||
609 | memset(p, '.', n); | ||
610 | p += n; | ||
611 | str += n; | ||
612 | } | ||
613 | strcpy(p, str); | ||
614 | |||
615 | /* buf[] holds quarantined version of str */ | ||
616 | |||
617 | /* Each part of the line that matches has the HIGHLIGHT | ||
618 | and NORMAL escape sequences placed around it. | ||
619 | NB: we regex against line, but insert text | ||
620 | from quarantined copy (buf[]) */ | ||
621 | str = buf; | ||
622 | growline = NULL; | ||
623 | eflags = 0; | ||
624 | goto start; | ||
625 | |||
626 | while (match_status == 0) { | ||
627 | char *new = xasprintf("%s%.*s"HIGHLIGHT"%.*s"NORMAL, | ||
628 | niro | 984 | growline ? growline : "", |
629 | niro | 532 | match_structs.rm_so, str, |
630 | match_structs.rm_eo - match_structs.rm_so, | ||
631 | str + match_structs.rm_so); | ||
632 | niro | 816 | free(growline); |
633 | growline = new; | ||
634 | niro | 532 | str += match_structs.rm_eo; |
635 | line += match_structs.rm_eo; | ||
636 | eflags = REG_NOTBOL; | ||
637 | start: | ||
638 | /* Most of the time doesn't find the regex, optimize for that */ | ||
639 | match_status = regexec(&pattern, line, 1, &match_structs, eflags); | ||
640 | niro | 816 | /* if even "" matches, treat it as "not a match" */ |
641 | if (match_structs.rm_so >= match_structs.rm_eo) | ||
642 | match_status = 1; | ||
643 | niro | 532 | } |
644 | |||
645 | niro | 816 | lineno_str(nbuf9, line); |
646 | niro | 532 | if (!growline) { |
647 | niro | 816 | printf(CLEAR_2_EOL"%s%s\n", nbuf9, str); |
648 | niro | 532 | return; |
649 | } | ||
650 | niro | 816 | printf(CLEAR_2_EOL"%s%s%s\n", nbuf9, growline, str); |
651 | niro | 532 | free(growline); |
652 | } | ||
653 | #else | ||
654 | void print_found(const char *line); | ||
655 | #endif | ||
656 | |||
657 | static void print_ascii(const char *str) | ||
658 | { | ||
659 | char buf[width]; | ||
660 | niro | 816 | char nbuf9[9]; |
661 | niro | 532 | char *p; |
662 | size_t n; | ||
663 | |||
664 | niro | 816 | lineno_str(nbuf9, str); |
665 | printf(CLEAR_2_EOL"%s", nbuf9); | ||
666 | |||
667 | niro | 532 | while (*str) { |
668 | n = strcspn(str, controls); | ||
669 | if (n) { | ||
670 | if (!str[n]) break; | ||
671 | printf("%.*s", (int) n, str); | ||
672 | str += n; | ||
673 | } | ||
674 | n = strspn(str, controls); | ||
675 | p = buf; | ||
676 | do { | ||
677 | if (*str == 0x7f) | ||
678 | *p++ = '?'; | ||
679 | else if (*str == (char)0x9b) | ||
680 | /* VT100's CSI, aka Meta-ESC. Who's inventor? */ | ||
681 | /* I want to know who committed this sin */ | ||
682 | *p++ = '{'; | ||
683 | else | ||
684 | *p++ = ctrlconv[(unsigned char)*str]; | ||
685 | str++; | ||
686 | } while (--n); | ||
687 | *p = '\0'; | ||
688 | print_hilite(buf); | ||
689 | } | ||
690 | puts(str); | ||
691 | } | ||
692 | |||
693 | /* Print the buffer */ | ||
694 | static void buffer_print(void) | ||
695 | { | ||
696 | niro | 816 | unsigned i; |
697 | niro | 532 | |
698 | move_cursor(0, 0); | ||
699 | for (i = 0; i <= max_displayed_line; i++) | ||
700 | if (pattern_valid) | ||
701 | print_found(buffer[i]); | ||
702 | else | ||
703 | print_ascii(buffer[i]); | ||
704 | status_print(); | ||
705 | } | ||
706 | |||
707 | static void buffer_fill_and_print(void) | ||
708 | { | ||
709 | niro | 816 | unsigned i; |
710 | #if ENABLE_FEATURE_LESS_DASHCMD | ||
711 | int fpos = cur_fline; | ||
712 | |||
713 | if (option_mask32 & FLAG_S) { | ||
714 | /* Go back to the beginning of this line */ | ||
715 | while (fpos && LINENO(flines[fpos]) == LINENO(flines[fpos-1])) | ||
716 | fpos--; | ||
717 | } | ||
718 | |||
719 | i = 0; | ||
720 | while (i <= max_displayed_line && fpos <= max_fline) { | ||
721 | int lineno = LINENO(flines[fpos]); | ||
722 | buffer[i] = flines[fpos]; | ||
723 | i++; | ||
724 | do { | ||
725 | fpos++; | ||
726 | } while ((fpos <= max_fline) | ||
727 | && (option_mask32 & FLAG_S) | ||
728 | && lineno == LINENO(flines[fpos]) | ||
729 | ); | ||
730 | } | ||
731 | #else | ||
732 | niro | 532 | for (i = 0; i <= max_displayed_line && cur_fline + i <= max_fline; i++) { |
733 | buffer[i] = flines[cur_fline + i]; | ||
734 | } | ||
735 | niro | 816 | #endif |
736 | niro | 532 | for (; i <= max_displayed_line; i++) { |
737 | buffer[i] = empty_line_marker; | ||
738 | } | ||
739 | buffer_print(); | ||
740 | } | ||
741 | |||
742 | /* Move the buffer up and down in the file in order to scroll */ | ||
743 | static void buffer_down(int nlines) | ||
744 | { | ||
745 | cur_fline += nlines; | ||
746 | read_lines(); | ||
747 | niro | 816 | cap_cur_fline(nlines); |
748 | niro | 532 | buffer_fill_and_print(); |
749 | } | ||
750 | |||
751 | static void buffer_up(int nlines) | ||
752 | { | ||
753 | cur_fline -= nlines; | ||
754 | if (cur_fline < 0) cur_fline = 0; | ||
755 | read_lines(); | ||
756 | buffer_fill_and_print(); | ||
757 | } | ||
758 | |||
759 | static void buffer_line(int linenum) | ||
760 | { | ||
761 | if (linenum < 0) | ||
762 | linenum = 0; | ||
763 | cur_fline = linenum; | ||
764 | read_lines(); | ||
765 | if (linenum + max_displayed_line > max_fline) | ||
766 | linenum = max_fline - max_displayed_line + TILDES; | ||
767 | niro | 816 | if (linenum < 0) |
768 | linenum = 0; | ||
769 | niro | 532 | cur_fline = linenum; |
770 | buffer_fill_and_print(); | ||
771 | } | ||
772 | |||
773 | static void open_file_and_read_lines(void) | ||
774 | { | ||
775 | if (filename) { | ||
776 | niro | 984 | xmove_fd(xopen(filename, O_RDONLY), STDIN_FILENO); |
777 | niro | 532 | } else { |
778 | /* "less" with no arguments in argv[] */ | ||
779 | /* For status line only */ | ||
780 | filename = xstrdup(bb_msg_standard_input); | ||
781 | } | ||
782 | readpos = 0; | ||
783 | readeof = 0; | ||
784 | niro | 816 | last_line_pos = 0; |
785 | niro | 532 | terminated = 1; |
786 | read_lines(); | ||
787 | } | ||
788 | |||
789 | /* Reinitialize everything for a new file - free the memory and start over */ | ||
790 | static void reinitialize(void) | ||
791 | { | ||
792 | niro | 816 | unsigned i; |
793 | niro | 532 | |
794 | if (flines) { | ||
795 | for (i = 0; i <= max_fline; i++) | ||
796 | niro | 816 | free(MEMPTR(flines[i])); |
797 | niro | 532 | free(flines); |
798 | flines = NULL; | ||
799 | } | ||
800 | |||
801 | max_fline = -1; | ||
802 | cur_fline = 0; | ||
803 | max_lineno = 0; | ||
804 | open_file_and_read_lines(); | ||
805 | buffer_fill_and_print(); | ||
806 | } | ||
807 | |||
808 | niro | 984 | static int getch_nowait(void) |
809 | niro | 532 | { |
810 | niro | 816 | int rd; |
811 | struct pollfd pfd[2]; | ||
812 | |||
813 | pfd[0].fd = STDIN_FILENO; | ||
814 | pfd[0].events = POLLIN; | ||
815 | pfd[1].fd = kbd_fd; | ||
816 | pfd[1].events = POLLIN; | ||
817 | niro | 532 | again: |
818 | niro | 816 | tcsetattr(kbd_fd, TCSANOW, &term_less); |
819 | /* NB: select/poll returns whenever read will not block. Therefore: | ||
820 | * if eof is reached, select/poll will return immediately | ||
821 | * because read will immediately return 0 bytes. | ||
822 | * Even if select/poll says that input is available, read CAN block | ||
823 | niro | 532 | * (switch fd into O_NONBLOCK'ed mode to avoid it) |
824 | */ | ||
825 | niro | 816 | rd = 1; |
826 | /* Are we interested in stdin? */ | ||
827 | //TODO: reuse code for determining this | ||
828 | if (!(option_mask32 & FLAG_S) | ||
829 | ? !(max_fline > cur_fline + max_displayed_line) | ||
830 | : !(max_fline >= cur_fline | ||
831 | && max_lineno > LINENO(flines[cur_fline]) + max_displayed_line) | ||
832 | niro | 532 | ) { |
833 | niro | 816 | if (eof_error > 0) /* did NOT reach eof yet */ |
834 | rd = 0; /* yes, we are interested in stdin */ | ||
835 | niro | 532 | } |
836 | niro | 816 | /* Position cursor if line input is done */ |
837 | if (less_gets_pos >= 0) | ||
838 | move_cursor(max_displayed_line + 2, less_gets_pos + 1); | ||
839 | niro | 984 | fflush_all(); |
840 | niro | 532 | |
841 | niro | 984 | if (kbd_input[0] == 0) { /* if nothing is buffered */ |
842 | niro | 816 | #if ENABLE_FEATURE_LESS_WINCH |
843 | while (1) { | ||
844 | int r; | ||
845 | /* NB: SIGWINCH interrupts poll() */ | ||
846 | r = poll(pfd + rd, 2 - rd, -1); | ||
847 | if (/*r < 0 && errno == EINTR &&*/ winch_counter) | ||
848 | return '\\'; /* anything which has no defined function */ | ||
849 | if (r) break; | ||
850 | } | ||
851 | #else | ||
852 | safe_poll(pfd + rd, 2 - rd, -1); | ||
853 | #endif | ||
854 | niro | 532 | } |
855 | niro | 816 | |
856 | /* We have kbd_fd in O_NONBLOCK mode, read inside read_key() | ||
857 | * would not block even if there is no input available */ | ||
858 | niro | 1123 | rd = read_key(kbd_fd, kbd_input, /*timeout off:*/ -2); |
859 | niro | 816 | if (rd == -1) { |
860 | if (errno == EAGAIN) { | ||
861 | /* No keyboard input available. Since poll() did return, | ||
862 | * we should have input on stdin */ | ||
863 | read_lines(); | ||
864 | buffer_fill_and_print(); | ||
865 | goto again; | ||
866 | } | ||
867 | /* EOF/error (ssh session got killed etc) */ | ||
868 | less_exit(0); | ||
869 | } | ||
870 | set_tty_cooked(); | ||
871 | return rd; | ||
872 | niro | 532 | } |
873 | |||
874 | niro | 984 | /* Grab a character from input without requiring the return key. |
875 | * May return KEYCODE_xxx values. | ||
876 | * Note that this function works best with raw input. */ | ||
877 | niro | 816 | static int less_getch(int pos) |
878 | niro | 532 | { |
879 | niro | 816 | int i; |
880 | |||
881 | niro | 532 | again: |
882 | niro | 816 | less_gets_pos = pos; |
883 | i = getch_nowait(); | ||
884 | less_gets_pos = -1; | ||
885 | niro | 532 | |
886 | niro | 816 | /* Discard Ctrl-something chars */ |
887 | if (i >= 0 && i < ' ' && i != 0x0d && i != 8) | ||
888 | goto again; | ||
889 | niro | 532 | return i; |
890 | } | ||
891 | |||
892 | static char* less_gets(int sz) | ||
893 | { | ||
894 | niro | 816 | int c; |
895 | unsigned i = 0; | ||
896 | niro | 532 | char *result = xzalloc(1); |
897 | niro | 816 | |
898 | niro | 532 | while (1) { |
899 | niro | 816 | c = '\0'; |
900 | less_gets_pos = sz + i; | ||
901 | c = getch_nowait(); | ||
902 | if (c == 0x0d) { | ||
903 | result[i] = '\0'; | ||
904 | less_gets_pos = -1; | ||
905 | niro | 532 | return result; |
906 | niro | 816 | } |
907 | niro | 532 | if (c == 0x7f) |
908 | c = 8; | ||
909 | if (c == 8 && i) { | ||
910 | printf("\x8 \x8"); | ||
911 | i--; | ||
912 | } | ||
913 | niro | 816 | if (c < ' ') /* filters out KEYCODE_xxx too (<0) */ |
914 | niro | 532 | continue; |
915 | if (i >= width - sz - 1) | ||
916 | continue; /* len limit */ | ||
917 | niro | 816 | bb_putchar(c); |
918 | niro | 532 | result[i++] = c; |
919 | result = xrealloc(result, i+1); | ||
920 | } | ||
921 | } | ||
922 | |||
923 | static void examine_file(void) | ||
924 | { | ||
925 | niro | 816 | char *new_fname; |
926 | |||
927 | niro | 532 | print_statusline("Examine: "); |
928 | niro | 816 | new_fname = less_gets(sizeof("Examine: ") - 1); |
929 | if (!new_fname[0]) { | ||
930 | status_print(); | ||
931 | err: | ||
932 | free(new_fname); | ||
933 | return; | ||
934 | } | ||
935 | if (access(new_fname, R_OK) != 0) { | ||
936 | print_statusline("Cannot read this file"); | ||
937 | goto err; | ||
938 | } | ||
939 | niro | 532 | free(filename); |
940 | niro | 816 | filename = new_fname; |
941 | niro | 532 | /* files start by = argv. why we assume that argv is infinitely long?? |
942 | files[num_files] = filename; | ||
943 | current_file = num_files + 1; | ||
944 | num_files++; */ | ||
945 | files[0] = filename; | ||
946 | num_files = current_file = 1; | ||
947 | reinitialize(); | ||
948 | } | ||
949 | |||
950 | /* This function changes the file currently being paged. direction can be one of the following: | ||
951 | * -1: go back one file | ||
952 | * 0: go to the first file | ||
953 | * 1: go forward one file */ | ||
954 | static void change_file(int direction) | ||
955 | { | ||
956 | if (current_file != ((direction > 0) ? num_files : 1)) { | ||
957 | current_file = direction ? current_file + direction : 1; | ||
958 | free(filename); | ||
959 | filename = xstrdup(files[current_file - 1]); | ||
960 | reinitialize(); | ||
961 | } else { | ||
962 | print_statusline(direction > 0 ? "No next file" : "No previous file"); | ||
963 | } | ||
964 | } | ||
965 | |||
966 | static void remove_current_file(void) | ||
967 | { | ||
968 | niro | 816 | unsigned i; |
969 | niro | 532 | |
970 | if (num_files < 2) | ||
971 | return; | ||
972 | |||
973 | if (current_file != 1) { | ||
974 | change_file(-1); | ||
975 | for (i = 3; i <= num_files; i++) | ||
976 | files[i - 2] = files[i - 1]; | ||
977 | num_files--; | ||
978 | } else { | ||
979 | change_file(1); | ||
980 | for (i = 2; i <= num_files; i++) | ||
981 | files[i - 2] = files[i - 1]; | ||
982 | num_files--; | ||
983 | current_file--; | ||
984 | } | ||
985 | } | ||
986 | |||
987 | static void colon_process(void) | ||
988 | { | ||
989 | int keypress; | ||
990 | |||
991 | /* Clear the current line and print a prompt */ | ||
992 | print_statusline(" :"); | ||
993 | |||
994 | niro | 816 | keypress = less_getch(2); |
995 | niro | 532 | switch (keypress) { |
996 | case 'd': | ||
997 | remove_current_file(); | ||
998 | break; | ||
999 | case 'e': | ||
1000 | examine_file(); | ||
1001 | break; | ||
1002 | #if ENABLE_FEATURE_LESS_FLAGS | ||
1003 | case 'f': | ||
1004 | m_status_print(); | ||
1005 | break; | ||
1006 | #endif | ||
1007 | case 'n': | ||
1008 | change_file(1); | ||
1009 | break; | ||
1010 | case 'p': | ||
1011 | change_file(-1); | ||
1012 | break; | ||
1013 | case 'q': | ||
1014 | niro | 816 | less_exit(EXIT_SUCCESS); |
1015 | niro | 532 | break; |
1016 | case 'x': | ||
1017 | change_file(0); | ||
1018 | break; | ||
1019 | } | ||
1020 | } | ||
1021 | |||
1022 | #if ENABLE_FEATURE_LESS_REGEXP | ||
1023 | niro | 816 | static void normalize_match_pos(int match) |
1024 | niro | 532 | { |
1025 | if (match >= num_matches) | ||
1026 | niro | 816 | match = num_matches - 1; |
1027 | niro | 532 | if (match < 0) |
1028 | niro | 816 | match = 0; |
1029 | match_pos = match; | ||
1030 | niro | 532 | } |
1031 | |||
1032 | static void goto_match(int match) | ||
1033 | { | ||
1034 | niro | 816 | if (!pattern_valid) |
1035 | return; | ||
1036 | if (match < 0) | ||
1037 | match = 0; | ||
1038 | /* Try to find next match if eof isn't reached yet */ | ||
1039 | if (match >= num_matches && eof_error > 0) { | ||
1040 | wanted_match = match; /* "I want to read until I see N'th match" */ | ||
1041 | read_lines(); | ||
1042 | } | ||
1043 | if (num_matches) { | ||
1044 | normalize_match_pos(match); | ||
1045 | buffer_line(match_lines[match_pos]); | ||
1046 | } else { | ||
1047 | print_statusline("No matches found"); | ||
1048 | } | ||
1049 | niro | 532 | } |
1050 | |||
1051 | static void fill_match_lines(unsigned pos) | ||
1052 | { | ||
1053 | if (!pattern_valid) | ||
1054 | return; | ||
1055 | /* Run the regex on each line of the current file */ | ||
1056 | while (pos <= max_fline) { | ||
1057 | /* If this line matches */ | ||
1058 | if (regexec(&pattern, flines[pos], 0, NULL, 0) == 0 | ||
1059 | /* and we didn't match it last time */ | ||
1060 | && !(num_matches && match_lines[num_matches-1] == pos) | ||
1061 | ) { | ||
1062 | niro | 816 | match_lines = xrealloc_vector(match_lines, 4, num_matches); |
1063 | niro | 532 | match_lines[num_matches++] = pos; |
1064 | } | ||
1065 | pos++; | ||
1066 | } | ||
1067 | } | ||
1068 | |||
1069 | static void regex_process(void) | ||
1070 | { | ||
1071 | char *uncomp_regex, *err; | ||
1072 | |||
1073 | /* Reset variables */ | ||
1074 | free(match_lines); | ||
1075 | match_lines = NULL; | ||
1076 | match_pos = 0; | ||
1077 | num_matches = 0; | ||
1078 | if (pattern_valid) { | ||
1079 | regfree(&pattern); | ||
1080 | pattern_valid = 0; | ||
1081 | } | ||
1082 | |||
1083 | /* Get the uncompiled regular expression from the user */ | ||
1084 | clear_line(); | ||
1085 | niro | 816 | bb_putchar((option_mask32 & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/'); |
1086 | niro | 532 | uncomp_regex = less_gets(1); |
1087 | if (!uncomp_regex[0]) { | ||
1088 | free(uncomp_regex); | ||
1089 | buffer_print(); | ||
1090 | return; | ||
1091 | } | ||
1092 | |||
1093 | /* Compile the regex and check for errors */ | ||
1094 | niro | 816 | err = regcomp_or_errmsg(&pattern, uncomp_regex, |
1095 | (option_mask32 & FLAG_I) ? REG_ICASE : 0); | ||
1096 | niro | 532 | free(uncomp_regex); |
1097 | if (err) { | ||
1098 | print_statusline(err); | ||
1099 | free(err); | ||
1100 | return; | ||
1101 | } | ||
1102 | niro | 816 | |
1103 | niro | 532 | pattern_valid = 1; |
1104 | match_pos = 0; | ||
1105 | fill_match_lines(0); | ||
1106 | while (match_pos < num_matches) { | ||
1107 | niro | 816 | if ((int)match_lines[match_pos] > cur_fline) |
1108 | niro | 532 | break; |
1109 | match_pos++; | ||
1110 | } | ||
1111 | if (option_mask32 & LESS_STATE_MATCH_BACKWARDS) | ||
1112 | match_pos--; | ||
1113 | niro | 816 | |
1114 | /* It's possible that no matches are found yet. | ||
1115 | * goto_match() will read input looking for match, | ||
1116 | * if needed */ | ||
1117 | goto_match(match_pos); | ||
1118 | niro | 532 | } |
1119 | #endif | ||
1120 | |||
1121 | static void number_process(int first_digit) | ||
1122 | { | ||
1123 | niro | 816 | unsigned i; |
1124 | niro | 532 | int num; |
1125 | niro | 816 | int keypress; |
1126 | niro | 532 | char num_input[sizeof(int)*4]; /* more than enough */ |
1127 | |||
1128 | num_input[0] = first_digit; | ||
1129 | |||
1130 | /* Clear the current line, print a prompt, and then print the digit */ | ||
1131 | clear_line(); | ||
1132 | printf(":%c", first_digit); | ||
1133 | |||
1134 | /* Receive input until a letter is given */ | ||
1135 | niro | 816 | i = 1; |
1136 | niro | 532 | while (i < sizeof(num_input)-1) { |
1137 | niro | 816 | keypress = less_getch(i + 1); |
1138 | if ((unsigned)keypress > 255 || !isdigit(num_input[i])) | ||
1139 | niro | 532 | break; |
1140 | niro | 816 | num_input[i] = keypress; |
1141 | bb_putchar(keypress); | ||
1142 | niro | 532 | i++; |
1143 | } | ||
1144 | |||
1145 | num_input[i] = '\0'; | ||
1146 | num = bb_strtou(num_input, NULL, 10); | ||
1147 | /* on format error, num == -1 */ | ||
1148 | if (num < 1 || num > MAXLINES) { | ||
1149 | buffer_print(); | ||
1150 | return; | ||
1151 | } | ||
1152 | |||
1153 | /* We now know the number and the letter entered, so we process them */ | ||
1154 | switch (keypress) { | ||
1155 | niro | 816 | case KEYCODE_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015': |
1156 | niro | 532 | buffer_down(num); |
1157 | break; | ||
1158 | niro | 816 | case KEYCODE_UP: case 'b': case 'w': case 'y': case 'u': |
1159 | niro | 532 | buffer_up(num); |
1160 | break; | ||
1161 | case 'g': case '<': case 'G': case '>': | ||
1162 | cur_fline = num + max_displayed_line; | ||
1163 | niro | 816 | read_lines(); |
1164 | niro | 532 | buffer_line(num - 1); |
1165 | break; | ||
1166 | case 'p': case '%': | ||
1167 | num = num * (max_fline / 100); /* + max_fline / 2; */ | ||
1168 | cur_fline = num + max_displayed_line; | ||
1169 | niro | 816 | read_lines(); |
1170 | niro | 532 | buffer_line(num); |
1171 | break; | ||
1172 | #if ENABLE_FEATURE_LESS_REGEXP | ||
1173 | case 'n': | ||
1174 | goto_match(match_pos + num); | ||
1175 | break; | ||
1176 | case '/': | ||
1177 | option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS; | ||
1178 | regex_process(); | ||
1179 | break; | ||
1180 | case '?': | ||
1181 | option_mask32 |= LESS_STATE_MATCH_BACKWARDS; | ||
1182 | regex_process(); | ||
1183 | break; | ||
1184 | #endif | ||
1185 | } | ||
1186 | } | ||
1187 | |||
1188 | niro | 816 | #if ENABLE_FEATURE_LESS_DASHCMD |
1189 | niro | 532 | static void flag_change(void) |
1190 | { | ||
1191 | int keypress; | ||
1192 | |||
1193 | clear_line(); | ||
1194 | niro | 816 | bb_putchar('-'); |
1195 | keypress = less_getch(1); | ||
1196 | niro | 532 | |
1197 | switch (keypress) { | ||
1198 | case 'M': | ||
1199 | option_mask32 ^= FLAG_M; | ||
1200 | break; | ||
1201 | case 'm': | ||
1202 | option_mask32 ^= FLAG_m; | ||
1203 | break; | ||
1204 | case 'E': | ||
1205 | option_mask32 ^= FLAG_E; | ||
1206 | break; | ||
1207 | case '~': | ||
1208 | option_mask32 ^= FLAG_TILDE; | ||
1209 | break; | ||
1210 | niro | 816 | case 'S': |
1211 | option_mask32 ^= FLAG_S; | ||
1212 | buffer_fill_and_print(); | ||
1213 | break; | ||
1214 | #if ENABLE_FEATURE_LESS_LINENUMS | ||
1215 | case 'N': | ||
1216 | option_mask32 ^= FLAG_N; | ||
1217 | re_wrap(); | ||
1218 | buffer_fill_and_print(); | ||
1219 | break; | ||
1220 | #endif | ||
1221 | niro | 532 | } |
1222 | } | ||
1223 | |||
1224 | niro | 816 | #ifdef BLOAT |
1225 | niro | 532 | static void show_flag_status(void) |
1226 | { | ||
1227 | int keypress; | ||
1228 | int flag_val; | ||
1229 | |||
1230 | clear_line(); | ||
1231 | niro | 816 | bb_putchar('_'); |
1232 | keypress = less_getch(1); | ||
1233 | niro | 532 | |
1234 | switch (keypress) { | ||
1235 | case 'M': | ||
1236 | flag_val = option_mask32 & FLAG_M; | ||
1237 | break; | ||
1238 | case 'm': | ||
1239 | flag_val = option_mask32 & FLAG_m; | ||
1240 | break; | ||
1241 | case '~': | ||
1242 | flag_val = option_mask32 & FLAG_TILDE; | ||
1243 | break; | ||
1244 | case 'N': | ||
1245 | flag_val = option_mask32 & FLAG_N; | ||
1246 | break; | ||
1247 | case 'E': | ||
1248 | flag_val = option_mask32 & FLAG_E; | ||
1249 | break; | ||
1250 | default: | ||
1251 | flag_val = 0; | ||
1252 | break; | ||
1253 | } | ||
1254 | |||
1255 | clear_line(); | ||
1256 | printf(HIGHLIGHT"The status of the flag is: %u"NORMAL, flag_val != 0); | ||
1257 | } | ||
1258 | #endif | ||
1259 | |||
1260 | niro | 816 | #endif /* ENABLE_FEATURE_LESS_DASHCMD */ |
1261 | |||
1262 | niro | 532 | static void save_input_to_file(void) |
1263 | { | ||
1264 | const char *msg = ""; | ||
1265 | char *current_line; | ||
1266 | niro | 816 | unsigned i; |
1267 | niro | 532 | FILE *fp; |
1268 | |||
1269 | print_statusline("Log file: "); | ||
1270 | current_line = less_gets(sizeof("Log file: ")-1); | ||
1271 | niro | 816 | if (current_line[0]) { |
1272 | fp = fopen_for_write(current_line); | ||
1273 | niro | 532 | if (!fp) { |
1274 | msg = "Error opening log file"; | ||
1275 | goto ret; | ||
1276 | } | ||
1277 | for (i = 0; i <= max_fline; i++) | ||
1278 | fprintf(fp, "%s\n", flines[i]); | ||
1279 | fclose(fp); | ||
1280 | msg = "Done"; | ||
1281 | } | ||
1282 | ret: | ||
1283 | print_statusline(msg); | ||
1284 | free(current_line); | ||
1285 | } | ||
1286 | |||
1287 | #if ENABLE_FEATURE_LESS_MARKS | ||
1288 | static void add_mark(void) | ||
1289 | { | ||
1290 | int letter; | ||
1291 | |||
1292 | print_statusline("Mark: "); | ||
1293 | niro | 816 | letter = less_getch(sizeof("Mark: ") - 1); |
1294 | niro | 532 | |
1295 | if (isalpha(letter)) { | ||
1296 | /* If we exceed 15 marks, start overwriting previous ones */ | ||
1297 | if (num_marks == 14) | ||
1298 | num_marks = 0; | ||
1299 | |||
1300 | mark_lines[num_marks][0] = letter; | ||
1301 | mark_lines[num_marks][1] = cur_fline; | ||
1302 | num_marks++; | ||
1303 | } else { | ||
1304 | print_statusline("Invalid mark letter"); | ||
1305 | } | ||
1306 | } | ||
1307 | |||
1308 | static void goto_mark(void) | ||
1309 | { | ||
1310 | int letter; | ||
1311 | int i; | ||
1312 | |||
1313 | print_statusline("Go to mark: "); | ||
1314 | niro | 816 | letter = less_getch(sizeof("Go to mark: ") - 1); |
1315 | niro | 532 | clear_line(); |
1316 | |||
1317 | if (isalpha(letter)) { | ||
1318 | for (i = 0; i <= num_marks; i++) | ||
1319 | if (letter == mark_lines[i][0]) { | ||
1320 | buffer_line(mark_lines[i][1]); | ||
1321 | break; | ||
1322 | } | ||
1323 | if (num_marks == 14 && letter != mark_lines[14][0]) | ||
1324 | print_statusline("Mark not set"); | ||
1325 | } else | ||
1326 | print_statusline("Invalid mark letter"); | ||
1327 | } | ||
1328 | #endif | ||
1329 | |||
1330 | #if ENABLE_FEATURE_LESS_BRACKETS | ||
1331 | static char opp_bracket(char bracket) | ||
1332 | { | ||
1333 | switch (bracket) { | ||
1334 | niro | 816 | case '{': case '[': /* '}' == '{' + 2. Same for '[' */ |
1335 | bracket++; | ||
1336 | case '(': /* ')' == '(' + 1 */ | ||
1337 | bracket++; | ||
1338 | break; | ||
1339 | case '}': case ']': | ||
1340 | bracket--; | ||
1341 | case ')': | ||
1342 | bracket--; | ||
1343 | break; | ||
1344 | }; | ||
1345 | return bracket; | ||
1346 | niro | 532 | } |
1347 | |||
1348 | static void match_right_bracket(char bracket) | ||
1349 | { | ||
1350 | niro | 816 | unsigned i; |
1351 | niro | 532 | |
1352 | if (strchr(flines[cur_fline], bracket) == NULL) { | ||
1353 | print_statusline("No bracket in top line"); | ||
1354 | return; | ||
1355 | } | ||
1356 | niro | 816 | bracket = opp_bracket(bracket); |
1357 | niro | 532 | for (i = cur_fline + 1; i < max_fline; i++) { |
1358 | niro | 816 | if (strchr(flines[i], bracket) != NULL) { |
1359 | buffer_line(i); | ||
1360 | return; | ||
1361 | niro | 532 | } |
1362 | } | ||
1363 | niro | 816 | print_statusline("No matching bracket found"); |
1364 | niro | 532 | } |
1365 | |||
1366 | static void match_left_bracket(char bracket) | ||
1367 | { | ||
1368 | int i; | ||
1369 | |||
1370 | if (strchr(flines[cur_fline + max_displayed_line], bracket) == NULL) { | ||
1371 | print_statusline("No bracket in bottom line"); | ||
1372 | return; | ||
1373 | } | ||
1374 | |||
1375 | niro | 816 | bracket = opp_bracket(bracket); |
1376 | niro | 532 | for (i = cur_fline + max_displayed_line; i >= 0; i--) { |
1377 | niro | 816 | if (strchr(flines[i], bracket) != NULL) { |
1378 | buffer_line(i); | ||
1379 | return; | ||
1380 | niro | 532 | } |
1381 | } | ||
1382 | niro | 816 | print_statusline("No matching bracket found"); |
1383 | niro | 532 | } |
1384 | #endif /* FEATURE_LESS_BRACKETS */ | ||
1385 | |||
1386 | static void keypress_process(int keypress) | ||
1387 | { | ||
1388 | switch (keypress) { | ||
1389 | niro | 816 | case KEYCODE_DOWN: case 'e': case 'j': case 0x0d: |
1390 | niro | 532 | buffer_down(1); |
1391 | break; | ||
1392 | niro | 816 | case KEYCODE_UP: case 'y': case 'k': |
1393 | niro | 532 | buffer_up(1); |
1394 | break; | ||
1395 | niro | 816 | case KEYCODE_PAGEDOWN: case ' ': case 'z': case 'f': |
1396 | niro | 532 | buffer_down(max_displayed_line + 1); |
1397 | break; | ||
1398 | niro | 816 | case KEYCODE_PAGEUP: case 'w': case 'b': |
1399 | niro | 532 | buffer_up(max_displayed_line + 1); |
1400 | break; | ||
1401 | case 'd': | ||
1402 | buffer_down((max_displayed_line + 1) / 2); | ||
1403 | break; | ||
1404 | case 'u': | ||
1405 | buffer_up((max_displayed_line + 1) / 2); | ||
1406 | break; | ||
1407 | niro | 816 | case KEYCODE_HOME: case 'g': case 'p': case '<': case '%': |
1408 | niro | 532 | buffer_line(0); |
1409 | break; | ||
1410 | niro | 816 | case KEYCODE_END: case 'G': case '>': |
1411 | niro | 532 | cur_fline = MAXLINES; |
1412 | read_lines(); | ||
1413 | buffer_line(cur_fline); | ||
1414 | break; | ||
1415 | case 'q': case 'Q': | ||
1416 | niro | 816 | less_exit(EXIT_SUCCESS); |
1417 | niro | 532 | break; |
1418 | #if ENABLE_FEATURE_LESS_MARKS | ||
1419 | case 'm': | ||
1420 | add_mark(); | ||
1421 | buffer_print(); | ||
1422 | break; | ||
1423 | case '\'': | ||
1424 | goto_mark(); | ||
1425 | buffer_print(); | ||
1426 | break; | ||
1427 | #endif | ||
1428 | case 'r': case 'R': | ||
1429 | buffer_print(); | ||
1430 | break; | ||
1431 | /*case 'R': | ||
1432 | full_repaint(); | ||
1433 | break;*/ | ||
1434 | case 's': | ||
1435 | save_input_to_file(); | ||
1436 | break; | ||
1437 | case 'E': | ||
1438 | examine_file(); | ||
1439 | break; | ||
1440 | #if ENABLE_FEATURE_LESS_FLAGS | ||
1441 | case '=': | ||
1442 | m_status_print(); | ||
1443 | break; | ||
1444 | #endif | ||
1445 | #if ENABLE_FEATURE_LESS_REGEXP | ||
1446 | case '/': | ||
1447 | option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS; | ||
1448 | regex_process(); | ||
1449 | break; | ||
1450 | case 'n': | ||
1451 | goto_match(match_pos + 1); | ||
1452 | break; | ||
1453 | case 'N': | ||
1454 | goto_match(match_pos - 1); | ||
1455 | break; | ||
1456 | case '?': | ||
1457 | option_mask32 |= LESS_STATE_MATCH_BACKWARDS; | ||
1458 | regex_process(); | ||
1459 | break; | ||
1460 | #endif | ||
1461 | niro | 816 | #if ENABLE_FEATURE_LESS_DASHCMD |
1462 | niro | 532 | case '-': |
1463 | flag_change(); | ||
1464 | buffer_print(); | ||
1465 | break; | ||
1466 | niro | 816 | #ifdef BLOAT |
1467 | niro | 532 | case '_': |
1468 | show_flag_status(); | ||
1469 | break; | ||
1470 | #endif | ||
1471 | niro | 816 | #endif |
1472 | niro | 532 | #if ENABLE_FEATURE_LESS_BRACKETS |
1473 | case '{': case '(': case '[': | ||
1474 | match_right_bracket(keypress); | ||
1475 | break; | ||
1476 | case '}': case ')': case ']': | ||
1477 | match_left_bracket(keypress); | ||
1478 | break; | ||
1479 | #endif | ||
1480 | case ':': | ||
1481 | colon_process(); | ||
1482 | break; | ||
1483 | } | ||
1484 | |||
1485 | if (isdigit(keypress)) | ||
1486 | number_process(keypress); | ||
1487 | } | ||
1488 | |||
1489 | niro | 816 | static void sig_catcher(int sig) |
1490 | niro | 532 | { |
1491 | niro | 816 | less_exit(- sig); |
1492 | niro | 532 | } |
1493 | |||
1494 | niro | 816 | #if ENABLE_FEATURE_LESS_WINCH |
1495 | static void sigwinch_handler(int sig UNUSED_PARAM) | ||
1496 | { | ||
1497 | winch_counter++; | ||
1498 | } | ||
1499 | #endif | ||
1500 | |||
1501 | int less_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | ||
1502 | niro | 532 | int less_main(int argc, char **argv) |
1503 | { | ||
1504 | int keypress; | ||
1505 | |||
1506 | niro | 816 | INIT_G(); |
1507 | |||
1508 | niro | 532 | /* TODO: -x: do not interpret backspace, -xx: tab also */ |
1509 | /* -xxx: newline also */ | ||
1510 | /* -w N: assume width N (-xxx -w 32: hex viewer of sorts) */ | ||
1511 | niro | 984 | getopt32(argv, "EMmN~I" IF_FEATURE_LESS_DASHCMD("S")); |
1512 | niro | 532 | argc -= optind; |
1513 | argv += optind; | ||
1514 | num_files = argc; | ||
1515 | files = argv; | ||
1516 | |||
1517 | /* Another popular pager, most, detects when stdout | ||
1518 | * is not a tty and turns into cat. This makes sense. */ | ||
1519 | if (!isatty(STDOUT_FILENO)) | ||
1520 | return bb_cat(argv); | ||
1521 | |||
1522 | if (!num_files) { | ||
1523 | if (isatty(STDIN_FILENO)) { | ||
1524 | /* Just "less"? No args and no redirection? */ | ||
1525 | bb_error_msg("missing filename"); | ||
1526 | bb_show_usage(); | ||
1527 | } | ||
1528 | niro | 816 | } else { |
1529 | niro | 532 | filename = xstrdup(files[0]); |
1530 | niro | 816 | } |
1531 | niro | 532 | |
1532 | niro | 816 | if (option_mask32 & FLAG_TILDE) |
1533 | empty_line_marker = ""; | ||
1534 | niro | 532 | |
1535 | niro | 816 | kbd_fd = open(CURRENT_TTY, O_RDONLY); |
1536 | if (kbd_fd < 0) | ||
1537 | return bb_cat(argv); | ||
1538 | ndelay_on(kbd_fd); | ||
1539 | |||
1540 | tcgetattr(kbd_fd, &term_orig); | ||
1541 | term_less = term_orig; | ||
1542 | term_less.c_lflag &= ~(ICANON | ECHO); | ||
1543 | term_less.c_iflag &= ~(IXON | ICRNL); | ||
1544 | /*term_less.c_oflag &= ~ONLCR;*/ | ||
1545 | term_less.c_cc[VMIN] = 1; | ||
1546 | term_less.c_cc[VTIME] = 0; | ||
1547 | |||
1548 | niro | 532 | get_terminal_width_height(kbd_fd, &width, &max_displayed_line); |
1549 | /* 20: two tabstops + 4 */ | ||
1550 | if (width < 20 || max_displayed_line < 3) | ||
1551 | niro | 816 | return bb_cat(argv); |
1552 | niro | 532 | max_displayed_line -= 2; |
1553 | |||
1554 | niro | 816 | /* We want to restore term_orig on exit */ |
1555 | bb_signals(BB_FATAL_SIGS, sig_catcher); | ||
1556 | #if ENABLE_FEATURE_LESS_WINCH | ||
1557 | signal(SIGWINCH, sigwinch_handler); | ||
1558 | #endif | ||
1559 | |||
1560 | niro | 532 | buffer = xmalloc((max_displayed_line+1) * sizeof(char *)); |
1561 | reinitialize(); | ||
1562 | while (1) { | ||
1563 | niro | 816 | #if ENABLE_FEATURE_LESS_WINCH |
1564 | while (WINCH_COUNTER) { | ||
1565 | again: | ||
1566 | winch_counter--; | ||
1567 | get_terminal_width_height(kbd_fd, &width, &max_displayed_line); | ||
1568 | /* 20: two tabstops + 4 */ | ||
1569 | if (width < 20) | ||
1570 | width = 20; | ||
1571 | if (max_displayed_line < 3) | ||
1572 | max_displayed_line = 3; | ||
1573 | max_displayed_line -= 2; | ||
1574 | free(buffer); | ||
1575 | buffer = xmalloc((max_displayed_line+1) * sizeof(char *)); | ||
1576 | /* Avoid re-wrap and/or redraw if we already know | ||
1577 | * we need to do it again. These ops are expensive */ | ||
1578 | if (WINCH_COUNTER) | ||
1579 | goto again; | ||
1580 | re_wrap(); | ||
1581 | if (WINCH_COUNTER) | ||
1582 | goto again; | ||
1583 | buffer_fill_and_print(); | ||
1584 | /* This took some time. Loop back and check, | ||
1585 | * were there another SIGWINCH? */ | ||
1586 | } | ||
1587 | #endif | ||
1588 | keypress = less_getch(-1); /* -1: do not position cursor */ | ||
1589 | niro | 532 | keypress_process(keypress); |
1590 | } | ||
1591 | } | ||
1592 | niro | 816 | |
1593 | /* | ||
1594 | Help text of less version 418 is below. | ||
1595 | If you are implementing something, keeping | ||
1596 | key and/or command line switch compatibility is a good idea: | ||
1597 | |||
1598 | |||
1599 | SUMMARY OF LESS COMMANDS | ||
1600 | |||
1601 | Commands marked with * may be preceded by a number, N. | ||
1602 | Notes in parentheses indicate the behavior if N is given. | ||
1603 | h H Display this help. | ||
1604 | q :q Q :Q ZZ Exit. | ||
1605 | --------------------------------------------------------------------------- | ||
1606 | MOVING | ||
1607 | e ^E j ^N CR * Forward one line (or N lines). | ||
1608 | y ^Y k ^K ^P * Backward one line (or N lines). | ||
1609 | f ^F ^V SPACE * Forward one window (or N lines). | ||
1610 | b ^B ESC-v * Backward one window (or N lines). | ||
1611 | z * Forward one window (and set window to N). | ||
1612 | w * Backward one window (and set window to N). | ||
1613 | ESC-SPACE * Forward one window, but don't stop at end-of-file. | ||
1614 | d ^D * Forward one half-window (and set half-window to N). | ||
1615 | u ^U * Backward one half-window (and set half-window to N). | ||
1616 | ESC-) RightArrow * Left one half screen width (or N positions). | ||
1617 | ESC-( LeftArrow * Right one half screen width (or N positions). | ||
1618 | F Forward forever; like "tail -f". | ||
1619 | r ^R ^L Repaint screen. | ||
1620 | R Repaint screen, discarding buffered input. | ||
1621 | --------------------------------------------------- | ||
1622 | Default "window" is the screen height. | ||
1623 | Default "half-window" is half of the screen height. | ||
1624 | --------------------------------------------------------------------------- | ||
1625 | SEARCHING | ||
1626 | /pattern * Search forward for (N-th) matching line. | ||
1627 | ?pattern * Search backward for (N-th) matching line. | ||
1628 | n * Repeat previous search (for N-th occurrence). | ||
1629 | N * Repeat previous search in reverse direction. | ||
1630 | ESC-n * Repeat previous search, spanning files. | ||
1631 | ESC-N * Repeat previous search, reverse dir. & spanning files. | ||
1632 | ESC-u Undo (toggle) search highlighting. | ||
1633 | --------------------------------------------------- | ||
1634 | Search patterns may be modified by one or more of: | ||
1635 | ^N or ! Search for NON-matching lines. | ||
1636 | ^E or * Search multiple files (pass thru END OF FILE). | ||
1637 | ^F or @ Start search at FIRST file (for /) or last file (for ?). | ||
1638 | ^K Highlight matches, but don't move (KEEP position). | ||
1639 | ^R Don't use REGULAR EXPRESSIONS. | ||
1640 | --------------------------------------------------------------------------- | ||
1641 | JUMPING | ||
1642 | g < ESC-< * Go to first line in file (or line N). | ||
1643 | G > ESC-> * Go to last line in file (or line N). | ||
1644 | p % * Go to beginning of file (or N percent into file). | ||
1645 | t * Go to the (N-th) next tag. | ||
1646 | T * Go to the (N-th) previous tag. | ||
1647 | { ( [ * Find close bracket } ) ]. | ||
1648 | } ) ] * Find open bracket { ( [. | ||
1649 | ESC-^F <c1> <c2> * Find close bracket <c2>. | ||
1650 | ESC-^B <c1> <c2> * Find open bracket <c1> | ||
1651 | --------------------------------------------------- | ||
1652 | Each "find close bracket" command goes forward to the close bracket | ||
1653 | matching the (N-th) open bracket in the top line. | ||
1654 | Each "find open bracket" command goes backward to the open bracket | ||
1655 | matching the (N-th) close bracket in the bottom line. | ||
1656 | m<letter> Mark the current position with <letter>. | ||
1657 | '<letter> Go to a previously marked position. | ||
1658 | '' Go to the previous position. | ||
1659 | ^X^X Same as '. | ||
1660 | --------------------------------------------------- | ||
1661 | A mark is any upper-case or lower-case letter. | ||
1662 | Certain marks are predefined: | ||
1663 | ^ means beginning of the file | ||
1664 | $ means end of the file | ||
1665 | --------------------------------------------------------------------------- | ||
1666 | CHANGING FILES | ||
1667 | :e [file] Examine a new file. | ||
1668 | ^X^V Same as :e. | ||
1669 | :n * Examine the (N-th) next file from the command line. | ||
1670 | :p * Examine the (N-th) previous file from the command line. | ||
1671 | :x * Examine the first (or N-th) file from the command line. | ||
1672 | :d Delete the current file from the command line list. | ||
1673 | = ^G :f Print current file name. | ||
1674 | --------------------------------------------------------------------------- | ||
1675 | MISCELLANEOUS COMMANDS | ||
1676 | -<flag> Toggle a command line option [see OPTIONS below]. | ||
1677 | --<name> Toggle a command line option, by name. | ||
1678 | _<flag> Display the setting of a command line option. | ||
1679 | __<name> Display the setting of an option, by name. | ||
1680 | +cmd Execute the less cmd each time a new file is examined. | ||
1681 | !command Execute the shell command with $SHELL. | ||
1682 | |Xcommand Pipe file between current pos & mark X to shell command. | ||
1683 | v Edit the current file with $VISUAL or $EDITOR. | ||
1684 | V Print version number of "less". | ||
1685 | --------------------------------------------------------------------------- | ||
1686 | OPTIONS | ||
1687 | Most options may be changed either on the command line, | ||
1688 | or from within less by using the - or -- command. | ||
1689 | Options may be given in one of two forms: either a single | ||
1690 | character preceded by a -, or a name preceeded by --. | ||
1691 | -? ........ --help | ||
1692 | Display help (from command line). | ||
1693 | -a ........ --search-skip-screen | ||
1694 | Forward search skips current screen. | ||
1695 | -b [N] .... --buffers=[N] | ||
1696 | Number of buffers. | ||
1697 | -B ........ --auto-buffers | ||
1698 | Don't automatically allocate buffers for pipes. | ||
1699 | -c ........ --clear-screen | ||
1700 | Repaint by clearing rather than scrolling. | ||
1701 | -d ........ --dumb | ||
1702 | Dumb terminal. | ||
1703 | -D [xn.n] . --color=xn.n | ||
1704 | Set screen colors. (MS-DOS only) | ||
1705 | -e -E .... --quit-at-eof --QUIT-AT-EOF | ||
1706 | Quit at end of file. | ||
1707 | -f ........ --force | ||
1708 | Force open non-regular files. | ||
1709 | -F ........ --quit-if-one-screen | ||
1710 | Quit if entire file fits on first screen. | ||
1711 | -g ........ --hilite-search | ||
1712 | Highlight only last match for searches. | ||
1713 | -G ........ --HILITE-SEARCH | ||
1714 | Don't highlight any matches for searches. | ||
1715 | -h [N] .... --max-back-scroll=[N] | ||
1716 | Backward scroll limit. | ||
1717 | -i ........ --ignore-case | ||
1718 | Ignore case in searches that do not contain uppercase. | ||
1719 | -I ........ --IGNORE-CASE | ||
1720 | Ignore case in all searches. | ||
1721 | -j [N] .... --jump-target=[N] | ||
1722 | Screen position of target lines. | ||
1723 | -J ........ --status-column | ||
1724 | Display a status column at left edge of screen. | ||
1725 | -k [file] . --lesskey-file=[file] | ||
1726 | Use a lesskey file. | ||
1727 | -L ........ --no-lessopen | ||
1728 | Ignore the LESSOPEN environment variable. | ||
1729 | -m -M .... --long-prompt --LONG-PROMPT | ||
1730 | Set prompt style. | ||
1731 | -n -N .... --line-numbers --LINE-NUMBERS | ||
1732 | Don't use line numbers. | ||
1733 | -o [file] . --log-file=[file] | ||
1734 | Copy to log file (standard input only). | ||
1735 | -O [file] . --LOG-FILE=[file] | ||
1736 | Copy to log file (unconditionally overwrite). | ||
1737 | -p [pattern] --pattern=[pattern] | ||
1738 | Start at pattern (from command line). | ||
1739 | -P [prompt] --prompt=[prompt] | ||
1740 | Define new prompt. | ||
1741 | -q -Q .... --quiet --QUIET --silent --SILENT | ||
1742 | Quiet the terminal bell. | ||
1743 | -r -R .... --raw-control-chars --RAW-CONTROL-CHARS | ||
1744 | Output "raw" control characters. | ||
1745 | -s ........ --squeeze-blank-lines | ||
1746 | Squeeze multiple blank lines. | ||
1747 | -S ........ --chop-long-lines | ||
1748 | Chop long lines. | ||
1749 | -t [tag] .. --tag=[tag] | ||
1750 | Find a tag. | ||
1751 | -T [tagsfile] --tag-file=[tagsfile] | ||
1752 | Use an alternate tags file. | ||
1753 | -u -U .... --underline-special --UNDERLINE-SPECIAL | ||
1754 | Change handling of backspaces. | ||
1755 | -V ........ --version | ||
1756 | Display the version number of "less". | ||
1757 | -w ........ --hilite-unread | ||
1758 | Highlight first new line after forward-screen. | ||
1759 | -W ........ --HILITE-UNREAD | ||
1760 | Highlight first new line after any forward movement. | ||
1761 | -x [N[,...]] --tabs=[N[,...]] | ||
1762 | Set tab stops. | ||
1763 | -X ........ --no-init | ||
1764 | Don't use termcap init/deinit strings. | ||
1765 | --no-keypad | ||
1766 | Don't use termcap keypad init/deinit strings. | ||
1767 | -y [N] .... --max-forw-scroll=[N] | ||
1768 | Forward scroll limit. | ||
1769 | -z [N] .... --window=[N] | ||
1770 | Set size of window. | ||
1771 | -" [c[c]] . --quotes=[c[c]] | ||
1772 | Set shell quote characters. | ||
1773 | -~ ........ --tilde | ||
1774 | Don't display tildes after end of file. | ||
1775 | -# [N] .... --shift=[N] | ||
1776 | Horizontal scroll amount (0 = one half screen width) | ||
1777 | |||
1778 | --------------------------------------------------------------------------- | ||
1779 | LINE EDITING | ||
1780 | These keys can be used to edit text being entered | ||
1781 | on the "command line" at the bottom of the screen. | ||
1782 | RightArrow ESC-l Move cursor right one character. | ||
1783 | LeftArrow ESC-h Move cursor left one character. | ||
1784 | CNTL-RightArrow ESC-RightArrow ESC-w Move cursor right one word. | ||
1785 | CNTL-LeftArrow ESC-LeftArrow ESC-b Move cursor left one word. | ||
1786 | HOME ESC-0 Move cursor to start of line. | ||
1787 | END ESC-$ Move cursor to end of line. | ||
1788 | BACKSPACE Delete char to left of cursor. | ||
1789 | DELETE ESC-x Delete char under cursor. | ||
1790 | CNTL-BACKSPACE ESC-BACKSPACE Delete word to left of cursor. | ||
1791 | CNTL-DELETE ESC-DELETE ESC-X Delete word under cursor. | ||
1792 | CNTL-U ESC (MS-DOS only) Delete entire line. | ||
1793 | UpArrow ESC-k Retrieve previous command line. | ||
1794 | DownArrow ESC-j Retrieve next command line. | ||
1795 | TAB Complete filename & cycle. | ||
1796 | SHIFT-TAB ESC-TAB Complete filename & reverse cycle. | ||
1797 | CNTL-L Complete filename, list all. | ||
1798 | */ |