less.c 31 KB

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