Magellan Linux

Diff of /tags/mkinitrd-6_3_1/busybox/miscutils/less.c

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 532 by niro, Sat Sep 1 22:45:15 2007 UTC revision 816 by niro, Fri Apr 24 18:33:46 2009 UTC
# Line 21  Line 21 
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"
# Line 38  Line 36 
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)
# Line 125  static void set_tty_cooked(void) Line 163  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)
# Line 159  static void print_statusline(const char Line 186  static void print_statusline(const char
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    
# Line 291  static void m_status_print(void) Line 479  static void m_status_print(void)
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)
# Line 298  static void m_status_print(void) Line 489  static void m_status_print(void)
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]);
# Line 314  static void status_print(void) Line 505  static void status_print(void)
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)) {
# Line 324  static void status_print(void) Line 518  static void status_print(void)
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)";
# Line 339  static void status_print(void) Line 533  static void status_print(void)
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  {  {
# Line 358  static void print_found(const char *line Line 593  static void print_found(const char *line
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;
# Line 394  static void print_found(const char *line Line 630  static void print_found(const char *line
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
# Line 417  void print_found(const char *line); Line 658  void print_found(const char *line);
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) {
# Line 450  static void print_ascii(const char *str) Line 694  static void print_ascii(const char *str)
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++)
# Line 463  static void buffer_print(void) Line 707  static void buffer_print(void)
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   }   }
# Line 476  static void buffer_fill_and_print(void) Line 743  static void buffer_fill_and_print(void)
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    
# Line 507  static void buffer_line(int linenum) Line 765  static void buffer_line(int linenum)
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  }  }
# Line 524  static void open_file_and_read_lines(voi Line 784  static void open_file_and_read_lines(voi
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  }  }
# Line 532  static void open_file_and_read_lines(voi Line 792  static void open_file_and_read_lines(voi
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   }   }
# Line 548  static void reinitialize(void) Line 808  static void reinitialize(void)
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;
# Line 680  static void change_file(int direction) Line 968  static void change_file(int direction)
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;
# Line 706  static void colon_process(void) Line 994  static void colon_process(void)
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();
# Line 726  static void colon_process(void) Line 1014  static void colon_process(void)
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);
# Line 735  static void colon_process(void) Line 1023  static void colon_process(void)
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)
# Line 762  static void fill_match_lines(unsigned po Line 1062  static void fill_match_lines(unsigned po
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++;
# Line 785  static void regex_process(void) Line 1085  static void regex_process(void)
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);
# Line 794  static void regex_process(void) Line 1094  static void regex_process(void)
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    
# Line 835  static void number_process(int first_dig Line 1135  static void number_process(int first_dig
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 */
# Line 855  static void number_process(int first_dig Line 1155  static void number_process(int first_dig
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
# Line 888  static void number_process(int first_dig Line 1188  static void number_process(int first_dig
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':
# Line 910  static void flag_change(void) Line 1210  static void flag_change(void)
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':
# Line 948  static void show_flag_status(void) Line 1260  static void show_flag_status(void)
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;
# Line 979  static void add_mark(void) Line 1293  static void add_mark(void)
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 */
# Line 1000  static void goto_mark(void) Line 1314  static void goto_mark(void)
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)) {
# Line 1020  static void goto_mark(void) Line 1334  static void goto_mark(void)
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) {
# Line 1062  static void match_left_bracket(char brac Line 1375  static void match_left_bracket(char brac
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':
# Line 1095  static void keypress_process(int keypres Line 1407  static void keypress_process(int keypres
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':
# Line 1149  static void keypress_process(int keypres Line 1461  static void keypress_process(int keypres
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);
# Line 1175  static void keypress_process(int keypres Line 1489  static void keypress_process(int keypres
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;
# Line 1205  int less_main(int argc, char **argv) Line 1528  int less_main(int argc, char **argv)
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    */

Legend:
Removed from v.532  
changed lines
  Added in v.816