rline.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. /* vim: tabstop=4 shiftwidth=4 noexpandtab
  2. * This file is part of ToaruOS and is released under the terms
  3. * of the NCSA / University of Illinois License - see LICENSE.md
  4. * Copyright (C) 2015-2018 K. Lange
  5. *
  6. * rline - a line reading library.
  7. *
  8. * Implements an interface similar to readline, providing more
  9. * complex line editing than what the raw tty interface supplies.
  10. */
  11. #define _POSIX_C_SOURCE 1
  12. #define _XOPEN_SOURCE 500
  13. #include <stdint.h>
  14. #include <stdio.h>
  15. #include <stdlib.h>
  16. #include <string.h>
  17. #include <termios.h>
  18. #include <toaru/kbd.h>
  19. #include <toaru/rline.h>
  20. static struct termios old;
  21. static void set_unbuffered() {
  22. tcgetattr(fileno(stdin), &old);
  23. struct termios new = old;
  24. new.c_lflag &= (~ICANON & ~ECHO);
  25. tcsetattr(fileno(stdin), TCSAFLUSH, &new);
  26. }
  27. static void set_buffered() {
  28. tcsetattr(fileno(stdin), TCSAFLUSH, &old);
  29. }
  30. void rline_redraw(rline_context_t * context) {
  31. if (context->quiet) return;
  32. printf("\033[u%s\033[K", context->buffer);
  33. for (int i = context->offset; i < context->collected; ++i) {
  34. printf("\033[D");
  35. }
  36. fflush(stdout);
  37. }
  38. void rline_redraw_clean(rline_context_t * context) {
  39. if (context->quiet) return;
  40. printf("\033[u%s", context->buffer);
  41. for (int i = context->offset; i < context->collected; ++i) {
  42. printf("\033[D");
  43. }
  44. fflush(stdout);
  45. }
  46. char * rline_history[RLINE_HISTORY_ENTRIES];
  47. int rline_history_count = 0;
  48. int rline_history_offset = 0;
  49. int rline_scroll = 0;
  50. char * rline_exit_string = "exit\n";
  51. static char rline_temp[1024];
  52. void rline_history_insert(char * str) {
  53. if (str[strlen(str)-1] == '\n') {
  54. str[strlen(str)-1] = '\0';
  55. }
  56. if (rline_history_count) {
  57. if (!strcmp(str, rline_history_prev(1))) {
  58. free(str);
  59. return;
  60. }
  61. }
  62. if (rline_history_count == RLINE_HISTORY_ENTRIES) {
  63. free(rline_history[rline_history_offset]);
  64. rline_history[rline_history_offset] = str;
  65. rline_history_offset = (rline_history_offset + 1) % RLINE_HISTORY_ENTRIES;
  66. } else {
  67. rline_history[rline_history_count] = str;
  68. rline_history_count++;
  69. }
  70. }
  71. void rline_history_append_line(char * str) {
  72. if (rline_history_count) {
  73. char ** s = &rline_history[(rline_history_count - 1 + rline_history_offset) % RLINE_HISTORY_ENTRIES];
  74. char * c = malloc(strlen(*s) + strlen(str) + 2);
  75. sprintf(c, "%s\n%s", *s, str);
  76. if (c[strlen(c)-1] == '\n') {
  77. c[strlen(c)-1] = '\0';
  78. }
  79. free(*s);
  80. *s = c;
  81. } else {
  82. /* wat */
  83. }
  84. }
  85. char * rline_history_get(int item) {
  86. return rline_history[(item + rline_history_offset) % RLINE_HISTORY_ENTRIES];
  87. }
  88. char * rline_history_prev(int item) {
  89. return rline_history_get(rline_history_count - item);
  90. }
  91. void rline_reverse_search(rline_context_t * context) {
  92. char input[512] = {0};
  93. int collected = 0;
  94. int start_at = 0;
  95. int changed = 0;
  96. fprintf(stderr, "\033[G\033[0m\033[s");
  97. fflush(stderr);
  98. key_event_state_t kbd_state = {0};
  99. char * match = "";
  100. int match_index = 0;
  101. while (1) {
  102. /* Find matches */
  103. try_rev_search_again:
  104. if (collected && changed) {
  105. match = "";
  106. match_index = 0;
  107. for (int i = start_at; i < rline_history_count; i++) {
  108. char * c = rline_history_prev(i+1);
  109. if (strstr(c, input)) {
  110. match = c;
  111. match_index = i;
  112. break;
  113. }
  114. }
  115. if (!strcmp(match,"")) {
  116. if (start_at) {
  117. start_at = 0;
  118. goto try_rev_search_again;
  119. }
  120. collected--;
  121. input[collected] = '\0';
  122. if (collected) {
  123. goto try_rev_search_again;
  124. }
  125. }
  126. }
  127. fprintf(stderr, "\033[u(reverse-i-search)`%s': %s\033[K", input, match);
  128. fflush(stderr);
  129. changed = 0;
  130. uint32_t key_sym = kbd_key(&kbd_state, fgetc(stdin));
  131. switch (key_sym) {
  132. case KEY_NONE:
  133. break;
  134. case KEY_BACKSPACE:
  135. if (collected > 0) {
  136. collected--;
  137. input[collected] = '\0';
  138. start_at = 0;
  139. changed = 1;
  140. }
  141. break;
  142. case KEY_CTRL_C:
  143. printf("^C\n");
  144. return;
  145. case KEY_CTRL_R:
  146. start_at = match_index + 1;
  147. changed = 1;
  148. break;
  149. case KEY_ESCAPE:
  150. case KEY_ARROW_LEFT:
  151. case KEY_ARROW_RIGHT:
  152. context->cancel = 1;
  153. case '\n':
  154. memcpy(context->buffer, match, strlen(match) + 1);
  155. context->collected = strlen(match);
  156. context->offset = context->collected;
  157. if (!context->quiet && context->callbacks->redraw_prompt) {
  158. fprintf(stderr, "\033[G\033[K");
  159. context->callbacks->redraw_prompt(context);
  160. }
  161. fprintf(stderr, "\033[s");
  162. rline_redraw_clean(context);
  163. if (key_sym == '\n' && !context->quiet) {
  164. fprintf(stderr, "\n");
  165. }
  166. return;
  167. default:
  168. if (key_sym < KEY_NORMAL_MAX) {
  169. input[collected] = (char)key_sym;
  170. collected++;
  171. input[collected] = '\0';
  172. start_at = 0;
  173. changed = 1;
  174. }
  175. break;
  176. }
  177. }
  178. }
  179. static void history_previous(rline_context_t * context) {
  180. if (rline_scroll == 0) {
  181. memcpy(rline_temp, context->buffer, strlen(context->buffer) + 1);
  182. }
  183. if (rline_scroll < rline_history_count) {
  184. rline_scroll++;
  185. for (int i = 0; i < (int)strlen(context->buffer); ++i) {
  186. printf("\010 \010");
  187. }
  188. char * h = rline_history_prev(rline_scroll);
  189. memcpy(context->buffer, h, strlen(h) + 1);
  190. printf("\033[u%s\033[K", h);
  191. fflush(stdout);
  192. }
  193. context->collected = strlen(context->buffer);
  194. context->offset = context->collected;
  195. }
  196. static void history_next(rline_context_t * context) {
  197. if (rline_scroll > 1) {
  198. rline_scroll--;
  199. for (int i = 0; i < (int)strlen(context->buffer); ++i) {
  200. printf("\010 \010");
  201. }
  202. char * h = rline_history_prev(rline_scroll);
  203. memcpy(context->buffer, h, strlen(h) + 1);
  204. printf("%s", h);
  205. fflush(stdout);
  206. } else if (rline_scroll == 1) {
  207. for (int i = 0; i < (int)strlen(context->buffer); ++i) {
  208. printf("\010 \010");
  209. }
  210. rline_scroll = 0;
  211. memcpy(context->buffer, rline_temp, strlen(rline_temp) + 1);
  212. printf("\033[u%s\033[K", context->buffer);
  213. fflush(stdout);
  214. }
  215. context->collected = strlen(context->buffer);
  216. context->offset = context->collected;
  217. }
  218. /**
  219. * Insert characters at the current cursor offset.
  220. */
  221. void rline_insert(rline_context_t * context, const char * what) {
  222. size_t insertion_length = strlen(what);
  223. if (context->collected + (int)insertion_length > context->requested) {
  224. insertion_length = context->requested - context->collected;
  225. }
  226. /* Move */
  227. memmove(&context->buffer[context->offset + insertion_length], &context->buffer[context->offset], context->collected - context->offset);
  228. memcpy(&context->buffer[context->offset], what, insertion_length);
  229. context->collected += insertion_length;
  230. context->offset += insertion_length;
  231. }
  232. static rline_callbacks_t _rline_null_callbacks = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};
  233. int rline(char * buffer, int buf_size, rline_callbacks_t * callbacks) {
  234. /* Initialize context */
  235. rline_context_t context = {
  236. buffer,
  237. callbacks,
  238. 0,
  239. buf_size,
  240. 0,
  241. 0,
  242. 0,
  243. 0,
  244. 0,
  245. };
  246. if (!callbacks) {
  247. callbacks = &_rline_null_callbacks;
  248. }
  249. set_unbuffered();
  250. printf("\033[s");
  251. fflush(stdout);
  252. key_event_state_t kbd_state = {0};
  253. /* Read keys */
  254. while ((context.collected < context.requested) && (!context.newline)) {
  255. uint32_t key_sym = kbd_key(&kbd_state, fgetc(stdin));
  256. if (key_sym == KEY_NONE) continue;
  257. if (key_sym != '\t') context.tabbed = 0;
  258. switch (key_sym) {
  259. case KEY_CTRL_C:
  260. printf("^C\n");
  261. context.buffer[0] = '\0';
  262. set_buffered();
  263. return 0;
  264. case KEY_CTRL_R:
  265. if (callbacks->rev_search) {
  266. callbacks->rev_search(&context);
  267. } else {
  268. rline_reverse_search(&context);
  269. }
  270. if (context.cancel) {
  271. context.cancel = 0;
  272. continue;
  273. } else {
  274. set_buffered();
  275. return context.collected;
  276. }
  277. case KEY_ARROW_UP:
  278. case KEY_CTRL_P:
  279. if (callbacks->key_up) {
  280. callbacks->key_up(&context);
  281. } else {
  282. history_previous(&context);
  283. }
  284. continue;
  285. case KEY_ARROW_DOWN:
  286. case KEY_CTRL_N:
  287. if (callbacks->key_down) {
  288. callbacks->key_down(&context);
  289. } else {
  290. history_next(&context);
  291. }
  292. continue;
  293. case KEY_CTRL_ARROW_RIGHT:
  294. while (context.offset < context.collected && context.buffer[context.offset] == ' ') {
  295. context.offset++;
  296. printf("\033[C");
  297. }
  298. while (context.offset < context.collected) {
  299. context.offset++;
  300. printf("\033[C");
  301. if (context.buffer[context.offset] == ' ') break;
  302. }
  303. fflush(stdout);
  304. continue;
  305. case KEY_CTRL_ARROW_LEFT:
  306. if (context.offset == 0) continue;
  307. context.offset--;
  308. printf("\033[D");
  309. while (context.offset && context.buffer[context.offset] == ' ') {
  310. context.offset--;
  311. printf("\033[D");
  312. }
  313. while (context.offset > 0) {
  314. if (context.buffer[context.offset-1] == ' ') break;
  315. context.offset--;
  316. printf("\033[D");
  317. }
  318. fflush(stdout);
  319. continue;
  320. case KEY_ARROW_RIGHT:
  321. if (callbacks->key_right) {
  322. callbacks->key_right(&context);
  323. } else {
  324. if (context.offset < context.collected) {
  325. printf("\033[C");
  326. fflush(stdout);
  327. context.offset++;
  328. }
  329. }
  330. continue;
  331. case KEY_ARROW_LEFT:
  332. if (callbacks->key_left) {
  333. callbacks->key_left(&context);
  334. } else {
  335. if (context.offset > 0) {
  336. printf("\033[D");
  337. fflush(stdout);
  338. context.offset--;
  339. }
  340. }
  341. continue;
  342. case KEY_CTRL_A:
  343. case KEY_HOME:
  344. while (context.offset > 0) {
  345. printf("\033[D");
  346. context.offset--;
  347. }
  348. fflush(stdout);
  349. continue;
  350. case KEY_CTRL_E:
  351. case KEY_END:
  352. while (context.offset < context.collected) {
  353. printf("\033[C");
  354. context.offset++;
  355. }
  356. fflush(stdout);
  357. continue;
  358. case KEY_CTRL_K:
  359. context.collected = context.offset;
  360. printf("\033[K");
  361. fflush(stdout);
  362. continue;
  363. case KEY_CTRL_D:
  364. if (context.collected == 0) {
  365. printf(rline_exit_string);
  366. sprintf(context.buffer, rline_exit_string);
  367. set_buffered();
  368. return strlen(context.buffer);
  369. }
  370. /* Intentional fallthrough */
  371. case KEY_DEL:
  372. if (context.collected) {
  373. if (context.offset == context.collected) {
  374. continue;
  375. }
  376. int remaining = context.collected - context.offset;
  377. for (int i = 1; i < remaining; ++i) {
  378. printf("%c", context.buffer[context.offset + i]);
  379. context.buffer[context.offset + i - 1] = context.buffer[context.offset + i];
  380. }
  381. printf(" ");
  382. for (int i = 0; i < remaining; ++i) {
  383. printf("\033[D");
  384. }
  385. context.collected--;
  386. fflush(stdout);
  387. }
  388. continue;
  389. case KEY_BACKSPACE:
  390. if (context.collected) {
  391. int should_redraw = 0;
  392. if (!context.offset) {
  393. continue;
  394. }
  395. printf("\010 \010");
  396. if (context.buffer[context.offset-1] == '\t') {
  397. should_redraw = 1;
  398. }
  399. if (context.offset != context.collected) {
  400. int remaining = context.collected - context.offset;
  401. for (int i = 0; i < remaining; ++i) {
  402. printf("%c", context.buffer[context.offset + i]);
  403. context.buffer[context.offset + i - 1] = context.buffer[context.offset + i];
  404. }
  405. printf(" ");
  406. for (int i = 0; i < remaining + 1; ++i) {
  407. printf("\033[D");
  408. }
  409. context.offset--;
  410. context.collected--;
  411. } else {
  412. context.buffer[--context.collected] = '\0';
  413. context.offset--;
  414. }
  415. if (should_redraw) {
  416. rline_redraw_clean(&context);
  417. }
  418. fflush(stdout);
  419. }
  420. continue;
  421. case KEY_CTRL_L: /* ^L: Clear Screen, redraw prompt and buffer */
  422. printf("\033[H\033[2J");
  423. fflush(stdout);
  424. /* Flush before yielding control to potentially foreign environment. */
  425. if (callbacks->redraw_prompt) {
  426. callbacks->redraw_prompt(&context);
  427. }
  428. printf("\033[s");
  429. rline_redraw_clean(&context);
  430. continue;
  431. case KEY_CTRL_W:
  432. /*
  433. * Erase word before cursor.
  434. * If the character before the cursor is a space, delete it.
  435. * Continue deleting until the previous character is a space.
  436. */
  437. if (context.collected) {
  438. if (!context.offset) {
  439. continue;
  440. }
  441. do {
  442. printf("\010 \010");
  443. if (context.offset != context.collected) {
  444. int remaining = context.collected - context.offset;
  445. for (int i = 0; i < remaining; ++i) {
  446. printf("%c", context.buffer[context.offset + i]);
  447. context.buffer[context.offset + i - 1] = context.buffer[context.offset + i];
  448. }
  449. printf(" ");
  450. for (int i = 0; i < remaining + 1; ++i) {
  451. printf("\033[D");
  452. }
  453. context.offset--;
  454. context.collected--;
  455. } else {
  456. context.buffer[--context.collected] = '\0';
  457. context.offset--;
  458. }
  459. } while ((context.offset) && (context.buffer[context.offset-1] != ' '));
  460. fflush(stdout);
  461. }
  462. continue;
  463. case '\t':
  464. if (callbacks->tab_complete) {
  465. callbacks->tab_complete(&context);
  466. }
  467. continue;
  468. case '\n':
  469. while (context.offset < context.collected) {
  470. printf("\033[C");
  471. context.offset++;
  472. }
  473. if (context.collected < context.requested) {
  474. context.buffer[context.collected] = '\n';
  475. context.buffer[++context.collected] = '\0';
  476. context.offset++;
  477. }
  478. printf("\n");
  479. fflush(stdout);
  480. context.newline = 1;
  481. continue;
  482. }
  483. if (context.offset != context.collected) {
  484. for (int i = context.collected; i > context.offset; --i) {
  485. context.buffer[i] = context.buffer[i-1];
  486. }
  487. if (context.collected < context.requested) {
  488. context.buffer[context.offset] = (char)key_sym;
  489. context.buffer[++context.collected] = '\0';
  490. context.offset++;
  491. }
  492. for (int i = context.offset - 1; i < context.collected; ++i) {
  493. printf("%c", context.buffer[i]);
  494. }
  495. for (int i = context.offset; i < context.collected; ++i) {
  496. printf("\033[D");
  497. }
  498. fflush(stdout);
  499. } else {
  500. printf("%c", (char)key_sym);
  501. if (context.collected < context.requested) {
  502. context.buffer[context.collected] = (char)key_sym;
  503. context.buffer[++context.collected] = '\0';
  504. context.offset++;
  505. }
  506. fflush(stdout);
  507. }
  508. }
  509. /* Cap that with a null */
  510. context.buffer[context.collected] = '\0';
  511. set_buffered();
  512. return context.collected;
  513. }
  514. static char * last_prompt = NULL;
  515. static void redraw_prompt(rline_context_t * c) {
  516. (void)c;
  517. printf("%s", last_prompt);
  518. fflush(stdout);
  519. return;
  520. }
  521. static void insert_tab(rline_context_t * c) {
  522. rline_insert(c, "\t");
  523. rline_redraw_clean(c);
  524. }
  525. void * rline_for_python(void * _stdin, void * _stdout, char * prompt) {
  526. last_prompt = prompt;
  527. rline_callbacks_t callbacks = {
  528. insert_tab, redraw_prompt, NULL,
  529. NULL, NULL, NULL, NULL, NULL
  530. };
  531. redraw_prompt(NULL);
  532. char * buf = malloc(1024);
  533. memset(buf, 0, 1024);
  534. rline(buf, 1024, &callbacks);
  535. rline_history_insert(strdup(buf));
  536. rline_scroll = 0;
  537. return buf;
  538. }