less.c 51 KB

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