less.c 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155
  1. /* vi: set sw=4 ts=4: */
  2. /*
  3. * Mini less implementation for busybox
  4. *
  5. * Copyright (C) 2005 by Rob Sullivan <cogito.ergo.cogito@gmail.com>
  6. *
  7. * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
  8. */
  9. /*
  10. * This program needs a lot of development, so consider it in a beta stage
  11. * at best.
  12. *
  13. * TODO:
  14. * - Add more regular expression support - search modifiers, certain matches, etc.
  15. * - Add more complex bracket searching - currently, nested brackets are
  16. * not considered.
  17. * - Add support for "F" as an input. This causes less to act in
  18. * a similar way to tail -f.
  19. * - Check for binary files, and prompt the user if a binary file
  20. * is detected.
  21. * - Allow horizontal scrolling. Currently, lines simply continue onto
  22. * the next line, per the terminal's discretion
  23. *
  24. * Notes:
  25. * - filename is an array and not a pointer because that avoids all sorts
  26. * of complications involving the fact that something that is pointed to
  27. * will be changed if the pointer is changed.
  28. * - the inp file pointer is used so that keyboard input works after
  29. * redirected input has been read from stdin
  30. */
  31. #include <stdio.h>
  32. #include <stdlib.h>
  33. #include <string.h>
  34. #include <termios.h>
  35. #include <unistd.h>
  36. #include <ctype.h>
  37. #include "busybox.h"
  38. #ifdef CONFIG_FEATURE_LESS_REGEXP
  39. #include "xregex.h"
  40. #endif
  41. /* These are the escape sequences corresponding to special keys */
  42. #define REAL_KEY_UP 'A'
  43. #define REAL_KEY_DOWN 'B'
  44. #define REAL_KEY_RIGHT 'C'
  45. #define REAL_KEY_LEFT 'D'
  46. #define REAL_PAGE_UP '5'
  47. #define REAL_PAGE_DOWN '6'
  48. /* These are the special codes assigned by this program to the special keys */
  49. #define PAGE_UP 20
  50. #define PAGE_DOWN 21
  51. #define KEY_UP 22
  52. #define KEY_DOWN 23
  53. #define KEY_RIGHT 24
  54. #define KEY_LEFT 25
  55. /* The escape codes for highlighted and normal text */
  56. #define HIGHLIGHT "\033[7m"
  57. #define NORMAL "\033[0m"
  58. /* The escape code to clear the screen */
  59. #define CLEAR "\033[H\033[J"
  60. /* Maximum number of lines in a file */
  61. #define MAXLINES 10000
  62. /* Get height and width of terminal */
  63. #define tty_width_height() get_terminal_width_height(0, &width, &height)
  64. static int height;
  65. static int width;
  66. static char **files;
  67. static char filename[256];
  68. static char **buffer;
  69. static char **flines;
  70. static int current_file = 1;
  71. static int line_pos;
  72. static int num_flines;
  73. static int num_files = 1;
  74. static int past_eof;
  75. /* Command line options */
  76. static unsigned long flags;
  77. #define FLAG_E 1
  78. #define FLAG_M (1<<1)
  79. #define FLAG_m (1<<2)
  80. #define FLAG_N (1<<3)
  81. #define FLAG_TILDE (1<<4)
  82. /* This is needed so that program behaviour changes when input comes from
  83. stdin */
  84. static int inp_stdin;
  85. #ifdef CONFIG_FEATURE_LESS_MARKS
  86. static int mark_lines[15][2];
  87. static int num_marks;
  88. #endif
  89. #ifdef CONFIG_FEATURE_LESS_REGEXP
  90. static int match_found;
  91. static int *match_lines;
  92. static int match_pos;
  93. static int num_matches;
  94. static int match_backwards;
  95. static regex_t old_pattern;
  96. #endif
  97. /* Needed termios structures */
  98. static struct termios term_orig, term_vi;
  99. /* File pointer to get input from */
  100. static FILE *inp;
  101. /* Reset terminal input to normal */
  102. static void set_tty_cooked(void)
  103. {
  104. fflush(stdout);
  105. tcsetattr(fileno(inp), TCSANOW, &term_orig);
  106. }
  107. /* Set terminal input to raw mode (taken from vi.c) */
  108. static void set_tty_raw(void)
  109. {
  110. tcsetattr(fileno(inp), TCSANOW, &term_vi);
  111. }
  112. /* Exit the program gracefully */
  113. static void tless_exit(int code)
  114. {
  115. /* TODO: We really should save the terminal state when we start,
  116. and restore it when we exit. Less does this with the
  117. "ti" and "te" termcap commands; can this be done with
  118. only termios.h? */
  119. putchar('\n');
  120. exit(code);
  121. }
  122. /* Grab a character from input without requiring the return key. If the
  123. character is ASCII \033, get more characters and assign certain sequences
  124. special return codes. Note that this function works best with raw input. */
  125. static int tless_getch(void)
  126. {
  127. int input;
  128. set_tty_raw();
  129. input = getc(inp);
  130. /* Detect escape sequences (i.e. arrow keys) and handle
  131. them accordingly */
  132. if (input == '\033' && getc(inp) == '[') {
  133. input = getc(inp);
  134. set_tty_cooked();
  135. if (input == REAL_KEY_UP)
  136. return KEY_UP;
  137. else if (input == REAL_KEY_DOWN)
  138. return KEY_DOWN;
  139. else if (input == REAL_KEY_RIGHT)
  140. return KEY_RIGHT;
  141. else if (input == REAL_KEY_LEFT)
  142. return KEY_LEFT;
  143. else if (input == REAL_PAGE_UP)
  144. return PAGE_UP;
  145. else if (input == REAL_PAGE_DOWN)
  146. return PAGE_DOWN;
  147. }
  148. /* The input is a normal ASCII value */
  149. else {
  150. set_tty_cooked();
  151. return input;
  152. }
  153. return 0;
  154. }
  155. /* Move the cursor to a position (x,y), where (0,0) is the
  156. top-left corner of the console */
  157. static void move_cursor(int x, int y)
  158. {
  159. printf("\033[%i;%iH", x, y);
  160. }
  161. static void clear_line(void)
  162. {
  163. move_cursor(height, 0);
  164. printf("\033[K");
  165. }
  166. /* This adds line numbers to every line, as the -N flag necessitates */
  167. static void add_linenumbers(void)
  168. {
  169. char current_line[256];
  170. int i;
  171. for (i = 0; i <= num_flines; i++) {
  172. safe_strncpy(current_line, flines[i], 256);
  173. flines[i] = bb_xasprintf("%5d %s", i + 1, current_line);
  174. }
  175. }
  176. static void data_readlines(void)
  177. {
  178. int i;
  179. char current_line[256];
  180. FILE *fp;
  181. fp = (inp_stdin) ? stdin : bb_xfopen(filename, "r");
  182. flines = NULL;
  183. for (i = 0; (feof(fp)==0) && (i <= MAXLINES); i++) {
  184. strcpy(current_line, "");
  185. fgets(current_line, 256, fp);
  186. if (fp != stdin)
  187. bb_xferror(fp, filename);
  188. flines = xrealloc(flines, (i+1) * sizeof(char *));
  189. flines[i] = bb_xstrdup(current_line);
  190. }
  191. num_flines = i - 2;
  192. /* Reset variables for a new file */
  193. line_pos = 0;
  194. past_eof = 0;
  195. fclose(fp);
  196. if (inp == NULL)
  197. inp = (inp_stdin) ? bb_xfopen(CURRENT_TTY, "r") : stdin;
  198. if (flags & FLAG_N)
  199. add_linenumbers();
  200. }
  201. #ifdef CONFIG_FEATURE_LESS_FLAGS
  202. /* Interestingly, writing calc_percent as a function and not a prototype saves around 32 bytes
  203. * on my build. */
  204. static int calc_percent(void)
  205. {
  206. return ((100 * (line_pos + height - 2) / num_flines) + 1);
  207. }
  208. /* Print a status line if -M was specified */
  209. static void m_status_print(void)
  210. {
  211. int percentage;
  212. if (!past_eof) {
  213. if (!line_pos) {
  214. if (num_files > 1)
  215. 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);
  216. else {
  217. printf("%s%s lines %i-%i/%i ", HIGHLIGHT, filename, line_pos + 1, line_pos + height - 1, num_flines + 1);
  218. }
  219. }
  220. else {
  221. printf("%s %s lines %i-%i/%i ", HIGHLIGHT, filename, line_pos + 1, line_pos + height - 1, num_flines + 1);
  222. }
  223. if (line_pos == num_flines - height + 2) {
  224. printf("(END) %s", NORMAL);
  225. if ((num_files > 1) && (current_file != num_files))
  226. printf("%s- Next: %s%s", HIGHLIGHT, files[current_file], NORMAL);
  227. }
  228. else {
  229. percentage = calc_percent();
  230. printf("%i%% %s", percentage, NORMAL);
  231. }
  232. }
  233. else {
  234. printf("%s%s lines %i-%i/%i (END) ", HIGHLIGHT, filename, line_pos + 1, num_flines + 1, num_flines + 1);
  235. if ((num_files > 1) && (current_file != num_files))
  236. printf("- Next: %s", files[current_file]);
  237. printf("%s", NORMAL);
  238. }
  239. }
  240. /* Print a status line if -m was specified */
  241. static void medium_status_print(void)
  242. {
  243. int percentage;
  244. percentage = calc_percent();
  245. if (!line_pos)
  246. printf("%s%s %i%%%s", HIGHLIGHT, filename, percentage, NORMAL);
  247. else if (line_pos == num_flines - height + 2)
  248. printf("%s(END)%s", HIGHLIGHT, NORMAL);
  249. else
  250. printf("%s%i%%%s", HIGHLIGHT, percentage, NORMAL);
  251. }
  252. #endif
  253. /* Print the status line */
  254. static void status_print(void)
  255. {
  256. /* Change the status if flags have been set */
  257. #ifdef CONFIG_FEATURE_LESS_FLAGS
  258. if (flags & FLAG_M)
  259. m_status_print();
  260. else if (flags & FLAG_m)
  261. medium_status_print();
  262. /* No flags set */
  263. else {
  264. #endif
  265. if (!line_pos) {
  266. printf("%s%s %s", HIGHLIGHT, filename, NORMAL);
  267. if (num_files > 1)
  268. printf("%s%s%i%s%i%s%s", HIGHLIGHT, "(file ", current_file, " of ", num_files, ")", NORMAL);
  269. }
  270. else if (line_pos == num_flines - height + 2) {
  271. printf("%s%s %s", HIGHLIGHT, "(END)", NORMAL);
  272. if ((num_files > 1) && (current_file != num_files))
  273. printf("%s%s%s%s", HIGHLIGHT, "- Next: ", files[current_file], NORMAL);
  274. }
  275. else {
  276. putchar(':');
  277. }
  278. #ifdef CONFIG_FEATURE_LESS_FLAGS
  279. }
  280. #endif
  281. }
  282. /* Print the buffer */
  283. static void buffer_print(void)
  284. {
  285. int i;
  286. printf("%s", CLEAR);
  287. if (num_flines >= height - 2) {
  288. for (i = 0; i < height - 1; i++)
  289. printf("%s", buffer[i]);
  290. }
  291. else {
  292. for (i = 1; i < (height - 1 - num_flines); i++)
  293. putchar('\n');
  294. for (i = 0; i < height - 1; i++)
  295. printf("%s", buffer[i]);
  296. }
  297. status_print();
  298. }
  299. /* Initialise the buffer */
  300. static void buffer_init(void)
  301. {
  302. int i;
  303. if (buffer == NULL) {
  304. /* malloc the number of lines needed for the buffer */
  305. buffer = xrealloc(buffer, height * sizeof(char *));
  306. } else {
  307. for (i = 0; i < (height - 1); i++)
  308. free(buffer[i]);
  309. }
  310. /* Fill the buffer until the end of the file or the
  311. end of the buffer is reached */
  312. for (i = 0; (i < (height - 1)) && (i <= num_flines); i++) {
  313. buffer[i] = bb_xstrdup(flines[i]);
  314. }
  315. /* If the buffer still isn't full, fill it with blank lines */
  316. for (; i < (height - 1); i++) {
  317. buffer[i] = bb_xstrdup("");
  318. }
  319. }
  320. /* Move the buffer up and down in the file in order to scroll */
  321. static void buffer_down(int nlines)
  322. {
  323. int i;
  324. if (!past_eof) {
  325. if (line_pos + (height - 3) + nlines < num_flines) {
  326. line_pos += nlines;
  327. for (i = 0; i < (height - 1); i++) {
  328. free(buffer[i]);
  329. buffer[i] = bb_xstrdup(flines[line_pos + i]);
  330. }
  331. }
  332. else {
  333. /* As the number of lines requested was too large, we just move
  334. to the end of the file */
  335. while (line_pos + (height - 3) + 1 < num_flines) {
  336. line_pos += 1;
  337. for (i = 0; i < (height - 1); i++) {
  338. free(buffer[i]);
  339. buffer[i] = bb_xstrdup(flines[line_pos + i]);
  340. }
  341. }
  342. }
  343. /* We exit if the -E flag has been set */
  344. if ((flags & FLAG_E) && (line_pos + (height - 2) == num_flines))
  345. tless_exit(0);
  346. }
  347. }
  348. static void buffer_up(int nlines)
  349. {
  350. int i;
  351. int tilde_line;
  352. if (!past_eof) {
  353. if (line_pos - nlines >= 0) {
  354. line_pos -= nlines;
  355. for (i = 0; i < (height - 1); i++) {
  356. free(buffer[i]);
  357. buffer[i] = bb_xstrdup(flines[line_pos + i]);
  358. }
  359. }
  360. else {
  361. /* As the requested number of lines to move was too large, we
  362. move one line up at a time until we can't. */
  363. while (line_pos != 0) {
  364. line_pos -= 1;
  365. for (i = 0; i < (height - 1); i++) {
  366. free(buffer[i]);
  367. buffer[i] = bb_xstrdup(flines[line_pos + i]);
  368. }
  369. }
  370. }
  371. }
  372. else {
  373. /* Work out where the tildes start */
  374. tilde_line = num_flines - line_pos + 3;
  375. line_pos -= nlines;
  376. /* Going backwards nlines lines has taken us to a point where
  377. nothing is past the EOF, so we revert to normal. */
  378. if (line_pos < num_flines - height + 3) {
  379. past_eof = 0;
  380. buffer_up(nlines);
  381. }
  382. else {
  383. /* We only move part of the buffer, as the rest
  384. is past the EOF */
  385. for (i = 0; i < (height - 1); i++) {
  386. free(buffer[i]);
  387. if (i < tilde_line - nlines + 1)
  388. buffer[i] = bb_xstrdup(flines[line_pos + i]);
  389. else {
  390. if (line_pos >= num_flines - height + 2)
  391. buffer[i] = bb_xstrdup("~\n");
  392. }
  393. }
  394. }
  395. }
  396. }
  397. static void buffer_line(int linenum)
  398. {
  399. int i;
  400. past_eof = 0;
  401. if (linenum < 0 || linenum > num_flines) {
  402. clear_line();
  403. printf("%s%s%i%s", HIGHLIGHT, "Cannot seek to line number ", linenum + 1, NORMAL);
  404. }
  405. else if (linenum < (num_flines - height - 2)) {
  406. for (i = 0; i < (height - 1); i++) {
  407. free(buffer[i]);
  408. buffer[i] = bb_xstrdup(flines[linenum + i]);
  409. }
  410. line_pos = linenum;
  411. buffer_print();
  412. }
  413. else {
  414. for (i = 0; i < (height - 1); i++) {
  415. free(buffer[i]);
  416. if (linenum + i < num_flines + 2)
  417. buffer[i] = bb_xstrdup(flines[linenum + i]);
  418. else
  419. buffer[i] = bb_xstrdup((flags & FLAG_TILDE) ? "\n" : "~\n");
  420. }
  421. line_pos = linenum;
  422. /* Set past_eof so buffer_down and buffer_up act differently */
  423. past_eof = 1;
  424. buffer_print();
  425. }
  426. }
  427. /* Reinitialise everything for a new file - free the memory and start over */
  428. static void reinitialise(void)
  429. {
  430. int i;
  431. for (i = 0; i <= num_flines; i++)
  432. free(flines[i]);
  433. free(flines);
  434. data_readlines();
  435. buffer_init();
  436. buffer_print();
  437. }
  438. static void examine_file(void)
  439. {
  440. int newline_offset;
  441. clear_line();
  442. printf("Examine: ");
  443. fgets(filename, 256, inp);
  444. /* As fgets adds a newline to the end of an input string, we
  445. need to remove it */
  446. newline_offset = strlen(filename) - 1;
  447. filename[newline_offset] = '\0';
  448. files[num_files] = bb_xstrdup(filename);
  449. current_file = num_files + 1;
  450. num_files++;
  451. inp_stdin = 0;
  452. reinitialise();
  453. }
  454. /* This function changes the file currently being paged. direction can be one of the following:
  455. * -1: go back one file
  456. * 0: go to the first file
  457. * 1: go forward one file
  458. */
  459. static void change_file(int direction)
  460. {
  461. if (current_file != ((direction > 0) ? num_files : 1)) {
  462. current_file = direction ? current_file + direction : 1;
  463. strcpy(filename, files[current_file - 1]);
  464. reinitialise();
  465. }
  466. else {
  467. clear_line();
  468. printf("%s%s%s", HIGHLIGHT, (direction > 0) ? "No next file" : "No previous file", NORMAL);
  469. }
  470. }
  471. static void remove_current_file(void)
  472. {
  473. int i;
  474. if (current_file != 1) {
  475. change_file(-1);
  476. for (i = 3; i <= num_files; i++)
  477. files[i - 2] = files[i - 1];
  478. num_files--;
  479. buffer_print();
  480. }
  481. else {
  482. change_file(1);
  483. for (i = 2; i <= num_files; i++)
  484. files[i - 2] = files[i - 1];
  485. num_files--;
  486. current_file--;
  487. buffer_print();
  488. }
  489. }
  490. static void colon_process(void)
  491. {
  492. int keypress;
  493. /* Clear the current line and print a prompt */
  494. clear_line();
  495. printf(" :");
  496. keypress = tless_getch();
  497. switch (keypress) {
  498. case 'd':
  499. remove_current_file();
  500. break;
  501. case 'e':
  502. examine_file();
  503. break;
  504. #ifdef CONFIG_FEATURE_LESS_FLAGS
  505. case 'f':
  506. clear_line();
  507. m_status_print();
  508. break;
  509. #endif
  510. case 'n':
  511. change_file(1);
  512. break;
  513. case 'p':
  514. change_file(-1);
  515. break;
  516. case 'q':
  517. tless_exit(0);
  518. break;
  519. case 'x':
  520. change_file(0);
  521. break;
  522. default:
  523. break;
  524. }
  525. }
  526. #ifdef CONFIG_FEATURE_LESS_REGEXP
  527. /* The below two regular expression handler functions NEED development. */
  528. /* Get a regular expression from the user, and then go through the current
  529. file line by line, running a processing regex function on each one. */
  530. static char *process_regex_on_line(char *line, regex_t *pattern, int action)
  531. {
  532. /* This function takes the regex and applies it to the line.
  533. Each part of the line that matches has the HIGHLIGHT
  534. and NORMAL escape sequences placed around it by
  535. insert_highlights if action = 1, or has the escape sequences
  536. removed if action = 0, and then the line is returned. */
  537. int match_status;
  538. char *line2 = (char *) xmalloc((sizeof(char) * (strlen(line) + 1)) + 64);
  539. char *growline = "";
  540. regmatch_t match_structs;
  541. line2 = bb_xstrdup(line);
  542. match_found = 0;
  543. match_status = regexec(pattern, line2, 1, &match_structs, 0);
  544. while (match_status == 0) {
  545. if (match_found == 0)
  546. match_found = 1;
  547. if (action) {
  548. 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);
  549. }
  550. else {
  551. 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);
  552. }
  553. line2 += match_structs.rm_eo;
  554. match_status = regexec(pattern, line2, 1, &match_structs, REG_NOTBOL);
  555. }
  556. growline = bb_xasprintf("%s%s", growline, line2);
  557. return (match_found ? growline : line);
  558. free(growline);
  559. free(line2);
  560. }
  561. static void goto_match(int match)
  562. {
  563. /* This goes to a specific match - all line positions of matches are
  564. stored within the match_lines[] array. */
  565. if ((match < num_matches) && (match >= 0)) {
  566. buffer_line(match_lines[match]);
  567. match_pos = match;
  568. }
  569. }
  570. static void regex_process(void)
  571. {
  572. char uncomp_regex[100];
  573. char *current_line;
  574. int i;
  575. int j = 0;
  576. regex_t pattern;
  577. /* Get the uncompiled regular expression from the user */
  578. clear_line();
  579. putchar((match_backwards) ? '?' : '/');
  580. uncomp_regex[0] = 0;
  581. fgets(uncomp_regex, sizeof(uncomp_regex), inp);
  582. if (strlen(uncomp_regex) == 1) {
  583. if (num_matches)
  584. goto_match(match_backwards ? match_pos - 1 : match_pos + 1);
  585. else
  586. buffer_print();
  587. return;
  588. }
  589. uncomp_regex[strlen(uncomp_regex) - 1] = '\0';
  590. /* Compile the regex and check for errors */
  591. xregcomp(&pattern, uncomp_regex, 0);
  592. if (num_matches) {
  593. /* Get rid of all the highlights we added previously */
  594. for (i = 0; i <= num_flines; i++) {
  595. current_line = process_regex_on_line(flines[i], &old_pattern, 0);
  596. flines[i] = bb_xstrdup(current_line);
  597. }
  598. }
  599. old_pattern = pattern;
  600. /* Reset variables */
  601. match_lines = xrealloc(match_lines, sizeof(int));
  602. match_lines[0] = -1;
  603. match_pos = 0;
  604. num_matches = 0;
  605. match_found = 0;
  606. /* Run the regex on each line of the current file here */
  607. for (i = 0; i <= num_flines; i++) {
  608. current_line = process_regex_on_line(flines[i], &pattern, 1);
  609. flines[i] = bb_xstrdup(current_line);
  610. if (match_found) {
  611. match_lines = xrealloc(match_lines, (j + 1) * sizeof(int));
  612. match_lines[j] = i;
  613. j++;
  614. }
  615. }
  616. num_matches = j;
  617. if ((match_lines[0] != -1) && (num_flines > height - 2)) {
  618. if (match_backwards) {
  619. for (i = 0; i < num_matches; i++) {
  620. if (match_lines[i] > line_pos) {
  621. match_pos = i - 1;
  622. buffer_line(match_lines[match_pos]);
  623. break;
  624. }
  625. }
  626. }
  627. else
  628. buffer_line(match_lines[0]);
  629. }
  630. else
  631. buffer_init();
  632. }
  633. #endif
  634. static void number_process(int first_digit)
  635. {
  636. int i = 1;
  637. int num;
  638. char num_input[80];
  639. char keypress;
  640. char *endptr;
  641. num_input[0] = first_digit;
  642. /* Clear the current line, print a prompt, and then print the digit */
  643. clear_line();
  644. printf(":%c", first_digit);
  645. /* Receive input until a letter is given (max 80 chars)*/
  646. while((i < 80) && (num_input[i] = tless_getch()) && isdigit(num_input[i])) {
  647. putchar(num_input[i]);
  648. i++;
  649. }
  650. /* Take the final letter out of the digits string */
  651. keypress = num_input[i];
  652. num_input[i] = '\0';
  653. num = strtol(num_input, &endptr, 10);
  654. if (endptr==num_input || *endptr!='\0' || num < 1 || num > MAXLINES) {
  655. buffer_print();
  656. return;
  657. }
  658. /* We now know the number and the letter entered, so we process them */
  659. switch (keypress) {
  660. case KEY_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015':
  661. buffer_down(num);
  662. break;
  663. case KEY_UP: case 'b': case 'w': case 'y': case 'u':
  664. buffer_up(num);
  665. break;
  666. case 'g': case '<': case 'G': case '>':
  667. if (num_flines >= height - 2)
  668. buffer_line(num - 1);
  669. break;
  670. case 'p': case '%':
  671. buffer_line(((num / 100) * num_flines) - 1);
  672. break;
  673. #ifdef CONFIG_FEATURE_LESS_REGEXP
  674. case 'n':
  675. goto_match(match_pos + num);
  676. break;
  677. case '/':
  678. match_backwards = 0;
  679. regex_process();
  680. break;
  681. case '?':
  682. match_backwards = 1;
  683. regex_process();
  684. break;
  685. #endif
  686. default:
  687. break;
  688. }
  689. }
  690. #ifdef CONFIG_FEATURE_LESS_FLAGCS
  691. static void flag_change(void)
  692. {
  693. int keypress;
  694. clear_line();
  695. putchar('-');
  696. keypress = tless_getch();
  697. switch (keypress) {
  698. case 'M':
  699. flags ^= FLAG_M;
  700. break;
  701. case 'm':
  702. flags ^= FLAG_m;
  703. break;
  704. case 'E':
  705. flags ^= FLAG_E;
  706. break;
  707. case '~':
  708. flags ^= FLAG_TILDE;
  709. break;
  710. default:
  711. break;
  712. }
  713. }
  714. static void show_flag_status(void)
  715. {
  716. int keypress;
  717. int flag_val;
  718. clear_line();
  719. putchar('_');
  720. keypress = tless_getch();
  721. switch (keypress) {
  722. case 'M':
  723. flag_val = flags & FLAG_M;
  724. break;
  725. case 'm':
  726. flag_val = flags & FLAG_m;
  727. break;
  728. case '~':
  729. flag_val = flags & FLAG_TILDE;
  730. break;
  731. case 'N':
  732. flag_val = flags & FLAG_N;
  733. break;
  734. case 'E':
  735. flag_val = flags & FLAG_E;
  736. break;
  737. default:
  738. flag_val = 0;
  739. break;
  740. }
  741. clear_line();
  742. printf("%s%s%i%s", HIGHLIGHT, "The status of the flag is: ", flag_val != 0, NORMAL);
  743. }
  744. #endif
  745. static void full_repaint(void)
  746. {
  747. int temp_line_pos = line_pos;
  748. data_readlines();
  749. buffer_init();
  750. buffer_line(temp_line_pos);
  751. }
  752. static void save_input_to_file(void)
  753. {
  754. char current_line[256];
  755. int i;
  756. FILE *fp;
  757. clear_line();
  758. printf("Log file: ");
  759. fgets(current_line, 256, inp);
  760. current_line[strlen(current_line) - 1] = '\0';
  761. if (strlen(current_line) > 1) {
  762. fp = bb_xfopen(current_line, "w");
  763. for (i = 0; i < num_flines; i++)
  764. fprintf(fp, "%s", flines[i]);
  765. fclose(fp);
  766. buffer_print();
  767. }
  768. else
  769. printf("%sNo log file%s", HIGHLIGHT, NORMAL);
  770. }
  771. #ifdef CONFIG_FEATURE_LESS_MARKS
  772. static void add_mark(void)
  773. {
  774. int letter;
  775. int mark_line;
  776. clear_line();
  777. printf("Mark: ");
  778. letter = tless_getch();
  779. if (isalpha(letter)) {
  780. mark_line = line_pos;
  781. /* If we exceed 15 marks, start overwriting previous ones */
  782. if (num_marks == 14)
  783. num_marks = 0;
  784. mark_lines[num_marks][0] = letter;
  785. mark_lines[num_marks][1] = line_pos;
  786. num_marks++;
  787. }
  788. else {
  789. clear_line();
  790. printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL);
  791. }
  792. }
  793. static void goto_mark(void)
  794. {
  795. int letter;
  796. int i;
  797. clear_line();
  798. printf("Go to mark: ");
  799. letter = tless_getch();
  800. clear_line();
  801. if (isalpha(letter)) {
  802. for (i = 0; i <= num_marks; i++)
  803. if (letter == mark_lines[i][0]) {
  804. buffer_line(mark_lines[i][1]);
  805. break;
  806. }
  807. if ((num_marks == 14) && (letter != mark_lines[14][0]))
  808. printf("%s%s%s", HIGHLIGHT, "Mark not set", NORMAL);
  809. }
  810. else
  811. printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL);
  812. }
  813. #endif
  814. #ifdef CONFIG_FEATURE_LESS_BRACKETS
  815. static char opp_bracket(char bracket)
  816. {
  817. switch (bracket) {
  818. case '{': case '[':
  819. return bracket + 2;
  820. break;
  821. case '(':
  822. return ')';
  823. break;
  824. case '}': case ']':
  825. return bracket - 2;
  826. break;
  827. case ')':
  828. return '(';
  829. break;
  830. default:
  831. return 0;
  832. break;
  833. }
  834. }
  835. static void match_right_bracket(char bracket)
  836. {
  837. int bracket_line = -1;
  838. int i;
  839. clear_line();
  840. if (strchr(flines[line_pos], bracket) == NULL)
  841. printf("%s%s%s", HIGHLIGHT, "No bracket in top line", NORMAL);
  842. else {
  843. for (i = line_pos + 1; i < num_flines; i++) {
  844. if (strchr(flines[i], opp_bracket(bracket)) != NULL) {
  845. bracket_line = i;
  846. break;
  847. }
  848. }
  849. if (bracket_line == -1)
  850. printf("%s%s%s", HIGHLIGHT, "No matching bracket found", NORMAL);
  851. buffer_line(bracket_line - height + 2);
  852. }
  853. }
  854. static void match_left_bracket(char bracket)
  855. {
  856. int bracket_line = -1;
  857. int i;
  858. clear_line();
  859. if (strchr(flines[line_pos + height - 2], bracket) == NULL) {
  860. printf("%s%s%s", HIGHLIGHT, "No bracket in bottom line", NORMAL);
  861. printf("%s", flines[line_pos + height]);
  862. sleep(4);
  863. }
  864. else {
  865. for (i = line_pos + height - 2; i >= 0; i--) {
  866. if (strchr(flines[i], opp_bracket(bracket)) != NULL) {
  867. bracket_line = i;
  868. break;
  869. }
  870. }
  871. if (bracket_line == -1)
  872. printf("%s%s%s", HIGHLIGHT, "No matching bracket found", NORMAL);
  873. buffer_line(bracket_line);
  874. }
  875. }
  876. #endif /* CONFIG_FEATURE_LESS_BRACKETS */
  877. static void keypress_process(int keypress)
  878. {
  879. switch (keypress) {
  880. case KEY_DOWN: case 'e': case 'j': case '\015':
  881. buffer_down(1);
  882. buffer_print();
  883. break;
  884. case KEY_UP: case 'y': case 'k':
  885. buffer_up(1);
  886. buffer_print();
  887. break;
  888. case PAGE_DOWN: case ' ': case 'z':
  889. buffer_down(height - 1);
  890. buffer_print();
  891. break;
  892. case PAGE_UP: case 'w': case 'b':
  893. buffer_up(height - 1);
  894. buffer_print();
  895. break;
  896. case 'd':
  897. buffer_down((height - 1) / 2);
  898. buffer_print();
  899. break;
  900. case 'u':
  901. buffer_up((height - 1) / 2);
  902. buffer_print();
  903. break;
  904. case 'g': case 'p': case '<': case '%':
  905. buffer_line(0);
  906. break;
  907. case 'G': case '>':
  908. buffer_line(num_flines - height + 2);
  909. break;
  910. case 'q': case 'Q':
  911. tless_exit(0);
  912. break;
  913. #ifdef CONFIG_FEATURE_LESS_MARKS
  914. case 'm':
  915. add_mark();
  916. buffer_print();
  917. break;
  918. case '\'':
  919. goto_mark();
  920. buffer_print();
  921. break;
  922. #endif
  923. case 'r':
  924. buffer_print();
  925. break;
  926. case 'R':
  927. full_repaint();
  928. break;
  929. case 's':
  930. if (inp_stdin)
  931. save_input_to_file();
  932. break;
  933. case 'E':
  934. examine_file();
  935. break;
  936. #ifdef CONFIG_FEATURE_LESS_FLAGS
  937. case '=':
  938. clear_line();
  939. m_status_print();
  940. break;
  941. #endif
  942. #ifdef CONFIG_FEATURE_LESS_REGEXP
  943. case '/':
  944. match_backwards = 0;
  945. regex_process();
  946. break;
  947. case 'n':
  948. goto_match(match_pos + 1);
  949. break;
  950. case 'N':
  951. goto_match(match_pos - 1);
  952. break;
  953. case '?':
  954. match_backwards = 1;
  955. regex_process();
  956. break;
  957. #endif
  958. #ifdef CONFIG_FEATURE_LESS_FLAGCS
  959. case '-':
  960. flag_change();
  961. buffer_print();
  962. break;
  963. case '_':
  964. show_flag_status();
  965. break;
  966. #endif
  967. #ifdef CONFIG_FEATURE_LESS_BRACKETS
  968. case '{': case '(': case '[':
  969. match_right_bracket(keypress);
  970. break;
  971. case '}': case ')': case ']':
  972. match_left_bracket(keypress);
  973. break;
  974. #endif
  975. case ':':
  976. colon_process();
  977. break;
  978. default:
  979. break;
  980. }
  981. if (isdigit(keypress))
  982. number_process(keypress);
  983. }
  984. int less_main(int argc, char **argv) {
  985. int keypress;
  986. flags = bb_getopt_ulflags(argc, argv, "EMmN~");
  987. argc -= optind;
  988. argv += optind;
  989. files = argv;
  990. num_files = argc;
  991. if (!num_files) {
  992. if (ttyname(STDIN_FILENO) == NULL)
  993. inp_stdin = 1;
  994. else {
  995. bb_error_msg("Missing filename");
  996. bb_show_usage();
  997. }
  998. }
  999. strcpy(filename, (inp_stdin) ? bb_msg_standard_input : files[0]);
  1000. tty_width_height();
  1001. data_readlines();
  1002. tcgetattr(fileno(inp), &term_orig);
  1003. term_vi = term_orig;
  1004. term_vi.c_lflag &= (~ICANON & ~ECHO);
  1005. term_vi.c_iflag &= (~IXON & ~ICRNL);
  1006. term_vi.c_oflag &= (~ONLCR);
  1007. term_vi.c_cc[VMIN] = 1;
  1008. term_vi.c_cc[VTIME] = 0;
  1009. buffer_init();
  1010. buffer_print();
  1011. while (1) {
  1012. keypress = tless_getch();
  1013. keypress_process(keypress);
  1014. }
  1015. }