tinyrl.c 31 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223
  1. /*
  2. * tinyrl.c
  3. */
  4. #include <assert.h>
  5. #include <stdio.h>
  6. #include <stdlib.h>
  7. #include <string.h>
  8. #include <ctype.h>
  9. #include <errno.h>
  10. #include <unistd.h>
  11. #include <faux/faux.h>
  12. #include <faux/str.h>
  13. #include "private.h"
  14. #define LINE_CHUNK 80
  15. tinyrl_t *tinyrl_new(FILE *istream, FILE *ostream,
  16. const char *hist_fname, size_t hist_stifle)
  17. {
  18. tinyrl_t *tinyrl = NULL;
  19. int i = 0;
  20. tinyrl = faux_zmalloc(sizeof(tinyrl_t));
  21. if (!tinyrl)
  22. return NULL;
  23. // Line
  24. faux_bzero(&tinyrl->line, sizeof(tinyrl->line));
  25. tinyrl_extend_line(tinyrl, LINE_CHUNK);
  26. // Input processing vars
  27. tinyrl->utf8_cont = 0;
  28. tinyrl->esc_cont = BOOL_FALSE;
  29. tinyrl->esc_seq[0] = '\0';
  30. tinyrl->esc_p = tinyrl->esc_seq;
  31. // Key handlers
  32. for (i = 0; i < NUM_HANDLERS; i++) {
  33. tinyrl->handlers[i] = tinyrl_key_default;
  34. }
  35. tinyrl->handlers[KEY_CR] = tinyrl_key_crlf;
  36. tinyrl->handlers[KEY_LF] = tinyrl_key_crlf;
  37. tinyrl->handlers[KEY_ETX] = tinyrl_key_interrupt;
  38. tinyrl->handlers[KEY_DEL] = tinyrl_key_backspace;
  39. tinyrl->handlers[KEY_BS] = tinyrl_key_backspace;
  40. tinyrl->handlers[KEY_EOT] = tinyrl_key_delete;
  41. tinyrl->handlers[KEY_FF] = tinyrl_key_clear_screen;
  42. tinyrl->handlers[KEY_NAK] = tinyrl_key_erase_line;
  43. tinyrl->handlers[KEY_SOH] = tinyrl_key_start_of_line;
  44. tinyrl->handlers[KEY_ENQ] = tinyrl_key_end_of_line;
  45. tinyrl->handlers[KEY_VT] = tinyrl_key_kill;
  46. tinyrl->handlers[KEY_EM] = tinyrl_key_yank;
  47. tinyrl->handlers[KEY_HT] = tinyrl_key_tab;
  48. tinyrl->handlers[KEY_ETB] = tinyrl_key_backword;
  49. tinyrl->max_line_length = 0;
  50. tinyrl->prompt = NULL;
  51. tinyrl->prompt_size = 0;
  52. tinyrl->buffer = NULL;
  53. tinyrl->buffer_size = 0;
  54. tinyrl->done = BOOL_FALSE;
  55. tinyrl->completion_over = BOOL_FALSE;
  56. tinyrl->attempted_completion_function = NULL;
  57. tinyrl->hotkey_fn = NULL;
  58. tinyrl->state = 0;
  59. tinyrl->kill_string = NULL;
  60. tinyrl->echo_char = '\0';
  61. tinyrl->echo_enabled = BOOL_TRUE;
  62. tinyrl->last_buffer = NULL;
  63. tinyrl->last_point = 0;
  64. tinyrl->last_line_size = 0;
  65. tinyrl->utf8 = BOOL_TRUE;
  66. // VT100 terminal
  67. tinyrl->term = vt100_new(istream, ostream);
  68. // To save terminal settings
  69. tinyrl_set_istream(tinyrl, istream);
  70. tinyrl->width = vt100_width(tinyrl->term);
  71. // History object
  72. tinyrl->hist = hist_new(hist_fname, hist_stifle);
  73. tinyrl_hist_restore(tinyrl);
  74. tty_raw_mode(tinyrl);
  75. return tinyrl;
  76. }
  77. void tinyrl_free(tinyrl_t *tinyrl)
  78. {
  79. assert(tinyrl);
  80. if (!tinyrl)
  81. return;
  82. tty_restore_mode(tinyrl);
  83. hist_free(tinyrl->hist);
  84. vt100_free(tinyrl->term);
  85. faux_str_free(tinyrl->buffer);
  86. faux_str_free(tinyrl->kill_string);
  87. faux_str_free(tinyrl->last_buffer);
  88. faux_str_free(tinyrl->prompt);
  89. faux_free(tinyrl);
  90. }
  91. void tty_raw_mode(tinyrl_t *tinyrl)
  92. {
  93. struct termios new_termios = {};
  94. FILE *istream = NULL;
  95. int fd = -1;
  96. if (!tinyrl)
  97. return;
  98. istream = vt100_istream(tinyrl->term);
  99. if (!istream)
  100. return;
  101. fd = fileno(istream);
  102. if (tcgetattr(fd, &new_termios) < 0)
  103. return;
  104. new_termios.c_iflag = 0;
  105. new_termios.c_oflag = OPOST | ONLCR;
  106. new_termios.c_lflag = 0;
  107. new_termios.c_cc[VMIN] = 1;
  108. new_termios.c_cc[VTIME] = 0;
  109. // Mode switch
  110. tcsetattr(fd, TCSADRAIN, &new_termios);
  111. }
  112. void tty_restore_mode(tinyrl_t *tinyrl)
  113. {
  114. FILE *istream = NULL;
  115. int fd = -1;
  116. istream = vt100_istream(tinyrl->term);
  117. if (!istream)
  118. return;
  119. fd = fileno(istream);
  120. // Do the mode switch
  121. tcsetattr(fd, TCSADRAIN, &tinyrl->default_termios);
  122. }
  123. bool_t tinyrl_bind_key(tinyrl_t *tinyrl, int key, tinyrl_key_func_t *fn)
  124. {
  125. assert(tinyrl);
  126. if (!tinyrl)
  127. return BOOL_FALSE;
  128. if ((key < 0) || (key > 255))
  129. return BOOL_FALSE;
  130. tinyrl->handlers[key] = fn;
  131. return BOOL_TRUE;
  132. }
  133. void tinyrl_set_istream(tinyrl_t *tinyrl, FILE *istream)
  134. {
  135. assert(tinyrl);
  136. if (!tinyrl)
  137. return;
  138. vt100_set_istream(tinyrl->term, istream);
  139. // Save terminal settings to restore on exit
  140. if (istream)
  141. tcgetattr(fileno(istream), &tinyrl->default_termios);
  142. }
  143. FILE *tinyrl_istream(const tinyrl_t *tinyrl)
  144. {
  145. return vt100_istream(tinyrl->term);
  146. }
  147. void tinyrl_set_ostream(tinyrl_t *tinyrl, FILE *ostream)
  148. {
  149. assert(tinyrl);
  150. if (!tinyrl)
  151. return;
  152. vt100_set_ostream(tinyrl->term, ostream);
  153. }
  154. FILE *tinyrl_ostream(const tinyrl_t *tinyrl)
  155. {
  156. return vt100_ostream(tinyrl->term);
  157. }
  158. bool_t tinyrl_utf8(const tinyrl_t *tinyrl)
  159. {
  160. assert(tinyrl);
  161. if (!tinyrl)
  162. return BOOL_TRUE;
  163. return tinyrl->utf8;
  164. }
  165. void tinyrl_set_utf8(tinyrl_t *tinyrl, bool_t utf8)
  166. {
  167. assert(tinyrl);
  168. if (!tinyrl)
  169. return;
  170. tinyrl->utf8 = utf8;
  171. }
  172. bool_t tinyrl_hist_save(const tinyrl_t *tinyrl)
  173. {
  174. assert(tinyrl);
  175. if (!tinyrl)
  176. return BOOL_FALSE;
  177. return hist_save(tinyrl->hist);
  178. }
  179. bool_t tinyrl_hist_restore(tinyrl_t *tinyrl)
  180. {
  181. assert(tinyrl);
  182. if (!tinyrl)
  183. return BOOL_FALSE;
  184. return hist_restore(tinyrl->hist);
  185. }
  186. static bool_t process_char(tinyrl_t *tinyrl, char key)
  187. {
  188. // Begin of ESC sequence
  189. if (!tinyrl->esc_cont && (KEY_ESC == key)) {
  190. tinyrl->esc_cont = BOOL_TRUE; // Start ESC sequence
  191. tinyrl->esc_p = tinyrl->esc_seq;
  192. // Note: Don't put ESC symbol itself to buffer
  193. return BOOL_TRUE;
  194. }
  195. // Continue ESC sequence
  196. if (tinyrl->esc_cont) {
  197. // Broken sequence. Too long
  198. if ((tinyrl->esc_p - tinyrl->esc_seq) >= (sizeof(tinyrl->esc_seq) - 1)) {
  199. tinyrl->esc_cont = BOOL_FALSE;
  200. return BOOL_FALSE;
  201. }
  202. // Save the curren char to sequence buffer
  203. *tinyrl->esc_p = key;
  204. tinyrl->esc_p++;
  205. // ANSI standard control sequences will end
  206. // with a character between 64 - 126
  207. if ((key != '[') && (key > 63)) {
  208. *tinyrl->esc_p = '\0';
  209. tinyrl_esc_seq(tinyrl, tinyrl->esc_seq);
  210. tinyrl->esc_cont = BOOL_FALSE;
  211. //tinyrl_redisplay(tinyrl);
  212. }
  213. return BOOL_TRUE;
  214. }
  215. // Call the handler for key
  216. // Handler (that has no special meaning) will put new char to line buffer
  217. if (!tinyrl->handlers[(unsigned char)key](tinyrl, key))
  218. vt100_ding(tinyrl->term);
  219. // if (tinyrl->done) // Some handler set the done flag
  220. // continue; // It will break the loop
  221. if (tinyrl->utf8) {
  222. // ASCII char (one byte)
  223. if (!(UTF8_7BIT_MASK & key)) {
  224. tinyrl->utf8_cont = 0;
  225. // First byte of multibyte symbol
  226. } else if (UTF8_11 == (key & UTF8_MASK)) {
  227. // Find out number of symbol's bytes
  228. unsigned int b = (unsigned int)key;
  229. tinyrl->utf8_cont = 0;
  230. while ((tinyrl->utf8_cont < 6) && (UTF8_10 != (b & UTF8_MASK))) {
  231. tinyrl->utf8_cont++;
  232. b = b << 1;
  233. }
  234. // Continue of multibyte symbol
  235. } else if ((tinyrl->utf8_cont > 0) && (UTF8_10 == (key & UTF8_MASK))) {
  236. tinyrl->utf8_cont--;
  237. }
  238. }
  239. // For non UTF-8 encoding the utf8_cont is always 0.
  240. // For UTF-8 it's 0 when one-byte symbol or we get
  241. // all bytes for the current multibyte character
  242. // if (!utf8_cont)
  243. // tinyrl_redisplay(tinyrl);
  244. return BOOL_TRUE;
  245. }
  246. int tinyrl_read(tinyrl_t *tinyrl)
  247. {
  248. int rc = 0;
  249. char key = 0;
  250. int count = 0;
  251. assert(tinyrl);
  252. while ((rc = vt100_getchar(tinyrl->term, &key)) > 0) {
  253. count++;
  254. process_char(tinyrl, key);
  255. }
  256. if ((rc < 0) && (EAGAIN == errno))
  257. return count;
  258. return rc;
  259. }
  260. /*
  261. * Ensure that buffer has enough space to hold len characters,
  262. * possibly reallocating it if necessary. The function returns BOOL_TRUE
  263. * if the line is successfully extended, BOOL_FALSE if not.
  264. */
  265. bool_t tinyrl_extend_line(tinyrl_t *tinyrl, size_t len)
  266. {
  267. char *new_buf = NULL;
  268. size_t new_size = 0;
  269. size_t chunk_num = 0;
  270. if (tinyrl->line.len >= len)
  271. return BOOL_TRUE;
  272. chunk_num = len / LINE_CHUNK;
  273. if ((len % LINE_CHUNK) > 0)
  274. chunk_num++;
  275. new_size = chunk_num * LINE_CHUNK;
  276. // First initialization
  277. if (tinyrl->line.str == NULL) {
  278. tinyrl->line.str = faux_zmalloc(new_size);
  279. if (!tinyrl->line.str)
  280. return BOOL_FALSE;
  281. tinyrl->line.size = new_size;
  282. return BOOL_TRUE;
  283. }
  284. new_buf = realloc(tinyrl->line.str, new_size);
  285. if (!new_buf)
  286. return BOOL_FALSE;
  287. tinyrl->line.str = new_buf;
  288. tinyrl->line.size = new_size;
  289. return BOOL_TRUE;
  290. }
  291. bool_t tinyrl_esc_seq(tinyrl_t *tinyrl, const char *esc_seq)
  292. {
  293. bool_t result = BOOL_FALSE;
  294. switch (vt100_esc_decode(tinyrl->term, esc_seq)) {
  295. case VT100_CURSOR_UP:
  296. result = tinyrl_key_up(tinyrl, 0);
  297. break;
  298. case VT100_CURSOR_DOWN:
  299. result = tinyrl_key_down(tinyrl, 0);
  300. break;
  301. case VT100_CURSOR_LEFT:
  302. result = tinyrl_key_left(tinyrl, 0);
  303. break;
  304. case VT100_CURSOR_RIGHT:
  305. result = tinyrl_key_right(tinyrl, 0);
  306. break;
  307. case VT100_HOME:
  308. result = tinyrl_key_start_of_line(tinyrl, 0);
  309. break;
  310. case VT100_END:
  311. result = tinyrl_key_end_of_line(tinyrl, 0);
  312. break;
  313. case VT100_DELETE:
  314. result = tinyrl_key_delete(tinyrl, 0);
  315. break;
  316. case VT100_INSERT:
  317. case VT100_PGDOWN:
  318. case VT100_PGUP:
  319. case VT100_UNKNOWN:
  320. break;
  321. }
  322. return result;
  323. }
  324. #if 0
  325. /*----------------------------------------------------------------------- */
  326. /*
  327. tinyrl is called whenever a line is edited in any way.
  328. It signals that if we are currently viewing a history line we should transfer it
  329. to the current buffer
  330. */
  331. static void changed_line(tinyrl_t * tinyrl)
  332. {
  333. /* if the current line is not our buffer then make it so */
  334. if (tinyrl->line != tinyrl->buffer) {
  335. /* replace the current buffer with the new details */
  336. free(tinyrl->buffer);
  337. tinyrl->line = tinyrl->buffer = lub_string_dup(tinyrl->line);
  338. tinyrl->buffer_size = strlen(tinyrl->buffer);
  339. assert(tinyrl->line);
  340. }
  341. }
  342. /*-------------------------------------------------------- */
  343. int tinyrl_printf(const tinyrl_t * tinyrl, const char *fmt, ...)
  344. {
  345. va_list args;
  346. int len;
  347. va_start(args, fmt);
  348. len = tinyrl_vt100_vprintf(tinyrl->term, fmt, args);
  349. va_end(args);
  350. return len;
  351. }
  352. /*----------------------------------------------------------------------- */
  353. static void tinyrl_internal_print(const tinyrl_t * tinyrl, const char *text)
  354. {
  355. if (tinyrl->echo_enabled) {
  356. /* simply echo the line */
  357. tinyrl_vt100_printf(tinyrl->term, "%s", text);
  358. } else {
  359. /* replace the line with echo char if defined */
  360. if (tinyrl->echo_char) {
  361. unsigned int i = strlen(text);
  362. while (i--) {
  363. tinyrl_vt100_printf(tinyrl->term, "%c",
  364. tinyrl->echo_char);
  365. }
  366. }
  367. }
  368. }
  369. /*----------------------------------------------------------------------- */
  370. static void tinyrl_internal_position(const tinyrl_t *tinyrl, int prompt_len,
  371. int line_len, unsigned int width)
  372. {
  373. int rows, cols;
  374. rows = ((line_len + prompt_len) / width) - (prompt_len / width);
  375. cols = ((line_len + prompt_len) % width) - (prompt_len % width);
  376. if (cols > 0)
  377. tinyrl_vt100_cursor_back(tinyrl->term, cols);
  378. else if (cols < 0)
  379. tinyrl_vt100_cursor_forward(tinyrl->term, -cols);
  380. if (rows > 0)
  381. tinyrl_vt100_cursor_up(tinyrl->term, rows);
  382. else if (rows < 0)
  383. tinyrl_vt100_cursor_down(tinyrl->term, -rows);
  384. }
  385. /*-------------------------------------------------------- */
  386. /* Jump to first free line after current multiline input */
  387. void tinyrl_multi_crlf(const tinyrl_t * tinyrl)
  388. {
  389. unsigned int line_size = strlen(tinyrl->last_buffer);
  390. unsigned int line_len = utf8_nsyms(tinyrl, tinyrl->last_buffer, line_size);
  391. unsigned int count = utf8_nsyms(tinyrl, tinyrl->last_buffer, tinyrl->last_point);
  392. tinyrl_internal_position(tinyrl, tinyrl->prompt_len + line_len,
  393. - (line_len - count), tinyrl->width);
  394. tinyrl_crlf(tinyrl);
  395. tinyrl_vt100_oflush(tinyrl->term);
  396. }
  397. /*----------------------------------------------------------------------- */
  398. void tinyrl_redisplay(tinyrl_t * tinyrl)
  399. {
  400. unsigned int line_size = strlen(tinyrl->line);
  401. unsigned int line_len = utf8_nsyms(tinyrl, tinyrl->line, line_size);
  402. unsigned int width = tinyrl_vt100__get_width(tinyrl->term);
  403. unsigned int count, eq_chars = 0;
  404. int cols;
  405. /* Prepare print position */
  406. if (tinyrl->last_buffer && (width == tinyrl->width)) {
  407. unsigned int eq_len = 0;
  408. /* If line and last line have the equal chars at begining */
  409. eq_chars = lub_string_equal_part(tinyrl->line, tinyrl->last_buffer,
  410. tinyrl->utf8);
  411. eq_len = utf8_nsyms(tinyrl, tinyrl->last_buffer, eq_chars);
  412. count = utf8_nsyms(tinyrl, tinyrl->last_buffer, tinyrl->last_point);
  413. tinyrl_internal_position(tinyrl, tinyrl->prompt_len + eq_len,
  414. count - eq_len, width);
  415. } else {
  416. /* Prepare to resize */
  417. if (width != tinyrl->width) {
  418. tinyrl_vt100_next_line(tinyrl->term);
  419. tinyrl_vt100_erase_down(tinyrl->term);
  420. }
  421. tinyrl_vt100_printf(tinyrl->term, "%s", tinyrl->prompt);
  422. }
  423. /* Print current line */
  424. tinyrl_internal_print(tinyrl, tinyrl->line + eq_chars);
  425. cols = (tinyrl->prompt_len + line_len) % width;
  426. if (!cols && (line_size - eq_chars))
  427. tinyrl_vt100_next_line(tinyrl->term);
  428. /* Erase down if current line is shorter than previous one */
  429. if (tinyrl->last_line_size > line_size)
  430. tinyrl_vt100_erase_down(tinyrl->term);
  431. /* Move the cursor to the insertion point */
  432. if (tinyrl->point < line_size) {
  433. unsigned int pre_len = utf8_nsyms(tinyrl,
  434. tinyrl->line, tinyrl->point);
  435. count = utf8_nsyms(tinyrl, tinyrl->line + tinyrl->point,
  436. line_size - tinyrl->point);
  437. tinyrl_internal_position(tinyrl, tinyrl->prompt_len + pre_len,
  438. count, width);
  439. }
  440. /* Update the display */
  441. tinyrl_vt100_oflush(tinyrl->term);
  442. /* Save the last line buffer */
  443. lub_string_free(tinyrl->last_buffer);
  444. tinyrl->last_buffer = lub_string_dup(tinyrl->line);
  445. tinyrl->last_point = tinyrl->point;
  446. tinyrl->width = width;
  447. tinyrl->last_line_size = line_size;
  448. }
  449. static char *internal_insertline(tinyrl_t * tinyrl, char *buffer)
  450. {
  451. char *p;
  452. char *s = buffer;
  453. /* strip any spurious '\r' or '\n' */
  454. if ((p = strchr(buffer, '\r')))
  455. *p = '\0';
  456. if ((p = strchr(buffer, '\n')))
  457. *p = '\0';
  458. /* skip any whitespace at the beginning of the line */
  459. if (0 == tinyrl->point) {
  460. while (*s && isspace(*s))
  461. s++;
  462. }
  463. if (*s) {
  464. /* append tinyrl string to the input buffer */
  465. (void)tinyrl_insert_text(tinyrl, s);
  466. }
  467. /* echo the command to the output stream */
  468. tinyrl_redisplay(tinyrl);
  469. return s;
  470. }
  471. /*----------------------------------------------------------------------- */
  472. static char *internal_readline(tinyrl_t * tinyrl,
  473. void *context, const char *str)
  474. {
  475. FILE *istream = tinyrl_vt100__get_istream(tinyrl->term);
  476. char *result = NULL;
  477. int lerrno = 0;
  478. tinyrl->done = BOOL_FALSE;
  479. tinyrl->point = 0;
  480. tinyrl->end = 0;
  481. tinyrl->buffer = lub_string_dup("");
  482. tinyrl->buffer_size = strlen(tinyrl->buffer);
  483. tinyrl->line = tinyrl->buffer;
  484. tinyrl->context = context;
  485. /* Interactive session */
  486. if (isatty(fileno(tinyrl->istream)) && !str) {
  487. unsigned int utf8_cont = 0; /* UTF-8 continue bytes */
  488. unsigned int esc_cont = 0; /* Escape sequence continues */
  489. char esc_seq[10]; /* Buffer for ESC sequence */
  490. char *esc_p = esc_seq;
  491. /* Set the terminal into raw mode */
  492. tty_raw_mode(tinyrl);
  493. tinyrl_reset_line_state(tinyrl);
  494. while (!tinyrl->done) {
  495. int key;
  496. key = tinyrl_getchar(tinyrl);
  497. /* Error || EOF || Timeout */
  498. if (key < 0) {
  499. if ((VT100_TIMEOUT == key) &&
  500. !tinyrl->timeout_fn(tinyrl))
  501. continue;
  502. /* It's time to finish the session */
  503. tinyrl->done = BOOL_TRUE;
  504. tinyrl->line = NULL;
  505. lerrno = ENOENT;
  506. continue;
  507. }
  508. /* Real key pressed */
  509. /* Common callback for any key */
  510. if (tinyrl->keypress_fn)
  511. tinyrl->keypress_fn(tinyrl, key);
  512. /* Check for ESC sequence. It's a special case. */
  513. if (!esc_cont && (key == KEY_ESC)) {
  514. esc_cont = 1; /* Start ESC sequence */
  515. esc_p = esc_seq;
  516. continue;
  517. }
  518. if (esc_cont) {
  519. /* Broken sequence */
  520. if (esc_p >= (esc_seq + sizeof(esc_seq) - 1)) {
  521. esc_cont = 0;
  522. continue;
  523. }
  524. /* Dump the control sequence into sequence buffer
  525. ANSI standard control sequences will end
  526. with a character between 64 - 126 */
  527. *esc_p = key & 0xff;
  528. esc_p++;
  529. /* tinyrl is an ANSI control sequence terminator code */
  530. if ((key != '[') && (key > 63)) {
  531. *esc_p = '\0';
  532. tinyrl_escape_seq(tinyrl, esc_seq);
  533. esc_cont = 0;
  534. tinyrl_redisplay(tinyrl);
  535. }
  536. continue;
  537. }
  538. /* Call the handler for tinyrl key */
  539. if (!tinyrl->handlers[key](tinyrl, key))
  540. tinyrl_ding(tinyrl);
  541. if (tinyrl->done) /* Some handler set the done flag */
  542. continue; /* It will break the loop */
  543. if (tinyrl->utf8) {
  544. if (!(UTF8_7BIT_MASK & key)) /* ASCII char */
  545. utf8_cont = 0;
  546. else if (utf8_cont && (UTF8_10 == (key & UTF8_MASK))) /* Continue byte */
  547. utf8_cont--;
  548. else if (UTF8_11 == (key & UTF8_MASK)) { /* First byte of multibyte char */
  549. /* Find out number of char's bytes */
  550. int b = key;
  551. utf8_cont = 0;
  552. while ((utf8_cont < 6) && (UTF8_10 != (b & UTF8_MASK))) {
  553. utf8_cont++;
  554. b = b << 1;
  555. }
  556. }
  557. }
  558. /* For non UTF-8 encoding the utf8_cont is always 0.
  559. For UTF-8 it's 0 when one-byte symbol or we get
  560. all bytes for the current multibyte character. */
  561. if (!utf8_cont)
  562. tinyrl_redisplay(tinyrl);
  563. }
  564. /* If the last character in the line (other than NULL)
  565. is a space remove it. */
  566. if (tinyrl->end && tinyrl->line && isspace(tinyrl->line[tinyrl->end - 1]))
  567. tinyrl_delete_text(tinyrl, tinyrl->end - 1, tinyrl->end);
  568. /* Restores the terminal mode */
  569. tty_restore_mode(tinyrl);
  570. /* Non-interactive session */
  571. } else {
  572. char *s = NULL, buffer[80];
  573. size_t len = sizeof(buffer);
  574. char *tmp = NULL;
  575. /* manually reset the line state without redisplaying */
  576. lub_string_free(tinyrl->last_buffer);
  577. tinyrl->last_buffer = NULL;
  578. if (str) {
  579. tmp = lub_string_dup(str);
  580. internal_insertline(tinyrl, tmp);
  581. } else {
  582. while (istream && (sizeof(buffer) == len) &&
  583. (s = fgets(buffer, sizeof(buffer), istream))) {
  584. s = internal_insertline(tinyrl, buffer);
  585. len = strlen(buffer) + 1; /* account for the '\0' */
  586. }
  587. if (!s || ((tinyrl->line[0] == '\0') && feof(istream))) {
  588. /* time to finish the session */
  589. tinyrl->line = NULL;
  590. lerrno = ENOENT;
  591. }
  592. }
  593. /*
  594. * check against fgets returning null as either error or end of file.
  595. * tinyrl is a measure to stop potential task spin on encountering an
  596. * error from fgets.
  597. */
  598. if (tinyrl->line && !tinyrl->handlers[KEY_LF](tinyrl, KEY_LF)) {
  599. /* an issue has occured */
  600. tinyrl->line = NULL;
  601. lerrno = ENOEXEC;
  602. }
  603. if (str)
  604. lub_string_free(tmp);
  605. }
  606. /*
  607. * duplicate the string for return to the client
  608. * we have to duplicate as we may be referencing a
  609. * history entry or our internal buffer
  610. */
  611. result = tinyrl->line ? lub_string_dup(tinyrl->line) : NULL;
  612. /* free our internal buffer */
  613. free(tinyrl->buffer);
  614. tinyrl->buffer = NULL;
  615. if (!result)
  616. errno = lerrno; /* get saved errno */
  617. return result;
  618. }
  619. /*----------------------------------------------------------------------- */
  620. char *tinyrl_readline(tinyrl_t * tinyrl, void *context)
  621. {
  622. return internal_readline(tinyrl, context, NULL);
  623. }
  624. /*----------------------------------------------------------------------- */
  625. char *tinyrl_forceline(tinyrl_t * tinyrl, void *context, const char *line)
  626. {
  627. return internal_readline(tinyrl, context, line);
  628. }
  629. /*----------------------------------------------------------------------- */
  630. /*
  631. * Insert text into the line at the current cursor position.
  632. */
  633. bool_t tinyrl_insert_text(tinyrl_t * tinyrl, const char *text)
  634. {
  635. unsigned int delta = strlen(text);
  636. /*
  637. * If the client wants to change the line ensure that the line and buffer
  638. * references are in sync
  639. */
  640. changed_line(tinyrl);
  641. if ((delta + tinyrl->end) > (tinyrl->buffer_size)) {
  642. /* extend the current buffer */
  643. if (BOOL_FALSE ==
  644. tinyrl_extend_line_buffer(tinyrl, tinyrl->end + delta))
  645. return BOOL_FALSE;
  646. }
  647. if (tinyrl->point < tinyrl->end) {
  648. /* move the current text to the right (including the terminator) */
  649. memmove(&tinyrl->buffer[tinyrl->point + delta],
  650. &tinyrl->buffer[tinyrl->point],
  651. (tinyrl->end - tinyrl->point) + 1);
  652. } else {
  653. /* terminate the string */
  654. tinyrl->buffer[tinyrl->end + delta] = '\0';
  655. }
  656. /* insert the new text */
  657. strncpy(&tinyrl->buffer[tinyrl->point], text, delta);
  658. /* now update the indexes */
  659. tinyrl->point += delta;
  660. tinyrl->end += delta;
  661. return BOOL_TRUE;
  662. }
  663. /*----------------------------------------------------------------------- */
  664. /*
  665. * A convenience function for displaying a list of strings in columnar
  666. * format on Readline's output stream. matches is the list of strings,
  667. * in argv format, such as a list of completion matches. len is the number
  668. * of strings in matches, and max is the length of the longest string in matches.
  669. * tinyrl function uses the setting of print-completions-horizontally to select
  670. * how the matches are displayed
  671. */
  672. void tinyrl_display_matches(const tinyrl_t *tinyrl,
  673. char *const *matches, unsigned int len, size_t max)
  674. {
  675. unsigned int width = tinyrl_vt100__get_width(tinyrl->term);
  676. unsigned int cols, rows;
  677. /* Find out column and rows number */
  678. if (max < width)
  679. cols = (width + 1) / (max + 1); /* allow for a space between words */
  680. else
  681. cols = 1;
  682. rows = len / cols + 1;
  683. assert(matches);
  684. if (matches) {
  685. unsigned int r, c;
  686. len--, matches++; /* skip the subtitution string */
  687. /* Print out a table of completions */
  688. for (r = 0; r < rows && len; r++) {
  689. for (c = 0; c < cols && len; c++) {
  690. const char *match = *matches++;
  691. len--;
  692. if ((c + 1) == cols) /* Last str in row */
  693. tinyrl_vt100_printf(tinyrl->term, "%s",
  694. match);
  695. else
  696. tinyrl_vt100_printf(tinyrl->term, "%-*s ",
  697. max, match);
  698. }
  699. tinyrl_crlf(tinyrl);
  700. }
  701. }
  702. }
  703. /*----------------------------------------------------------------------- */
  704. /*
  705. * Delete the text between start and end in the current line. (inclusive)
  706. * tinyrl adjusts the rl_point and rl_end indexes appropriately.
  707. */
  708. void tinyrl_delete_text(tinyrl_t * tinyrl, unsigned int start, unsigned int end)
  709. {
  710. unsigned int delta;
  711. /*
  712. * If the client wants to change the line ensure that the line and buffer
  713. * references are in sync
  714. */
  715. changed_line(tinyrl);
  716. /* make sure we play it safe */
  717. if (start > end) {
  718. unsigned int tmp = end;
  719. start = end;
  720. end = tmp;
  721. }
  722. if (end > tinyrl->end)
  723. end = tinyrl->end;
  724. delta = (end - start) + 1;
  725. /* move any text which is left */
  726. memmove(&tinyrl->buffer[start],
  727. &tinyrl->buffer[start + delta], tinyrl->end - end);
  728. /* now adjust the indexs */
  729. if (tinyrl->point >= start) {
  730. if (tinyrl->point > end) {
  731. /* move the insertion point back appropriately */
  732. tinyrl->point -= delta;
  733. } else {
  734. /* move the insertion point to the start */
  735. tinyrl->point = start;
  736. }
  737. }
  738. if (tinyrl->end > end)
  739. tinyrl->end -= delta;
  740. else
  741. tinyrl->end = start;
  742. /* put a terminator at the end of the buffer */
  743. tinyrl->buffer[tinyrl->end] = '\0';
  744. }
  745. /*-------------------------------------------------------- */
  746. /*
  747. * Returns an array of strings which is a list of completions for text.
  748. * If there are no completions, returns NULL. The first entry in the
  749. * returned array is the substitution for text. The remaining entries
  750. * are the possible completions. The array is terminated with a NULL pointer.
  751. *
  752. * entry_func is a function of two args, and returns a char *.
  753. * The first argument is text. The second is a state argument;
  754. * it is zero on the first call, and non-zero on subsequent calls.
  755. * entry_func returns a NULL pointer to the caller when there are no
  756. * more matches.
  757. */
  758. char **tinyrl_completion(tinyrl_t * tinyrl,
  759. const char *line, unsigned int start, unsigned int end,
  760. tinyrl_compentry_func_t * entry_func)
  761. {
  762. unsigned int state = 0;
  763. size_t size = 1;
  764. unsigned int offset = 1; /* Need at least one entry for the substitution */
  765. char **matches = NULL;
  766. char *match;
  767. /* duplicate the string upto the insertion point */
  768. char *text = lub_string_dupn(line, end);
  769. /* now try and find possible completions */
  770. while ((match = entry_func(tinyrl, text, start, state++))) {
  771. if (size == offset) {
  772. /* resize the buffer if needed - the +1 is for the NULL terminator */
  773. size += 10;
  774. matches =
  775. realloc(matches, (sizeof(char *) * (size + 1)));
  776. }
  777. /* not much we can do... */
  778. if (!matches)
  779. break;
  780. matches[offset] = match;
  781. /*
  782. * augment the substitute string with tinyrl entry
  783. */
  784. if (1 == offset) {
  785. /* let's be optimistic */
  786. matches[0] = lub_string_dup(match);
  787. } else {
  788. char *p = matches[0];
  789. size_t match_len = strlen(p);
  790. /* identify the common prefix */
  791. while ((tolower(*p) == tolower(*match)) && match_len--) {
  792. p++, match++;
  793. }
  794. /* terminate the prefix string */
  795. *p = '\0';
  796. }
  797. offset++;
  798. }
  799. /* be a good memory citizen */
  800. lub_string_free(text);
  801. if (matches)
  802. matches[offset] = NULL;
  803. return matches;
  804. }
  805. /*-------------------------------------------------------- */
  806. void tinyrl_delete_matches(char **tinyrl)
  807. {
  808. char **matches = tinyrl;
  809. while (*matches) {
  810. /* release the memory for each contained string */
  811. free(*matches++);
  812. }
  813. /* release the memory for the array */
  814. free(tinyrl);
  815. }
  816. /*-------------------------------------------------------- */
  817. void tinyrl_crlf(const tinyrl_t * tinyrl)
  818. {
  819. tinyrl_vt100_printf(tinyrl->term, "\n");
  820. }
  821. /*-------------------------------------------------------- */
  822. /*
  823. * Ring the terminal bell, obeying the setting of bell-style.
  824. */
  825. void tinyrl_ding(const tinyrl_t * tinyrl)
  826. {
  827. tinyrl_vt100_ding(tinyrl->term);
  828. }
  829. /*-------------------------------------------------------- */
  830. void tinyrl_reset_line_state(tinyrl_t * tinyrl)
  831. {
  832. lub_string_free(tinyrl->last_buffer);
  833. tinyrl->last_buffer = NULL;
  834. tinyrl->last_line_size = 0;
  835. tinyrl_redisplay(tinyrl);
  836. }
  837. /*-------------------------------------------------------- */
  838. void tinyrl_replace_line(tinyrl_t * tinyrl, const char *text, int clear_undo)
  839. {
  840. size_t new_len = strlen(text);
  841. /* ignored for now */
  842. clear_undo = clear_undo;
  843. /* ensure there is sufficient space */
  844. if (tinyrl_extend_line_buffer(tinyrl, new_len)) {
  845. /* overwrite the current contents of the buffer */
  846. strcpy(tinyrl->buffer, text);
  847. /* set the insert point and end point */
  848. tinyrl->point = tinyrl->end = new_len;
  849. }
  850. tinyrl_redisplay(tinyrl);
  851. }
  852. /*-------------------------------------------------------- */
  853. static tinyrl_match_e
  854. tinyrl_do_complete(tinyrl_t * tinyrl, bool_t with_extensions)
  855. {
  856. tinyrl_match_e result = TINYRL_NO_MATCH;
  857. char **matches = NULL;
  858. unsigned int start, end;
  859. bool_t completion = BOOL_FALSE;
  860. bool_t prefix = BOOL_FALSE;
  861. int i = 0;
  862. /* find the start and end of the current word */
  863. start = end = tinyrl->point;
  864. while (start && !isspace(tinyrl->line[start - 1]))
  865. start--;
  866. if (tinyrl->attempted_completion_function) {
  867. tinyrl->completion_over = BOOL_FALSE;
  868. tinyrl->completion_error_over = BOOL_FALSE;
  869. /* try and complete the current line buffer */
  870. matches = tinyrl->attempted_completion_function(tinyrl,
  871. tinyrl->line, start, end);
  872. }
  873. if (!matches && (BOOL_FALSE == tinyrl->completion_over)) {
  874. /* insert default completion call here... */
  875. }
  876. if (!matches)
  877. return result;
  878. /* identify and insert a common prefix if there is one */
  879. if (0 != strncmp(matches[0], &tinyrl->line[start],
  880. strlen(matches[0]))) {
  881. /*
  882. * delete the original text not including
  883. * the current insertion point character
  884. */
  885. if (tinyrl->end != end)
  886. end--;
  887. tinyrl_delete_text(tinyrl, start, end);
  888. if (BOOL_FALSE == tinyrl_insert_text(tinyrl, matches[0]))
  889. return TINYRL_NO_MATCH;
  890. completion = BOOL_TRUE;
  891. }
  892. for (i = 1; matches[i]; i++) {
  893. /* tinyrl is just a prefix string */
  894. if (0 == lub_string_nocasecmp(matches[0], matches[i]))
  895. prefix = BOOL_TRUE;
  896. }
  897. /* is there more than one completion? */
  898. if (matches[2]) {
  899. char **tmp = matches;
  900. unsigned int max, len;
  901. max = len = 0;
  902. while (*tmp) {
  903. size_t size = strlen(*tmp++);
  904. len++;
  905. if (size > max)
  906. max = size;
  907. }
  908. if (completion)
  909. result = TINYRL_COMPLETED_AMBIGUOUS;
  910. else if (prefix)
  911. result = TINYRL_MATCH_WITH_EXTENSIONS;
  912. else
  913. result = TINYRL_AMBIGUOUS;
  914. if (with_extensions || !prefix) {
  915. /* Either we always want to show extensions or
  916. * we haven't been able to complete the current line
  917. * and there is just a prefix, so let the user see the options
  918. */
  919. tinyrl_crlf(tinyrl);
  920. tinyrl_display_matches(tinyrl, matches, len, max);
  921. tinyrl_reset_line_state(tinyrl);
  922. }
  923. } else {
  924. result = completion ?
  925. TINYRL_COMPLETED_MATCH : TINYRL_MATCH;
  926. }
  927. /* free the memory */
  928. tinyrl_delete_matches(matches);
  929. /* redisplay the line */
  930. tinyrl_redisplay(tinyrl);
  931. return result;
  932. }
  933. /*-------------------------------------------------------- */
  934. tinyrl_match_e tinyrl_complete_with_extensions(tinyrl_t * tinyrl)
  935. {
  936. return tinyrl_do_complete(tinyrl, BOOL_TRUE);
  937. }
  938. /*-------------------------------------------------------- */
  939. tinyrl_match_e tinyrl_complete(tinyrl_t * tinyrl)
  940. {
  941. return tinyrl_do_complete(tinyrl, BOOL_FALSE);
  942. }
  943. /*-------------------------------------------------------- */
  944. void *tinyrl__get_context(const tinyrl_t * tinyrl)
  945. {
  946. return tinyrl->context;
  947. }
  948. /*--------------------------------------------------------- */
  949. const char *tinyrl__get_line(const tinyrl_t * tinyrl)
  950. {
  951. return tinyrl->line;
  952. }
  953. /*--------------------------------------------------------- */
  954. tinyrl_history_t *tinyrl__get_history(const tinyrl_t * tinyrl)
  955. {
  956. return tinyrl->history;
  957. }
  958. /*--------------------------------------------------------- */
  959. void tinyrl_completion_over(tinyrl_t * tinyrl)
  960. {
  961. tinyrl->completion_over = BOOL_TRUE;
  962. }
  963. /*--------------------------------------------------------- */
  964. void tinyrl_completion_error_over(tinyrl_t * tinyrl)
  965. {
  966. tinyrl->completion_error_over = BOOL_TRUE;
  967. }
  968. /*--------------------------------------------------------- */
  969. bool_t tinyrl_is_completion_error_over(const tinyrl_t * tinyrl)
  970. {
  971. return tinyrl->completion_error_over;
  972. }
  973. /*--------------------------------------------------------- */
  974. void tinyrl_done(tinyrl_t * tinyrl)
  975. {
  976. tinyrl->done = BOOL_TRUE;
  977. }
  978. /*--------------------------------------------------------- */
  979. void tinyrl_enable_echo(tinyrl_t * tinyrl)
  980. {
  981. tinyrl->echo_enabled = BOOL_TRUE;
  982. }
  983. /*--------------------------------------------------------- */
  984. void tinyrl_disable_echo(tinyrl_t * tinyrl, char echo_char)
  985. {
  986. tinyrl->echo_enabled = BOOL_FALSE;
  987. tinyrl->echo_char = echo_char;
  988. }
  989. /*-------------------------------------------------------- */
  990. const char *tinyrl__get_prompt(const tinyrl_t * tinyrl)
  991. {
  992. return tinyrl->prompt;
  993. }
  994. /*-------------------------------------------------------- */
  995. void tinyrl__set_prompt(tinyrl_t *tinyrl, const char *prompt)
  996. {
  997. if (tinyrl->prompt) {
  998. lub_string_free(tinyrl->prompt);
  999. tinyrl->prompt_size = 0;
  1000. tinyrl->prompt_len = 0;
  1001. }
  1002. tinyrl->prompt = lub_string_dup(prompt);
  1003. if (tinyrl->prompt) {
  1004. tinyrl->prompt_size = strlen(tinyrl->prompt);
  1005. tinyrl->prompt_len = utf8_nsyms(tinyrl, tinyrl->prompt,
  1006. tinyrl->prompt_size);
  1007. }
  1008. }
  1009. /*-------------------------------------------------------- */
  1010. void tinyrl__set_hotkey_fn(tinyrl_t *tinyrl,
  1011. tinyrl_key_func_t *fn)
  1012. {
  1013. tinyrl->hotkey_fn = fn;
  1014. }
  1015. /*-------------------------------------------------------- */
  1016. bool_t tinyrl_is_quoting(const tinyrl_t * tinyrl)
  1017. {
  1018. bool_t result = BOOL_FALSE;
  1019. /* count the quotes upto the current insertion point */
  1020. unsigned int i = 0;
  1021. while (i < tinyrl->point) {
  1022. if (result && (tinyrl->line[i] == '\\')) {
  1023. i++;
  1024. if (i >= tinyrl->point)
  1025. break;
  1026. i++;
  1027. continue;
  1028. }
  1029. if (tinyrl->line[i++] == '"') {
  1030. result = result ? BOOL_FALSE : BOOL_TRUE;
  1031. }
  1032. }
  1033. return result;
  1034. }
  1035. /*-------------------------------------------------------- */
  1036. bool_t tinyrl_is_empty(const tinyrl_t *tinyrl)
  1037. {
  1038. return (tinyrl->point == 0) ? BOOL_TRUE : BOOL_FALSE;
  1039. }
  1040. /*--------------------------------------------------------- */
  1041. void tinyrl_limit_line_length(tinyrl_t * tinyrl, unsigned int length)
  1042. {
  1043. tinyrl->max_line_length = length;
  1044. }
  1045. #endif