Changeset 1770 in MondoRescue for branches/stable/mindi-busybox/miscutils/less.c
- Timestamp:
- Nov 6, 2007, 11:01:53 AM (16 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/stable/mindi-busybox/miscutils/less.c
r821 r1770 9 9 10 10 /* 11 * This program needs a lot of development, so consider it in a beta stage 12 * at best. 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. 13 18 * 14 * TODO: 15 * - Add more regular expression support - search modifiers, certain matches, etc. 16 * - Add more complex bracket searching - currently, nested brackets are 17 * not considered. 18 * - Add support for "F" as an input. This causes less to act in 19 * a similar way to tail -f. 20 * - Check for binary files, and prompt the user if a binary file 21 * is detected. 22 * - Allow horizontal scrolling. Currently, lines simply continue onto 23 * the next line, per the terminal's discretion 24 * 25 * Notes: 26 * - filename is an array and not a pointer because that avoids all sorts 27 * of complications involving the fact that something that is pointed to 28 * will be changed if the pointer is changed. 29 * - the inp file pointer is used so that keyboard input works after 30 * redirected input has been read from stdin 31 */ 32 33 #include "busybox.h" 34 #include <stdio.h> 35 #include <stdlib.h> 36 #include <string.h> 37 #include <termios.h> 38 #include <unistd.h> 39 #include <ctype.h> 40 41 #ifdef CONFIG_FEATURE_LESS_REGEXP 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 #include <sched.h> /* sched_yield() */ 25 26 #include "libbb.h" 27 #if ENABLE_FEATURE_LESS_REGEXP 42 28 #include "xregex.h" 43 29 #endif 44 30 45 46 /* These are the escape sequences corresponding to special keys */ 47 #define REAL_KEY_UP 'A' 48 #define REAL_KEY_DOWN 'B' 49 #define REAL_KEY_RIGHT 'C' 50 #define REAL_KEY_LEFT 'D' 51 #define REAL_PAGE_UP '5' 52 #define REAL_PAGE_DOWN '6' 53 54 /* These are the special codes assigned by this program to the special keys */ 55 #define PAGE_UP 20 56 #define PAGE_DOWN 21 57 #define KEY_UP 22 58 #define KEY_DOWN 23 59 #define KEY_RIGHT 24 60 #define KEY_LEFT 25 31 /* FIXME: currently doesn't work right */ 32 #undef ENABLE_FEATURE_LESS_FLAGCS 33 #define ENABLE_FEATURE_LESS_FLAGCS 0 61 34 62 35 /* The escape codes for highlighted and normal text */ 63 36 #define HIGHLIGHT "\033[7m" 64 37 #define NORMAL "\033[0m" 65 66 38 /* The escape code to clear the screen */ 67 39 #define CLEAR "\033[H\033[J" 68 69 /* Maximum number of lines in a file */ 70 #define MAXLINES 10000 71 72 static int height; 73 static int width; 74 static char **files; 75 static char filename[256]; 76 static char **buffer; 77 static char **flines; 78 static int current_file = 1; 79 static int line_pos; 80 static int num_flines; 81 static int num_files = 1; 82 static int past_eof; 40 /* The escape code to clear to end of line */ 41 #define CLEAR_2_EOL "\033[K" 42 43 /* These are the escape sequences corresponding to special keys */ 44 enum { 45 REAL_KEY_UP = 'A', 46 REAL_KEY_DOWN = 'B', 47 REAL_KEY_RIGHT = 'C', 48 REAL_KEY_LEFT = 'D', 49 REAL_PAGE_UP = '5', 50 REAL_PAGE_DOWN = '6', 51 REAL_KEY_HOME = '7', // vt100? linux vt? or what? 52 REAL_KEY_END = '8', 53 REAL_KEY_HOME_ALT = '1', // ESC [1~ (vt100? linux vt? or what?) 54 REAL_KEY_END_ALT = '4', // ESC [4~ 55 REAL_KEY_HOME_XTERM = 'H', 56 REAL_KEY_END_XTERM = 'F', 57 58 /* These are the special codes assigned by this program to the special keys */ 59 KEY_UP = 20, 60 KEY_DOWN = 21, 61 KEY_RIGHT = 22, 62 KEY_LEFT = 23, 63 PAGE_UP = 24, 64 PAGE_DOWN = 25, 65 KEY_HOME = 26, 66 KEY_END = 27, 67 68 /* Absolute max of lines eaten */ 69 MAXLINES = CONFIG_FEATURE_LESS_MAXLINES, 70 71 /* This many "after the end" lines we will show (at max) */ 72 TILDES = 1, 73 }; 83 74 84 75 /* Command line options */ 85 static unsigned long flags; 86 #define FLAG_E 1 87 #define FLAG_M (1<<1) 88 #define FLAG_m (1<<2) 89 #define FLAG_N (1<<3) 90 #define FLAG_TILDE (1<<4) 91 92 /* This is needed so that program behaviour changes when input comes from 93 stdin */ 94 static int inp_stdin; 95 96 #ifdef CONFIG_FEATURE_LESS_MARKS 97 static int mark_lines[15][2]; 98 static int num_marks; 99 #endif 100 101 #ifdef CONFIG_FEATURE_LESS_REGEXP 102 static int match_found; 103 static int *match_lines; 104 static int match_pos; 105 static int num_matches; 106 static int match_backwards; 107 static regex_t old_pattern; 108 #endif 109 110 /* Needed termios structures */ 111 static struct termios term_orig, term_vi; 112 113 /* File pointer to get input from */ 114 static FILE *inp; 76 enum { 77 FLAG_E = 1, 78 FLAG_M = 1 << 1, 79 FLAG_m = 1 << 2, 80 FLAG_N = 1 << 3, 81 FLAG_TILDE = 1 << 4, 82 /* hijack command line options variable for internal state vars */ 83 LESS_STATE_MATCH_BACKWARDS = 1 << 15, 84 }; 85 86 #if !ENABLE_FEATURE_LESS_REGEXP 87 enum { pattern_valid = 0 }; 88 #endif 89 90 struct globals { 91 int cur_fline; /* signed */ 92 int kbd_fd; /* fd to get input from */ 93 /* last position in last line, taking into account tabs */ 94 size_t linepos; 95 unsigned max_displayed_line; 96 unsigned max_fline; 97 unsigned max_lineno; /* this one tracks linewrap */ 98 unsigned width; 99 ssize_t eof_error; /* eof if 0, error if < 0 */ 100 size_t readpos; 101 size_t readeof; 102 const char **buffer; 103 const char **flines; 104 const char *empty_line_marker; 105 unsigned num_files; 106 unsigned current_file; 107 char *filename; 108 char **files; 109 #if ENABLE_FEATURE_LESS_MARKS 110 unsigned num_marks; 111 unsigned mark_lines[15][2]; 112 #endif 113 #if ENABLE_FEATURE_LESS_REGEXP 114 unsigned *match_lines; 115 int match_pos; /* signed! */ 116 unsigned num_matches; 117 regex_t pattern; 118 smallint pattern_valid; 119 #endif 120 smallint terminated; 121 struct termios term_orig, term_less; 122 }; 123 #define G (*ptr_to_globals) 124 #define cur_fline (G.cur_fline ) 125 #define kbd_fd (G.kbd_fd ) 126 #define linepos (G.linepos ) 127 #define max_displayed_line (G.max_displayed_line) 128 #define max_fline (G.max_fline ) 129 #define max_lineno (G.max_lineno ) 130 #define width (G.width ) 131 #define eof_error (G.eof_error ) 132 #define readpos (G.readpos ) 133 #define readeof (G.readeof ) 134 #define buffer (G.buffer ) 135 #define flines (G.flines ) 136 #define empty_line_marker (G.empty_line_marker ) 137 #define num_files (G.num_files ) 138 #define current_file (G.current_file ) 139 #define filename (G.filename ) 140 #define files (G.files ) 141 #define num_marks (G.num_marks ) 142 #define mark_lines (G.mark_lines ) 143 #if ENABLE_FEATURE_LESS_REGEXP 144 #define match_lines (G.match_lines ) 145 #define match_pos (G.match_pos ) 146 #define num_matches (G.num_matches ) 147 #define pattern (G.pattern ) 148 #define pattern_valid (G.pattern_valid ) 149 #endif 150 #define terminated (G.terminated ) 151 #define term_orig (G.term_orig ) 152 #define term_less (G.term_less ) 153 #define INIT_G() do { \ 154 PTR_TO_GLOBALS = xzalloc(sizeof(G)); \ 155 empty_line_marker = "~"; \ 156 num_files = 1; \ 157 current_file = 1; \ 158 eof_error = 1; \ 159 terminated = 1; \ 160 } while (0) 115 161 116 162 /* Reset terminal input to normal */ … … 118 164 { 119 165 fflush(stdout); 120 tcsetattr(fileno(inp), TCSANOW, &term_orig); 121 } 122 123 /* Set terminal input to raw mode (taken from vi.c) */ 124 static void set_tty_raw(void) 125 { 126 tcsetattr(fileno(inp), TCSANOW, &term_vi); 166 tcsetattr(kbd_fd, TCSANOW, &term_orig); 127 167 } 128 168 129 169 /* Exit the program gracefully */ 130 static void tless_exit(int code)170 static void less_exit(int code) 131 171 { 132 172 /* TODO: We really should save the terminal state when we start, 133 and restore it when we exit. Less does this with the 134 "ti" and "te" termcap commands; can this be done with 135 only termios.h? */ 136 173 * and restore it when we exit. Less does this with the 174 * "ti" and "te" termcap commands; can this be done with 175 * only termios.h? */ 137 176 putchar('\n'); 138 exit(code); 139 } 140 141 /* Grab a character from input without requiring the return key. If the 142 character is ASCII \033, get more characters and assign certain sequences 143 special return codes. Note that this function works best with raw input. */ 144 static int tless_getch(void) 145 { 146 int input; 147 148 set_tty_raw(); 149 150 input = getc(inp); 151 /* Detect escape sequences (i.e. arrow keys) and handle 152 them accordingly */ 153 154 if (input == '\033' && getc(inp) == '[') { 155 input = getc(inp); 156 set_tty_cooked(); 157 if (input == REAL_KEY_UP) 158 return KEY_UP; 159 else if (input == REAL_KEY_DOWN) 160 return KEY_DOWN; 161 else if (input == REAL_KEY_RIGHT) 162 return KEY_RIGHT; 163 else if (input == REAL_KEY_LEFT) 164 return KEY_LEFT; 165 else if (input == REAL_PAGE_UP) 166 return PAGE_UP; 167 else if (input == REAL_PAGE_DOWN) 168 return PAGE_DOWN; 169 } 170 /* The input is a normal ASCII value */ 171 else { 172 set_tty_cooked(); 173 return input; 174 } 175 return 0; 177 fflush_stdout_and_exit(code); 176 178 } 177 179 178 180 /* Move the cursor to a position (x,y), where (0,0) is the 179 181 top-left corner of the console */ 180 static void move_cursor(int x, int y)181 { 182 printf("\033[% i;%iH", x, y);182 static void move_cursor(int line, int row) 183 { 184 printf("\033[%u;%uH", line, row); 183 185 } 184 186 185 187 static void clear_line(void) 186 188 { 187 move_cursor(height, 0); 188 printf("\033[K"); 189 } 190 191 /* This adds line numbers to every line, as the -N flag necessitates */ 192 static void add_linenumbers(void) 193 { 194 char current_line[256]; 195 int i; 196 197 for (i = 0; i <= num_flines; i++) { 198 safe_strncpy(current_line, flines[i], 256); 199 flines[i] = bb_xasprintf("%5d %s", i + 1, current_line); 200 } 201 } 202 203 static void data_readlines(void) 204 { 205 int i; 206 char current_line[256]; 207 FILE *fp; 208 209 fp = (inp_stdin) ? stdin : bb_xfopen(filename, "r"); 210 flines = NULL; 211 for (i = 0; (feof(fp)==0) && (i <= MAXLINES); i++) { 212 strcpy(current_line, ""); 213 fgets(current_line, 256, fp); 214 if (fp != stdin) 215 bb_xferror(fp, filename); 216 flines = xrealloc(flines, (i+1) * sizeof(char *)); 217 flines[i] = bb_xstrdup(current_line); 218 } 219 num_flines = i - 2; 220 221 /* Reset variables for a new file */ 222 223 line_pos = 0; 224 past_eof = 0; 225 226 fclose(fp); 227 228 if (inp == NULL) 229 inp = (inp_stdin) ? bb_xfopen(CURRENT_TTY, "r") : stdin; 230 231 if (flags & FLAG_N) 232 add_linenumbers(); 233 } 234 235 #ifdef CONFIG_FEATURE_LESS_FLAGS 236 237 /* Interestingly, writing calc_percent as a function and not a prototype saves around 32 bytes 189 printf("\033[%u;0H" CLEAR_2_EOL, max_displayed_line + 2); 190 } 191 192 static void print_hilite(const char *str) 193 { 194 printf(HIGHLIGHT"%s"NORMAL, str); 195 } 196 197 static void print_statusline(const char *str) 198 { 199 clear_line(); 200 printf(HIGHLIGHT"%.*s"NORMAL, width - 1, str); 201 } 202 203 #if ENABLE_FEATURE_LESS_REGEXP 204 static void fill_match_lines(unsigned pos); 205 #else 206 #define fill_match_lines(pos) ((void)0) 207 #endif 208 209 /* Devilishly complex routine. 210 * 211 * Has to deal with EOF and EPIPE on input, 212 * with line wrapping, with last line not ending in '\n' 213 * (possibly not ending YET!), with backspace and tabs. 214 * It reads input again if last time we got an EOF (thus supporting 215 * growing files) or EPIPE (watching output of slow process like make). 216 * 217 * Variables used: 218 * flines[] - array of lines already read. Linewrap may cause 219 * one source file line to occupy several flines[n]. 220 * flines[max_fline] - last line, possibly incomplete. 221 * terminated - 1 if flines[max_fline] is 'terminated' 222 * (if there was '\n' [which isn't stored itself, we just remember 223 * that it was seen]) 224 * max_lineno - last line's number, this one doesn't increment 225 * on line wrap, only on "real" new lines. 226 * readbuf[0..readeof-1] - small preliminary buffer. 227 * readbuf[readpos] - next character to add to current line. 228 * linepos - screen line position of next char to be read 229 * (takes into account tabs and backspaces) 230 * eof_error - < 0 error, == 0 EOF, > 0 not EOF/error 231 */ 232 static void read_lines(void) 233 { 234 #define readbuf bb_common_bufsiz1 235 char *current_line, *p; 236 USE_FEATURE_LESS_REGEXP(unsigned old_max_fline = max_fline;) 237 int w = width; 238 char last_terminated = terminated; 239 240 if (option_mask32 & FLAG_N) 241 w -= 8; 242 243 current_line = xmalloc(w); 244 p = current_line; 245 max_fline += last_terminated; 246 if (!last_terminated) { 247 const char *cp = flines[max_fline]; 248 if (option_mask32 & FLAG_N) 249 cp += 8; 250 strcpy(current_line, cp); 251 p += strlen(current_line); 252 /* linepos is still valid from previous read_lines() */ 253 } else { 254 linepos = 0; 255 } 256 257 while (1) { 258 again: 259 *p = '\0'; 260 terminated = 0; 261 while (1) { 262 char c; 263 /* if no unprocessed chars left, eat more */ 264 if (readpos >= readeof) { 265 smallint yielded = 0; 266 267 ndelay_on(0); 268 read_again: 269 eof_error = safe_read(0, readbuf, sizeof(readbuf)); 270 readpos = 0; 271 readeof = eof_error; 272 if (eof_error < 0) { 273 if (errno == EAGAIN && !yielded) { 274 /* We can hit EAGAIN while searching for regexp match. 275 * Yield is not 100% reliable solution in general, 276 * but for less it should be good enough - 277 * we give stdin supplier some CPU time to produce 278 * more input. We do it just once. 279 * Currently, we do not stop when we found the Nth 280 * occurrence we were looking for. We read till end 281 * (or double EAGAIN). TODO? */ 282 sched_yield(); 283 yielded = 1; 284 goto read_again; 285 } 286 readeof = 0; 287 if (errno != EAGAIN) 288 print_statusline("read error"); 289 } 290 ndelay_off(0); 291 292 if (eof_error <= 0) { 293 goto reached_eof; 294 } 295 } 296 c = readbuf[readpos]; 297 /* backspace? [needed for manpages] */ 298 /* <tab><bs> is (a) insane and */ 299 /* (b) harder to do correctly, so we refuse to do it */ 300 if (c == '\x8' && linepos && p[-1] != '\t') { 301 readpos++; /* eat it */ 302 linepos--; 303 /* was buggy (p could end up <= current_line)... */ 304 *--p = '\0'; 305 continue; 306 } 307 { 308 size_t new_linepos = linepos + 1; 309 if (c == '\t') { 310 new_linepos += 7; 311 new_linepos &= (~7); 312 } 313 if (new_linepos >= w) 314 break; 315 linepos = new_linepos; 316 } 317 /* ok, we will eat this char */ 318 readpos++; 319 if (c == '\n') { 320 terminated = 1; 321 linepos = 0; 322 break; 323 } 324 /* NUL is substituted by '\n'! */ 325 if (c == '\0') c = '\n'; 326 *p++ = c; 327 *p = '\0'; 328 } 329 /* Corner case: linewrap with only "" wrapping to next line */ 330 /* Looks ugly on screen, so we do not store this empty line */ 331 if (!last_terminated && !current_line[0]) { 332 last_terminated = 1; 333 max_lineno++; 334 goto again; 335 } 336 reached_eof: 337 last_terminated = terminated; 338 flines = xrealloc(flines, (max_fline+1) * sizeof(char *)); 339 if (option_mask32 & FLAG_N) { 340 /* Width of 7 preserves tab spacing in the text */ 341 flines[max_fline] = xasprintf( 342 (max_lineno <= 9999999) ? "%7u %s" : "%07u %s", 343 max_lineno % 10000000, current_line); 344 free(current_line); 345 if (terminated) 346 max_lineno++; 347 } else { 348 flines[max_fline] = xrealloc(current_line, strlen(current_line)+1); 349 } 350 if (max_fline >= MAXLINES) { 351 eof_error = 0; /* Pretend we saw EOF */ 352 break; 353 } 354 if (max_fline > cur_fline + max_displayed_line) 355 break; 356 if (eof_error <= 0) { 357 if (eof_error < 0 && errno == EAGAIN) { 358 /* not yet eof or error, reset flag (or else 359 * we will hog CPU - select() will return 360 * immediately */ 361 eof_error = 1; 362 } 363 break; 364 } 365 max_fline++; 366 current_line = xmalloc(w); 367 p = current_line; 368 linepos = 0; 369 } 370 fill_match_lines(old_max_fline); 371 #undef readbuf 372 } 373 374 #if ENABLE_FEATURE_LESS_FLAGS 375 /* Interestingly, writing calc_percent as a function saves around 32 bytes 238 376 * on my build. */ 239 377 static int calc_percent(void) 240 378 { 241 return ((100 * (line_pos + height - 2) / num_flines) + 1); 379 unsigned p = (100 * (cur_fline+max_displayed_line+1) + max_fline/2) / (max_fline+1); 380 return p <= 100 ? p : 100; 242 381 } 243 382 … … 247 386 int percentage; 248 387 249 if (!past_eof) { 250 if (!line_pos) { 251 if (num_files > 1) 252 printf("%s%s %s%i%s%i%s%i-%i/%i ", HIGHLIGHT, filename, "(file ", current_file, " of ", num_files, ") lines ", line_pos + 1, line_pos + height - 1, num_flines + 1); 253 else { 254 printf("%s%s lines %i-%i/%i ", HIGHLIGHT, filename, line_pos + 1, line_pos + height - 1, num_flines + 1); 255 } 256 } 257 else { 258 printf("%s %s lines %i-%i/%i ", HIGHLIGHT, filename, line_pos + 1, line_pos + height - 1, num_flines + 1); 259 } 260 261 if (line_pos == num_flines - height + 2) { 262 printf("(END) %s", NORMAL); 263 if ((num_files > 1) && (current_file != num_files)) 264 printf("%s- Next: %s%s", HIGHLIGHT, files[current_file], NORMAL); 265 } 266 else { 267 percentage = calc_percent(); 268 printf("%i%% %s", percentage, NORMAL); 269 } 270 } 271 else { 272 printf("%s%s lines %i-%i/%i (END) ", HIGHLIGHT, filename, line_pos + 1, num_flines + 1, num_flines + 1); 273 if ((num_files > 1) && (current_file != num_files)) 274 printf("- Next: %s", files[current_file]); 275 printf("%s", NORMAL); 276 } 277 } 278 279 /* Print a status line if -m was specified */ 280 static void medium_status_print(void) 281 { 282 int percentage; 388 clear_line(); 389 printf(HIGHLIGHT"%s", filename); 390 if (num_files > 1) 391 printf(" (file %i of %i)", current_file, num_files); 392 printf(" lines %i-%i/%i ", 393 cur_fline + 1, cur_fline + max_displayed_line + 1, 394 max_fline + 1); 395 if (cur_fline >= max_fline - max_displayed_line) { 396 printf("(END)"NORMAL); 397 if (num_files > 1 && current_file != num_files) 398 printf(HIGHLIGHT" - next: %s"NORMAL, files[current_file]); 399 return; 400 } 283 401 percentage = calc_percent(); 284 285 if (!line_pos) 286 printf("%s%s %i%%%s", HIGHLIGHT, filename, percentage, NORMAL); 287 else if (line_pos == num_flines - height + 2) 288 printf("%s(END)%s", HIGHLIGHT, NORMAL); 289 else 290 printf("%s%i%%%s", HIGHLIGHT, percentage, NORMAL); 402 printf("%i%%"NORMAL, percentage); 291 403 } 292 404 #endif … … 295 407 static void status_print(void) 296 408 { 409 const char *p; 410 297 411 /* Change the status if flags have been set */ 298 #if def CONFIG_FEATURE_LESS_FLAGS299 if ( flags & FLAG_M)412 #if ENABLE_FEATURE_LESS_FLAGS 413 if (option_mask32 & (FLAG_M|FLAG_m)) { 300 414 m_status_print(); 301 else if (flags & FLAG_m)302 medium_status_print();415 return; 416 } 303 417 /* No flags set */ 304 else { 305 #endif 306 if (!line_pos) { 307 printf("%s%s %s", HIGHLIGHT, filename, NORMAL); 308 if (num_files > 1) 309 printf("%s%s%i%s%i%s%s", HIGHLIGHT, "(file ", current_file, " of ", num_files, ")", NORMAL); 418 #endif 419 420 clear_line(); 421 if (cur_fline && cur_fline < max_fline - max_displayed_line) { 422 putchar(':'); 423 return; 424 } 425 p = "(END)"; 426 if (!cur_fline) 427 p = filename; 428 if (num_files > 1) { 429 printf(HIGHLIGHT"%s (file %i of %i)"NORMAL, 430 p, current_file, num_files); 431 return; 432 } 433 print_hilite(p); 434 } 435 436 static void cap_cur_fline(int nlines) 437 { 438 int diff; 439 if (cur_fline < 0) 440 cur_fline = 0; 441 if (cur_fline + max_displayed_line > max_fline + TILDES) { 442 cur_fline -= nlines; 443 if (cur_fline < 0) 444 cur_fline = 0; 445 diff = max_fline - (cur_fline + max_displayed_line) + TILDES; 446 /* As the number of lines requested was too large, we just move 447 to the end of the file */ 448 if (diff > 0) 449 cur_fline += diff; 450 } 451 } 452 453 static const char controls[] ALIGN1 = 454 /* NUL: never encountered; TAB: not converted */ 455 /**/"\x01\x02\x03\x04\x05\x06\x07\x08" "\x0a\x0b\x0c\x0d\x0e\x0f" 456 "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" 457 "\x7f\x9b"; /* DEL and infamous Meta-ESC :( */ 458 static const char ctrlconv[] ALIGN1 = 459 /* '\n': it's a former NUL - subst with '@', not 'J' */ 460 "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x40\x4b\x4c\x4d\x4e\x4f" 461 "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"; 462 463 #if ENABLE_FEATURE_LESS_REGEXP 464 static void print_found(const char *line) 465 { 466 int match_status; 467 int eflags; 468 char *growline; 469 regmatch_t match_structs; 470 471 char buf[width]; 472 const char *str = line; 473 char *p = buf; 474 size_t n; 475 476 while (*str) { 477 n = strcspn(str, controls); 478 if (n) { 479 if (!str[n]) break; 480 memcpy(p, str, n); 481 p += n; 482 str += n; 310 483 } 311 else if (line_pos == num_flines - height + 2) { 312 printf("%s%s %s", HIGHLIGHT, "(END)", NORMAL); 313 if ((num_files > 1) && (current_file != num_files)) 314 printf("%s%s%s%s", HIGHLIGHT, "- Next: ", files[current_file], NORMAL); 484 n = strspn(str, controls); 485 memset(p, '.', n); 486 p += n; 487 str += n; 488 } 489 strcpy(p, str); 490 491 /* buf[] holds quarantined version of str */ 492 493 /* Each part of the line that matches has the HIGHLIGHT 494 and NORMAL escape sequences placed around it. 495 NB: we regex against line, but insert text 496 from quarantined copy (buf[]) */ 497 str = buf; 498 growline = NULL; 499 eflags = 0; 500 goto start; 501 502 while (match_status == 0) { 503 char *new = xasprintf("%s%.*s"HIGHLIGHT"%.*s"NORMAL, 504 growline ? : "", 505 match_structs.rm_so, str, 506 match_structs.rm_eo - match_structs.rm_so, 507 str + match_structs.rm_so); 508 free(growline); growline = new; 509 str += match_structs.rm_eo; 510 line += match_structs.rm_eo; 511 eflags = REG_NOTBOL; 512 start: 513 /* Most of the time doesn't find the regex, optimize for that */ 514 match_status = regexec(&pattern, line, 1, &match_structs, eflags); 515 } 516 517 if (!growline) { 518 printf(CLEAR_2_EOL"%s\n", str); 519 return; 520 } 521 printf(CLEAR_2_EOL"%s%s\n", growline, str); 522 free(growline); 523 } 524 #else 525 void print_found(const char *line); 526 #endif 527 528 static void print_ascii(const char *str) 529 { 530 char buf[width]; 531 char *p; 532 size_t n; 533 534 printf(CLEAR_2_EOL); 535 while (*str) { 536 n = strcspn(str, controls); 537 if (n) { 538 if (!str[n]) break; 539 printf("%.*s", (int) n, str); 540 str += n; 315 541 } 316 else { 317 putchar(':'); 318 } 319 #ifdef CONFIG_FEATURE_LESS_FLAGS 320 } 321 #endif 542 n = strspn(str, controls); 543 p = buf; 544 do { 545 if (*str == 0x7f) 546 *p++ = '?'; 547 else if (*str == (char)0x9b) 548 /* VT100's CSI, aka Meta-ESC. Who's inventor? */ 549 /* I want to know who committed this sin */ 550 *p++ = '{'; 551 else 552 *p++ = ctrlconv[(unsigned char)*str]; 553 str++; 554 } while (--n); 555 *p = '\0'; 556 print_hilite(buf); 557 } 558 puts(str); 322 559 } 323 560 … … 327 564 int i; 328 565 329 printf("%s", CLEAR); 330 if (num_flines >= height - 2) { 331 for (i = 0; i < height - 1; i++) 332 printf("%s", buffer[i]); 333 } 334 else { 335 for (i = 1; i < (height - 1 - num_flines); i++) 336 putchar('\n'); 337 for (i = 0; i < height - 1; i++) 338 printf("%s", buffer[i]); 339 } 340 566 move_cursor(0, 0); 567 for (i = 0; i <= max_displayed_line; i++) 568 if (pattern_valid) 569 print_found(buffer[i]); 570 else 571 print_ascii(buffer[i]); 341 572 status_print(); 342 573 } 343 574 344 /* Initialise the buffer */ 345 static void buffer_init(void) 575 static void buffer_fill_and_print(void) 346 576 { 347 577 int i; 348 349 if (buffer == NULL) { 350 /* malloc the number of lines needed for the buffer */ 351 buffer = xrealloc(buffer, height * sizeof(char *)); 352 } else { 353 for (i = 0; i < (height - 1); i++) 354 free(buffer[i]); 355 } 356 357 /* Fill the buffer until the end of the file or the 358 end of the buffer is reached */ 359 for (i = 0; (i < (height - 1)) && (i <= num_flines); i++) { 360 buffer[i] = bb_xstrdup(flines[i]); 361 } 362 363 /* If the buffer still isn't full, fill it with blank lines */ 364 for (; i < (height - 1); i++) { 365 buffer[i] = bb_xstrdup(""); 366 } 578 for (i = 0; i <= max_displayed_line && cur_fline + i <= max_fline; i++) { 579 buffer[i] = flines[cur_fline + i]; 580 } 581 for (; i <= max_displayed_line; i++) { 582 buffer[i] = empty_line_marker; 583 } 584 buffer_print(); 367 585 } 368 586 … … 370 588 static void buffer_down(int nlines) 371 589 { 590 cur_fline += nlines; 591 read_lines(); 592 cap_cur_fline(nlines); 593 buffer_fill_and_print(); 594 } 595 596 static void buffer_up(int nlines) 597 { 598 cur_fline -= nlines; 599 if (cur_fline < 0) cur_fline = 0; 600 read_lines(); 601 buffer_fill_and_print(); 602 } 603 604 static void buffer_line(int linenum) 605 { 606 if (linenum < 0) 607 linenum = 0; 608 cur_fline = linenum; 609 read_lines(); 610 if (linenum + max_displayed_line > max_fline) 611 linenum = max_fline - max_displayed_line + TILDES; 612 if (linenum < 0) 613 linenum = 0; 614 cur_fline = linenum; 615 buffer_fill_and_print(); 616 } 617 618 static void open_file_and_read_lines(void) 619 { 620 if (filename) { 621 int fd = xopen(filename, O_RDONLY); 622 dup2(fd, 0); 623 if (fd) close(fd); 624 } else { 625 /* "less" with no arguments in argv[] */ 626 /* For status line only */ 627 filename = xstrdup(bb_msg_standard_input); 628 } 629 readpos = 0; 630 readeof = 0; 631 linepos = 0; 632 terminated = 1; 633 read_lines(); 634 } 635 636 /* Reinitialize everything for a new file - free the memory and start over */ 637 static void reinitialize(void) 638 { 372 639 int i; 373 640 374 if (!past_eof) { 375 if (line_pos + (height - 3) + nlines < num_flines) { 376 line_pos += nlines; 377 for (i = 0; i < (height - 1); i++) { 378 free(buffer[i]); 379 buffer[i] = bb_xstrdup(flines[line_pos + i]); 380 } 641 if (flines) { 642 for (i = 0; i <= max_fline; i++) 643 free((void*)(flines[i])); 644 free(flines); 645 flines = NULL; 646 } 647 648 max_fline = -1; 649 cur_fline = 0; 650 max_lineno = 0; 651 open_file_and_read_lines(); 652 buffer_fill_and_print(); 653 } 654 655 static void getch_nowait(char* input, int sz) 656 { 657 ssize_t rd; 658 fd_set readfds; 659 again: 660 fflush(stdout); 661 662 /* NB: select returns whenever read will not block. Therefore: 663 * (a) with O_NONBLOCK'ed fds select will return immediately 664 * (b) if eof is reached, select will also return 665 * because read will immediately return 0 bytes. 666 * Even if select says that input is available, read CAN block 667 * (switch fd into O_NONBLOCK'ed mode to avoid it) 668 */ 669 FD_ZERO(&readfds); 670 if (max_fline <= cur_fline + max_displayed_line 671 && eof_error > 0 /* did NOT reach eof yet */ 672 ) { 673 /* We are interested in stdin */ 674 FD_SET(0, &readfds); 675 } 676 FD_SET(kbd_fd, &readfds); 677 tcsetattr(kbd_fd, TCSANOW, &term_less); 678 select(kbd_fd + 1, &readfds, NULL, NULL, NULL); 679 680 input[0] = '\0'; 681 ndelay_on(kbd_fd); 682 rd = read(kbd_fd, input, sz); 683 ndelay_off(kbd_fd); 684 if (rd < 0) { 685 /* No keyboard input, but we have input on stdin! */ 686 if (errno != EAGAIN) /* Huh?? */ 687 return; 688 read_lines(); 689 buffer_fill_and_print(); 690 goto again; 691 } 692 } 693 694 /* Grab a character from input without requiring the return key. If the 695 * character is ASCII \033, get more characters and assign certain sequences 696 * special return codes. Note that this function works best with raw input. */ 697 static int less_getch(void) 698 { 699 char input[16]; 700 unsigned i; 701 again: 702 memset(input, 0, sizeof(input)); 703 getch_nowait(input, sizeof(input)); 704 705 /* Detect escape sequences (i.e. arrow keys) and handle 706 * them accordingly */ 707 if (input[0] == '\033' && input[1] == '[') { 708 set_tty_cooked(); 709 i = input[2] - REAL_KEY_UP; 710 if (i < 4) 711 return 20 + i; 712 i = input[2] - REAL_PAGE_UP; 713 if (i < 4) 714 return 24 + i; 715 if (input[2] == REAL_KEY_HOME_XTERM) 716 return KEY_HOME; 717 if (input[2] == REAL_KEY_HOME_ALT) 718 return KEY_HOME; 719 if (input[2] == REAL_KEY_END_XTERM) 720 return KEY_END; 721 if (input[2] == REAL_KEY_END_ALT) 722 return KEY_END; 723 return 0; 724 } 725 /* Reject almost all control chars */ 726 i = input[0]; 727 if (i < ' ' && i != 0x0d && i != 8) goto again; 728 set_tty_cooked(); 729 return i; 730 } 731 732 static char* less_gets(int sz) 733 { 734 char c; 735 int i = 0; 736 char *result = xzalloc(1); 737 while (1) { 738 fflush(stdout); 739 740 /* I be damned if I know why is it needed *repeatedly*, 741 * but it is needed. Is it because of stdio? */ 742 tcsetattr(kbd_fd, TCSANOW, &term_less); 743 744 c = '\0'; 745 read(kbd_fd, &c, 1); 746 if (c == 0x0d) 747 return result; 748 if (c == 0x7f) 749 c = 8; 750 if (c == 8 && i) { 751 printf("\x8 \x8"); 752 i--; 381 753 } 382 else { 383 /* As the number of lines requested was too large, we just move 384 to the end of the file */ 385 while (line_pos + (height - 3) + 1 < num_flines) { 386 line_pos += 1; 387 for (i = 0; i < (height - 1); i++) { 388 free(buffer[i]); 389 buffer[i] = bb_xstrdup(flines[line_pos + i]); 390 } 391 } 392 } 393 394 /* We exit if the -E flag has been set */ 395 if ((flags & FLAG_E) && (line_pos + (height - 2) == num_flines)) 396 tless_exit(0); 397 } 398 } 399 400 static void buffer_up(int nlines) 401 { 402 int i; 403 int tilde_line; 404 405 if (!past_eof) { 406 if (line_pos - nlines >= 0) { 407 line_pos -= nlines; 408 for (i = 0; i < (height - 1); i++) { 409 free(buffer[i]); 410 buffer[i] = bb_xstrdup(flines[line_pos + i]); 411 } 412 } 413 else { 414 /* As the requested number of lines to move was too large, we 415 move one line up at a time until we can't. */ 416 while (line_pos != 0) { 417 line_pos -= 1; 418 for (i = 0; i < (height - 1); i++) { 419 free(buffer[i]); 420 buffer[i] = bb_xstrdup(flines[line_pos + i]); 421 } 422 } 423 } 424 } 425 else { 426 /* Work out where the tildes start */ 427 tilde_line = num_flines - line_pos + 3; 428 429 line_pos -= nlines; 430 /* Going backwards nlines lines has taken us to a point where 431 nothing is past the EOF, so we revert to normal. */ 432 if (line_pos < num_flines - height + 3) { 433 past_eof = 0; 434 buffer_up(nlines); 435 } 436 else { 437 /* We only move part of the buffer, as the rest 438 is past the EOF */ 439 for (i = 0; i < (height - 1); i++) { 440 free(buffer[i]); 441 if (i < tilde_line - nlines + 1) 442 buffer[i] = bb_xstrdup(flines[line_pos + i]); 443 else { 444 if (line_pos >= num_flines - height + 2) 445 buffer[i] = bb_xstrdup("~\n"); 446 } 447 } 448 } 449 } 450 } 451 452 static void buffer_line(int linenum) 453 { 454 int i; 455 past_eof = 0; 456 457 if (linenum < 0 || linenum > num_flines) { 458 clear_line(); 459 printf("%s%s%i%s", HIGHLIGHT, "Cannot seek to line number ", linenum + 1, NORMAL); 460 } 461 else if (linenum < (num_flines - height - 2)) { 462 for (i = 0; i < (height - 1); i++) { 463 free(buffer[i]); 464 buffer[i] = bb_xstrdup(flines[linenum + i]); 465 } 466 line_pos = linenum; 467 buffer_print(); 468 } 469 else { 470 for (i = 0; i < (height - 1); i++) { 471 free(buffer[i]); 472 if (linenum + i < num_flines + 2) 473 buffer[i] = bb_xstrdup(flines[linenum + i]); 474 else 475 buffer[i] = bb_xstrdup((flags & FLAG_TILDE) ? "\n" : "~\n"); 476 } 477 line_pos = linenum; 478 /* Set past_eof so buffer_down and buffer_up act differently */ 479 past_eof = 1; 480 buffer_print(); 481 } 482 } 483 484 /* Reinitialise everything for a new file - free the memory and start over */ 485 static void reinitialise(void) 486 { 487 int i; 488 489 for (i = 0; i <= num_flines; i++) 490 free(flines[i]); 491 free(flines); 492 493 data_readlines(); 494 buffer_init(); 495 buffer_print(); 754 if (c < ' ') 755 continue; 756 if (i >= width - sz - 1) 757 continue; /* len limit */ 758 putchar(c); 759 result[i++] = c; 760 result = xrealloc(result, i+1); 761 result[i] = '\0'; 762 } 496 763 } 497 764 498 765 static void examine_file(void) 499 766 { 500 int newline_offset; 501 502 clear_line(); 503 printf("Examine: "); 504 fgets(filename, 256, inp); 505 506 /* As fgets adds a newline to the end of an input string, we 507 need to remove it */ 508 newline_offset = strlen(filename) - 1; 509 filename[newline_offset] = '\0'; 510 511 files[num_files] = bb_xstrdup(filename); 767 print_statusline("Examine: "); 768 free(filename); 769 filename = less_gets(sizeof("Examine: ")-1); 770 /* files start by = argv. why we assume that argv is infinitely long?? 771 files[num_files] = filename; 512 772 current_file = num_files + 1; 513 num_files++; 514 515 inp_stdin = 0;516 reinitiali se();773 num_files++; */ 774 files[0] = filename; 775 num_files = current_file = 1; 776 reinitialize(); 517 777 } 518 778 … … 520 780 * -1: go back one file 521 781 * 0: go to the first file 522 * 1: go forward one file 523 */ 782 * 1: go forward one file */ 524 783 static void change_file(int direction) 525 784 { 526 785 if (current_file != ((direction > 0) ? num_files : 1)) { 527 786 current_file = direction ? current_file + direction : 1; 528 strcpy(filename, files[current_file - 1]); 529 reinitialise(); 530 } 531 else { 532 clear_line(); 533 printf("%s%s%s", HIGHLIGHT, (direction > 0) ? "No next file" : "No previous file", NORMAL); 787 free(filename); 788 filename = xstrdup(files[current_file - 1]); 789 reinitialize(); 790 } else { 791 print_statusline(direction > 0 ? "No next file" : "No previous file"); 534 792 } 535 793 } … … 538 796 { 539 797 int i; 798 799 if (num_files < 2) 800 return; 540 801 541 802 if (current_file != 1) { … … 544 805 files[i - 2] = files[i - 1]; 545 806 num_files--; 546 buffer_print(); 547 } 548 else { 807 } else { 549 808 change_file(1); 550 809 for (i = 2; i <= num_files; i++) … … 552 811 num_files--; 553 812 current_file--; 554 buffer_print();555 813 } 556 814 } … … 561 819 562 820 /* Clear the current line and print a prompt */ 563 clear_line(); 564 printf(" :"); 565 566 keypress = tless_getch(); 821 print_statusline(" :"); 822 823 keypress = less_getch(); 567 824 switch (keypress) { 568 case 'd': 569 remove_current_file(); 570 break; 571 case 'e': 572 examine_file(); 573 break; 574 #ifdef CONFIG_FEATURE_LESS_FLAGS 575 case 'f': 576 clear_line(); 577 m_status_print(); 578 break; 579 #endif 580 case 'n': 581 change_file(1); 582 break; 583 case 'p': 584 change_file(-1); 585 break; 586 case 'q': 587 tless_exit(0); 588 break; 589 case 'x': 590 change_file(0); 591 break; 592 default: 593 break; 594 } 595 } 596 597 #ifdef CONFIG_FEATURE_LESS_REGEXP 598 /* The below two regular expression handler functions NEED development. */ 599 600 /* Get a regular expression from the user, and then go through the current 601 file line by line, running a processing regex function on each one. */ 602 603 static char *process_regex_on_line(char *line, regex_t *pattern, int action) 604 { 605 /* This function takes the regex and applies it to the line. 606 Each part of the line that matches has the HIGHLIGHT 607 and NORMAL escape sequences placed around it by 608 insert_highlights if action = 1, or has the escape sequences 609 removed if action = 0, and then the line is returned. */ 610 int match_status; 611 char *line2 = (char *) xmalloc((sizeof(char) * (strlen(line) + 1)) + 64); 612 char *growline = ""; 613 regmatch_t match_structs; 614 615 line2 = bb_xstrdup(line); 616 617 match_found = 0; 618 match_status = regexec(pattern, line2, 1, &match_structs, 0); 619 620 while (match_status == 0) { 621 if (match_found == 0) 622 match_found = 1; 623 624 if (action) { 625 growline = bb_xasprintf("%s%.*s%s%.*s%s", growline, match_structs.rm_so, line2, HIGHLIGHT, match_structs.rm_eo - match_structs.rm_so, line2 + match_structs.rm_so, NORMAL); 825 case 'd': 826 remove_current_file(); 827 break; 828 case 'e': 829 examine_file(); 830 break; 831 #if ENABLE_FEATURE_LESS_FLAGS 832 case 'f': 833 m_status_print(); 834 break; 835 #endif 836 case 'n': 837 change_file(1); 838 break; 839 case 'p': 840 change_file(-1); 841 break; 842 case 'q': 843 less_exit(0); 844 break; 845 case 'x': 846 change_file(0); 847 break; 848 } 849 } 850 851 #if ENABLE_FEATURE_LESS_REGEXP 852 static void normalize_match_pos(int match) 853 { 854 if (match >= num_matches) 855 match = num_matches - 1; 856 if (match < 0) 857 match = 0; 858 match_pos = match; 859 } 860 861 static void goto_match(int match) 862 { 863 int sv; 864 865 if (!pattern_valid) 866 return; 867 if (match < 0) 868 match = 0; 869 sv = cur_fline; 870 /* Try to find next match if eof isn't reached yet */ 871 if (match >= num_matches && eof_error > 0) { 872 cur_fline = MAXLINES; /* look as far as needed */ 873 read_lines(); 874 } 875 if (num_matches) { 876 cap_cur_fline(cur_fline); 877 normalize_match_pos(match); 878 buffer_line(match_lines[match_pos]); 879 } else { 880 cur_fline = sv; 881 print_statusline("No matches found"); 882 } 883 } 884 885 static void fill_match_lines(unsigned pos) 886 { 887 if (!pattern_valid) 888 return; 889 /* Run the regex on each line of the current file */ 890 while (pos <= max_fline) { 891 /* If this line matches */ 892 if (regexec(&pattern, flines[pos], 0, NULL, 0) == 0 893 /* and we didn't match it last time */ 894 && !(num_matches && match_lines[num_matches-1] == pos) 895 ) { 896 match_lines = xrealloc(match_lines, (num_matches+1) * sizeof(int)); 897 match_lines[num_matches++] = pos; 626 898 } 627 else { 628 growline = bb_xasprintf("%s%.*s%.*s", growline, match_structs.rm_so - 4, line2, match_structs.rm_eo - match_structs.rm_so, line2 + match_structs.rm_so); 629 } 630 631 line2 += match_structs.rm_eo; 632 match_status = regexec(pattern, line2, 1, &match_structs, REG_NOTBOL); 633 } 634 635 growline = bb_xasprintf("%s%s", growline, line2); 636 637 return (match_found ? growline : line); 638 639 free(growline); 640 free(line2); 641 } 642 643 static void goto_match(int match) 644 { 645 /* This goes to a specific match - all line positions of matches are 646 stored within the match_lines[] array. */ 647 if ((match < num_matches) && (match >= 0)) { 648 buffer_line(match_lines[match]); 649 match_pos = match; 899 pos++; 650 900 } 651 901 } … … 653 903 static void regex_process(void) 654 904 { 655 char uncomp_regex[100]; 656 char *current_line; 657 int i; 658 int j = 0; 659 regex_t pattern; 905 char *uncomp_regex, *err; 906 907 /* Reset variables */ 908 free(match_lines); 909 match_lines = NULL; 910 match_pos = 0; 911 num_matches = 0; 912 if (pattern_valid) { 913 regfree(&pattern); 914 pattern_valid = 0; 915 } 916 660 917 /* Get the uncompiled regular expression from the user */ 661 918 clear_line(); 662 putchar((match_backwards) ? '?' : '/'); 663 uncomp_regex[0] = 0; 664 fgets(uncomp_regex, sizeof(uncomp_regex), inp); 665 666 if (strlen(uncomp_regex) == 1) { 667 if (num_matches) 668 goto_match(match_backwards ? match_pos - 1 : match_pos + 1); 669 else 670 buffer_print(); 919 putchar((option_mask32 & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/'); 920 uncomp_regex = less_gets(1); 921 if (!uncomp_regex[0]) { 922 free(uncomp_regex); 923 buffer_print(); 671 924 return; 672 925 } 673 uncomp_regex[strlen(uncomp_regex) - 1] = '\0'; 674 926 675 927 /* Compile the regex and check for errors */ 676 xregcomp(&pattern, uncomp_regex, 0); 677 678 if (num_matches) { 679 /* Get rid of all the highlights we added previously */ 680 for (i = 0; i <= num_flines; i++) { 681 current_line = process_regex_on_line(flines[i], &old_pattern, 0); 682 flines[i] = bb_xstrdup(current_line); 683 } 684 } 685 old_pattern = pattern; 686 687 /* Reset variables */ 688 match_lines = xrealloc(match_lines, sizeof(int)); 689 match_lines[0] = -1; 928 err = regcomp_or_errmsg(&pattern, uncomp_regex, 0); 929 free(uncomp_regex); 930 if (err) { 931 print_statusline(err); 932 free(err); 933 return; 934 } 935 936 pattern_valid = 1; 690 937 match_pos = 0; 691 num_matches = 0; 692 match_found = 0; 693 /* Run the regex on each line of the current file here */ 694 for (i = 0; i <= num_flines; i++) { 695 current_line = process_regex_on_line(flines[i], &pattern, 1); 696 flines[i] = bb_xstrdup(current_line); 697 if (match_found) { 698 match_lines = xrealloc(match_lines, (j + 1) * sizeof(int)); 699 match_lines[j] = i; 700 j++; 701 } 702 } 703 704 num_matches = j; 705 if ((match_lines[0] != -1) && (num_flines > height - 2)) { 706 if (match_backwards) { 707 for (i = 0; i < num_matches; i++) { 708 if (match_lines[i] > line_pos) { 709 match_pos = i - 1; 710 buffer_line(match_lines[match_pos]); 711 break; 712 } 713 } 714 } 715 else 716 buffer_line(match_lines[0]); 717 } 718 else 719 buffer_init(); 938 fill_match_lines(0); 939 while (match_pos < num_matches) { 940 if (match_lines[match_pos] > cur_fline) 941 break; 942 match_pos++; 943 } 944 if (option_mask32 & LESS_STATE_MATCH_BACKWARDS) 945 match_pos--; 946 947 /* It's possible that no matches are found yet. 948 * goto_match() will read input looking for match, 949 * if needed */ 950 goto_match(match_pos); 720 951 } 721 952 #endif … … 725 956 int i = 1; 726 957 int num; 727 char num_input[ 80];958 char num_input[sizeof(int)*4]; /* more than enough */ 728 959 char keypress; 729 char *endptr;730 960 731 961 num_input[0] = first_digit; … … 735 965 printf(":%c", first_digit); 736 966 737 /* Receive input until a letter is given (max 80 chars)*/ 738 while((i < 80) && (num_input[i] = tless_getch()) && isdigit(num_input[i])) { 967 /* Receive input until a letter is given */ 968 while (i < sizeof(num_input)-1) { 969 num_input[i] = less_getch(); 970 if (!num_input[i] || !isdigit(num_input[i])) 971 break; 739 972 putchar(num_input[i]); 740 973 i++; … … 744 977 keypress = num_input[i]; 745 978 num_input[i] = '\0'; 746 num = strtol(num_input, &endptr, 10); 747 if (endptr==num_input || *endptr!='\0' || num < 1 || num > MAXLINES) { 979 num = bb_strtou(num_input, NULL, 10); 980 /* on format error, num == -1 */ 981 if (num < 1 || num > MAXLINES) { 748 982 buffer_print(); 749 983 return; … … 752 986 /* We now know the number and the letter entered, so we process them */ 753 987 switch (keypress) { 754 case KEY_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015': 755 buffer_down(num); 756 break; 757 case KEY_UP: case 'b': case 'w': case 'y': case 'u': 758 buffer_up(num); 759 break; 760 case 'g': case '<': case 'G': case '>': 761 if (num_flines >= height - 2) 762 buffer_line(num - 1); 763 break; 764 case 'p': case '%': 765 buffer_line(((num / 100) * num_flines) - 1); 766 break; 767 #ifdef CONFIG_FEATURE_LESS_REGEXP 768 case 'n': 769 goto_match(match_pos + num); 770 break; 771 case '/': 772 match_backwards = 0; 773 regex_process(); 774 break; 775 case '?': 776 match_backwards = 1; 777 regex_process(); 778 break; 779 #endif 780 default: 781 break; 782 } 783 } 784 785 #ifdef CONFIG_FEATURE_LESS_FLAGCS 988 case KEY_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015': 989 buffer_down(num); 990 break; 991 case KEY_UP: case 'b': case 'w': case 'y': case 'u': 992 buffer_up(num); 993 break; 994 case 'g': case '<': case 'G': case '>': 995 cur_fline = num + max_displayed_line; 996 read_lines(); 997 buffer_line(num - 1); 998 break; 999 case 'p': case '%': 1000 num = num * (max_fline / 100); /* + max_fline / 2; */ 1001 cur_fline = num + max_displayed_line; 1002 read_lines(); 1003 buffer_line(num); 1004 break; 1005 #if ENABLE_FEATURE_LESS_REGEXP 1006 case 'n': 1007 goto_match(match_pos + num); 1008 break; 1009 case '/': 1010 option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS; 1011 regex_process(); 1012 break; 1013 case '?': 1014 option_mask32 |= LESS_STATE_MATCH_BACKWARDS; 1015 regex_process(); 1016 break; 1017 #endif 1018 } 1019 } 1020 1021 #if ENABLE_FEATURE_LESS_FLAGCS 786 1022 static void flag_change(void) 787 1023 { … … 790 1026 clear_line(); 791 1027 putchar('-'); 792 keypress = tless_getch();1028 keypress = less_getch(); 793 1029 794 1030 switch (keypress) { 795 case 'M': 796 flags ^= FLAG_M; 797 break; 798 case 'm': 799 flags ^= FLAG_m; 800 break; 801 case 'E': 802 flags ^= FLAG_E; 803 break; 804 case '~': 805 flags ^= FLAG_TILDE; 806 break; 807 default: 808 break; 1031 case 'M': 1032 option_mask32 ^= FLAG_M; 1033 break; 1034 case 'm': 1035 option_mask32 ^= FLAG_m; 1036 break; 1037 case 'E': 1038 option_mask32 ^= FLAG_E; 1039 break; 1040 case '~': 1041 option_mask32 ^= FLAG_TILDE; 1042 break; 809 1043 } 810 1044 } … … 817 1051 clear_line(); 818 1052 putchar('_'); 819 keypress = tless_getch();1053 keypress = less_getch(); 820 1054 821 1055 switch (keypress) { 822 823 flag_val = flags& FLAG_M;824 825 826 flag_val = flags& FLAG_m;827 828 829 flag_val = flags& FLAG_TILDE;830 831 832 flag_val = flags& FLAG_N;833 834 835 flag_val = flags& FLAG_E;836 837 838 839 1056 case 'M': 1057 flag_val = option_mask32 & FLAG_M; 1058 break; 1059 case 'm': 1060 flag_val = option_mask32 & FLAG_m; 1061 break; 1062 case '~': 1063 flag_val = option_mask32 & FLAG_TILDE; 1064 break; 1065 case 'N': 1066 flag_val = option_mask32 & FLAG_N; 1067 break; 1068 case 'E': 1069 flag_val = option_mask32 & FLAG_E; 1070 break; 1071 default: 1072 flag_val = 0; 1073 break; 840 1074 } 841 1075 842 1076 clear_line(); 843 printf("%s%s%i%s", HIGHLIGHT, "The status of the flag is: ", flag_val != 0, NORMAL); 844 } 845 #endif 846 847 static void full_repaint(void) 848 { 849 int temp_line_pos = line_pos; 850 data_readlines(); 851 buffer_init(); 852 buffer_line(temp_line_pos); 853 } 854 1077 printf(HIGHLIGHT"The status of the flag is: %u"NORMAL, flag_val != 0); 1078 } 1079 #endif 855 1080 856 1081 static void save_input_to_file(void) 857 1082 { 858 char current_line[256]; 1083 const char *msg = ""; 1084 char *current_line; 859 1085 int i; 860 1086 FILE *fp; 861 1087 862 clear_line(); 863 printf("Log file: "); 864 fgets(current_line, 256, inp); 865 current_line[strlen(current_line) - 1] = '\0'; 866 if (strlen(current_line) > 1) { 867 fp = bb_xfopen(current_line, "w"); 868 for (i = 0; i < num_flines; i++) 869 fprintf(fp, "%s", flines[i]); 1088 print_statusline("Log file: "); 1089 current_line = less_gets(sizeof("Log file: ")-1); 1090 if (strlen(current_line) > 0) { 1091 fp = fopen(current_line, "w"); 1092 if (!fp) { 1093 msg = "Error opening log file"; 1094 goto ret; 1095 } 1096 for (i = 0; i <= max_fline; i++) 1097 fprintf(fp, "%s\n", flines[i]); 870 1098 fclose(fp); 871 buffer_print(); 872 } 873 else 874 printf("%sNo log file%s", HIGHLIGHT, NORMAL); 875 } 876 877 #ifdef CONFIG_FEATURE_LESS_MARKS 1099 msg = "Done"; 1100 } 1101 ret: 1102 print_statusline(msg); 1103 free(current_line); 1104 } 1105 1106 #if ENABLE_FEATURE_LESS_MARKS 878 1107 static void add_mark(void) 879 1108 { 880 1109 int letter; 881 int mark_line; 882 883 clear_line(); 884 printf("Mark: "); 885 letter = tless_getch(); 1110 1111 print_statusline("Mark: "); 1112 letter = less_getch(); 886 1113 887 1114 if (isalpha(letter)) { 888 mark_line = line_pos;889 890 1115 /* If we exceed 15 marks, start overwriting previous ones */ 891 1116 if (num_marks == 14) … … 893 1118 894 1119 mark_lines[num_marks][0] = letter; 895 mark_lines[num_marks][1] = line_pos;1120 mark_lines[num_marks][1] = cur_fline; 896 1121 num_marks++; 897 } 898 else { 899 clear_line(); 900 printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL); 1122 } else { 1123 print_statusline("Invalid mark letter"); 901 1124 } 902 1125 } … … 907 1130 int i; 908 1131 909 clear_line(); 910 printf("Go to mark: "); 911 letter = tless_getch(); 1132 print_statusline("Go to mark: "); 1133 letter = less_getch(); 912 1134 clear_line(); 913 1135 … … 918 1140 break; 919 1141 } 920 if ((num_marks == 14) && (letter != mark_lines[14][0])) 921 printf("%s%s%s", HIGHLIGHT, "Mark not set", NORMAL); 922 } 923 else 924 printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL); 925 } 926 #endif 927 928 929 #ifdef CONFIG_FEATURE_LESS_BRACKETS 930 1142 if (num_marks == 14 && letter != mark_lines[14][0]) 1143 print_statusline("Mark not set"); 1144 } else 1145 print_statusline("Invalid mark letter"); 1146 } 1147 #endif 1148 1149 #if ENABLE_FEATURE_LESS_BRACKETS 931 1150 static char opp_bracket(char bracket) 932 1151 { 933 1152 switch (bracket) { 934 case '{': case '[': 935 return bracket + 2; 936 break; 937 case '(': 938 return ')'; 939 break; 940 case '}': case ']': 941 return bracket - 2; 942 break; 943 case ')': 944 return '('; 945 break; 946 default: 947 return 0; 948 break; 949 } 1153 case '{': case '[': 1154 return bracket + 2; 1155 case '(': 1156 return ')'; 1157 case '}': case ']': 1158 return bracket - 2; 1159 case ')': 1160 return '('; 1161 } 1162 return 0; 950 1163 } 951 1164 … … 955 1168 int i; 956 1169 957 clear_line(); 958 959 if (strchr(flines[line_pos], bracket) == NULL) 960 printf("%s%s%s", HIGHLIGHT, "No bracket in top line", NORMAL); 961 else { 962 for (i = line_pos + 1; i < num_flines; i++) { 963 if (strchr(flines[i], opp_bracket(bracket)) != NULL) { 964 bracket_line = i; 965 break; 966 } 1170 if (strchr(flines[cur_fline], bracket) == NULL) { 1171 print_statusline("No bracket in top line"); 1172 return; 1173 } 1174 for (i = cur_fline + 1; i < max_fline; i++) { 1175 if (strchr(flines[i], opp_bracket(bracket)) != NULL) { 1176 bracket_line = i; 1177 break; 967 1178 } 968 969 if (bracket_line == -1) 970 printf("%s%s%s", HIGHLIGHT, "No matching bracket found", NORMAL); 971 972 buffer_line(bracket_line - height + 2); 973 } 1179 } 1180 if (bracket_line == -1) 1181 print_statusline("No matching bracket found"); 1182 buffer_line(bracket_line - max_displayed_line); 974 1183 } 975 1184 … … 979 1188 int i; 980 1189 981 clear_line(); 982 983 if (strchr(flines[line_pos + height - 2], bracket) == NULL) { 984 printf("%s%s%s", HIGHLIGHT, "No bracket in bottom line", NORMAL); 985 printf("%s", flines[line_pos + height]); 986 sleep(4); 987 } 988 else { 989 for (i = line_pos + height - 2; i >= 0; i--) { 990 if (strchr(flines[i], opp_bracket(bracket)) != NULL) { 991 bracket_line = i; 992 break; 993 } 1190 if (strchr(flines[cur_fline + max_displayed_line], bracket) == NULL) { 1191 print_statusline("No bracket in bottom line"); 1192 return; 1193 } 1194 1195 for (i = cur_fline + max_displayed_line; i >= 0; i--) { 1196 if (strchr(flines[i], opp_bracket(bracket)) != NULL) { 1197 bracket_line = i; 1198 break; 994 1199 } 995 996 if (bracket_line == -1) 997 printf("%s%s%s", HIGHLIGHT, "No matching bracket found", NORMAL); 998 999 buffer_line(bracket_line); 1000 } 1001 } 1002 1003 #endif /* CONFIG_FEATURE_LESS_BRACKETS */ 1200 } 1201 if (bracket_line == -1) 1202 print_statusline("No matching bracket found"); 1203 buffer_line(bracket_line); 1204 } 1205 #endif /* FEATURE_LESS_BRACKETS */ 1004 1206 1005 1207 static void keypress_process(int keypress) 1006 1208 { 1007 1209 switch (keypress) { 1008 case KEY_DOWN: case 'e': case 'j': case '\015': 1009 buffer_down(1); 1010 buffer_print(); 1011 break; 1012 case KEY_UP: case 'y': case 'k': 1013 buffer_up(1); 1014 buffer_print(); 1015 break; 1016 case PAGE_DOWN: case ' ': case 'z': 1017 buffer_down(height - 1); 1018 buffer_print(); 1019 break; 1020 case PAGE_UP: case 'w': case 'b': 1021 buffer_up(height - 1); 1022 buffer_print(); 1023 break; 1024 case 'd': 1025 buffer_down((height - 1) / 2); 1026 buffer_print(); 1027 break; 1028 case 'u': 1029 buffer_up((height - 1) / 2); 1030 buffer_print(); 1031 break; 1032 case 'g': case 'p': case '<': case '%': 1033 buffer_line(0); 1034 break; 1035 case 'G': case '>': 1036 buffer_line(num_flines - height + 2); 1037 break; 1038 case 'q': case 'Q': 1039 tless_exit(0); 1040 break; 1041 #ifdef CONFIG_FEATURE_LESS_MARKS 1042 case 'm': 1043 add_mark(); 1044 buffer_print(); 1045 break; 1046 case '\'': 1047 goto_mark(); 1048 buffer_print(); 1049 break; 1050 #endif 1051 case 'r': 1052 buffer_print(); 1053 break; 1054 case 'R': 1055 full_repaint(); 1056 break; 1057 case 's': 1058 if (inp_stdin) 1059 save_input_to_file(); 1060 break; 1061 case 'E': 1062 examine_file(); 1063 break; 1064 #ifdef CONFIG_FEATURE_LESS_FLAGS 1065 case '=': 1066 clear_line(); 1067 m_status_print(); 1068 break; 1069 #endif 1070 #ifdef CONFIG_FEATURE_LESS_REGEXP 1071 case '/': 1072 match_backwards = 0; 1073 regex_process(); 1074 break; 1075 case 'n': 1076 goto_match(match_pos + 1); 1077 break; 1078 case 'N': 1079 goto_match(match_pos - 1); 1080 break; 1081 case '?': 1082 match_backwards = 1; 1083 regex_process(); 1084 break; 1085 #endif 1086 #ifdef CONFIG_FEATURE_LESS_FLAGCS 1087 case '-': 1088 flag_change(); 1089 buffer_print(); 1090 break; 1091 case '_': 1092 show_flag_status(); 1093 break; 1094 #endif 1095 #ifdef CONFIG_FEATURE_LESS_BRACKETS 1096 case '{': case '(': case '[': 1097 match_right_bracket(keypress); 1098 break; 1099 case '}': case ')': case ']': 1100 match_left_bracket(keypress); 1101 break; 1102 #endif 1103 case ':': 1104 colon_process(); 1105 break; 1106 default: 1107 break; 1210 case KEY_DOWN: case 'e': case 'j': case 0x0d: 1211 buffer_down(1); 1212 break; 1213 case KEY_UP: case 'y': case 'k': 1214 buffer_up(1); 1215 break; 1216 case PAGE_DOWN: case ' ': case 'z': 1217 buffer_down(max_displayed_line + 1); 1218 break; 1219 case PAGE_UP: case 'w': case 'b': 1220 buffer_up(max_displayed_line + 1); 1221 break; 1222 case 'd': 1223 buffer_down((max_displayed_line + 1) / 2); 1224 break; 1225 case 'u': 1226 buffer_up((max_displayed_line + 1) / 2); 1227 break; 1228 case KEY_HOME: case 'g': case 'p': case '<': case '%': 1229 buffer_line(0); 1230 break; 1231 case KEY_END: case 'G': case '>': 1232 cur_fline = MAXLINES; 1233 read_lines(); 1234 buffer_line(cur_fline); 1235 break; 1236 case 'q': case 'Q': 1237 less_exit(0); 1238 break; 1239 #if ENABLE_FEATURE_LESS_MARKS 1240 case 'm': 1241 add_mark(); 1242 buffer_print(); 1243 break; 1244 case '\'': 1245 goto_mark(); 1246 buffer_print(); 1247 break; 1248 #endif 1249 case 'r': case 'R': 1250 buffer_print(); 1251 break; 1252 /*case 'R': 1253 full_repaint(); 1254 break;*/ 1255 case 's': 1256 save_input_to_file(); 1257 break; 1258 case 'E': 1259 examine_file(); 1260 break; 1261 #if ENABLE_FEATURE_LESS_FLAGS 1262 case '=': 1263 m_status_print(); 1264 break; 1265 #endif 1266 #if ENABLE_FEATURE_LESS_REGEXP 1267 case '/': 1268 option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS; 1269 regex_process(); 1270 break; 1271 case 'n': 1272 goto_match(match_pos + 1); 1273 break; 1274 case 'N': 1275 goto_match(match_pos - 1); 1276 break; 1277 case '?': 1278 option_mask32 |= LESS_STATE_MATCH_BACKWARDS; 1279 regex_process(); 1280 break; 1281 #endif 1282 #if ENABLE_FEATURE_LESS_FLAGCS 1283 case '-': 1284 flag_change(); 1285 buffer_print(); 1286 break; 1287 case '_': 1288 show_flag_status(); 1289 break; 1290 #endif 1291 #if ENABLE_FEATURE_LESS_BRACKETS 1292 case '{': case '(': case '[': 1293 match_right_bracket(keypress); 1294 break; 1295 case '}': case ')': case ']': 1296 match_left_bracket(keypress); 1297 break; 1298 #endif 1299 case ':': 1300 colon_process(); 1301 break; 1108 1302 } 1109 1303 … … 1112 1306 } 1113 1307 1114 int less_main(int argc, char **argv) { 1115 1308 static void sig_catcher(int sig ATTRIBUTE_UNUSED) 1309 { 1310 set_tty_cooked(); 1311 exit(1); 1312 } 1313 1314 int less_main(int argc, char **argv); 1315 int less_main(int argc, char **argv) 1316 { 1116 1317 int keypress; 1117 1318 1118 flags = bb_getopt_ulflags(argc, argv, "EMmN~"); 1119 1319 INIT_G(); 1320 1321 /* TODO: -x: do not interpret backspace, -xx: tab also */ 1322 /* -xxx: newline also */ 1323 /* -w N: assume width N (-xxx -w 32: hex viewer of sorts) */ 1324 getopt32(argv, "EMmN~"); 1120 1325 argc -= optind; 1121 1326 argv += optind; 1327 num_files = argc; 1122 1328 files = argv; 1123 num_files = argc; 1329 1330 /* Another popular pager, most, detects when stdout 1331 * is not a tty and turns into cat. This makes sense. */ 1332 if (!isatty(STDOUT_FILENO)) 1333 return bb_cat(argv); 1334 kbd_fd = open(CURRENT_TTY, O_RDONLY); 1335 if (kbd_fd < 0) 1336 return bb_cat(argv); 1124 1337 1125 1338 if (!num_files) { 1126 if (ttyname(STDIN_FILENO) == NULL) 1127 inp_stdin = 1; 1128 else { 1129 bb_error_msg("Missing filename"); 1339 if (isatty(STDIN_FILENO)) { 1340 /* Just "less"? No args and no redirection? */ 1341 bb_error_msg("missing filename"); 1130 1342 bb_show_usage(); 1131 1343 } 1132 } 1133 1134 strcpy(filename, (inp_stdin) ? bb_msg_standard_input : files[0]); 1135 get_terminal_width_height(0, &width, &height); 1136 data_readlines(); 1137 tcgetattr(fileno(inp), &term_orig); 1138 term_vi = term_orig; 1139 term_vi.c_lflag &= (~ICANON & ~ECHO); 1140 term_vi.c_iflag &= (~IXON & ~ICRNL); 1141 term_vi.c_oflag &= (~ONLCR); 1142 term_vi.c_cc[VMIN] = 1; 1143 term_vi.c_cc[VTIME] = 0; 1144 buffer_init(); 1145 buffer_print(); 1146 1344 } else 1345 filename = xstrdup(files[0]); 1346 1347 get_terminal_width_height(kbd_fd, &width, &max_displayed_line); 1348 /* 20: two tabstops + 4 */ 1349 if (width < 20 || max_displayed_line < 3) 1350 bb_error_msg_and_die("too narrow here"); 1351 max_displayed_line -= 2; 1352 1353 buffer = xmalloc((max_displayed_line+1) * sizeof(char *)); 1354 if (option_mask32 & FLAG_TILDE) 1355 empty_line_marker = ""; 1356 1357 tcgetattr(kbd_fd, &term_orig); 1358 signal(SIGTERM, sig_catcher); 1359 signal(SIGINT, sig_catcher); 1360 term_less = term_orig; 1361 term_less.c_lflag &= ~(ICANON | ECHO); 1362 term_less.c_iflag &= ~(IXON | ICRNL); 1363 /*term_less.c_oflag &= ~ONLCR;*/ 1364 term_less.c_cc[VMIN] = 1; 1365 term_less.c_cc[VTIME] = 0; 1366 1367 /* Want to do it just once, but it doesn't work, */ 1368 /* so we are redoing it (see code above). Mystery... */ 1369 /*tcsetattr(kbd_fd, TCSANOW, &term_less);*/ 1370 1371 reinitialize(); 1147 1372 while (1) { 1148 keypress = tless_getch();1373 keypress = less_getch(); 1149 1374 keypress_process(keypress); 1150 1375 }
Note:
See TracChangeset
for help on using the changeset viewer.