less.c 47 KB

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