rline_exp.c 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644
  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) 2018 K. Lange
  5. *
  6. * Experimental rline replacement with syntax highlighting, based
  7. * on bim's highlighting and line editing.
  8. *
  9. */
  10. #define _XOPEN_SOURCE
  11. #define _DEFAULT_SOURCE
  12. #include <stdio.h>
  13. #include <stdlib.h>
  14. #include <stdint.h>
  15. #include <ctype.h>
  16. #include <termios.h>
  17. #include <string.h>
  18. #include <wchar.h>
  19. #include <unistd.h>
  20. #include <locale.h>
  21. #include <poll.h>
  22. #include <signal.h>
  23. #include <sys/ioctl.h>
  24. #include <toaru/decodeutf8.h>
  25. #include <toaru/rline.h>
  26. #define ENTER_KEY '\n'
  27. #define BACKSPACE_KEY 0x08
  28. #define DELETE_KEY 0x7F
  29. #define MINIMUM_SIZE 10
  30. /**
  31. * Same structures as in bim.
  32. * A single character has:
  33. * - A codepoint (Unicode) of up to 21 bits.
  34. * - Flags for syntax highlighting.
  35. * - A display width for rendering.
  36. */
  37. typedef struct {
  38. uint32_t display_width:4;
  39. uint32_t flags:7;
  40. uint32_t codepoint:21;
  41. } __attribute__((packed)) char_t;
  42. /**
  43. * We generally only have the one line,
  44. * but this matches bim for compatibility reasons.
  45. */
  46. typedef struct {
  47. int available;
  48. int actual;
  49. int istate;
  50. char_t text[0];
  51. } line_t;
  52. /**
  53. * We operate on a single line of text.
  54. * Maybe we can expand this in the future
  55. * for continuations of edits such as when
  56. * a quote is unclosed?
  57. */
  58. static line_t * the_line = NULL;
  59. /**
  60. * Line editor state
  61. */
  62. static int loading = 0;
  63. static int column = 0;
  64. static int offset = 0;
  65. static int width = 0;
  66. static int full_width = 0;
  67. static int show_right_side = 0;
  68. static int show_left_side = 0;
  69. static int prompt_width_calc = 0;
  70. static int buf_size_max = 0;
  71. /**
  72. * Prompt strings.
  73. * Defaults to just a "> " prompt with no right side.
  74. * Support for right side prompts is important
  75. * for the ToaruOS shell.
  76. */
  77. static int prompt_width = 2;
  78. static char * prompt = "> ";
  79. static int prompt_right_width = 0;
  80. static char * prompt_right = "";
  81. int rline_exp_set_prompts(char * left, char * right, int left_width, int right_width) {
  82. prompt = left;
  83. prompt_right = right;
  84. prompt_width = left_width;
  85. prompt_right_width = right_width;
  86. return 0;
  87. }
  88. /**
  89. * Extra shell commands to highlight as keywords.
  90. * These are basically just copied from the
  91. * shell's tab completion database on startup.
  92. */
  93. static char ** shell_commands = {0};
  94. static int shell_commands_len = 0;
  95. int rline_exp_set_shell_commands(char ** cmds, int len) {
  96. shell_commands = cmds;
  97. shell_commands_len = len;
  98. return 0;
  99. }
  100. /**
  101. * Tab completion callback.
  102. * Compatible with the original rline version.
  103. */
  104. static rline_callback_t tab_complete_func = NULL;
  105. int rline_exp_set_tab_complete_func(rline_callback_t func) {
  106. tab_complete_func = func;
  107. return 0;
  108. }
  109. static int _unget = -1;
  110. static void _ungetc(int c) {
  111. _unget = c;
  112. }
  113. static int getch(int immediate) {
  114. if (_unget != -1) {
  115. int out = _unget;
  116. _unget = -1;
  117. return out;
  118. }
  119. if (immediate) {
  120. return getc(stdin);
  121. }
  122. struct pollfd fds[1];
  123. fds[0].fd = STDIN_FILENO;
  124. fds[0].events = POLLIN;
  125. int ret = poll(fds,1,10);
  126. if (ret > 0 && fds[0].revents & POLLIN) {
  127. unsigned char buf[1];
  128. read(STDIN_FILENO, buf, 1);
  129. return buf[0];
  130. } else {
  131. return -1;
  132. }
  133. }
  134. /**
  135. * Convert from Unicode string to utf-8.
  136. */
  137. static int to_eight(uint32_t codepoint, char * out) {
  138. memset(out, 0x00, 7);
  139. if (codepoint < 0x0080) {
  140. out[0] = (char)codepoint;
  141. } else if (codepoint < 0x0800) {
  142. out[0] = 0xC0 | (codepoint >> 6);
  143. out[1] = 0x80 | (codepoint & 0x3F);
  144. } else if (codepoint < 0x10000) {
  145. out[0] = 0xE0 | (codepoint >> 12);
  146. out[1] = 0x80 | ((codepoint >> 6) & 0x3F);
  147. out[2] = 0x80 | (codepoint & 0x3F);
  148. } else if (codepoint < 0x200000) {
  149. out[0] = 0xF0 | (codepoint >> 18);
  150. out[1] = 0x80 | ((codepoint >> 12) & 0x3F);
  151. out[2] = 0x80 | ((codepoint >> 6) & 0x3F);
  152. out[3] = 0x80 | ((codepoint) & 0x3F);
  153. } else if (codepoint < 0x4000000) {
  154. out[0] = 0xF8 | (codepoint >> 24);
  155. out[1] = 0x80 | (codepoint >> 18);
  156. out[2] = 0x80 | ((codepoint >> 12) & 0x3F);
  157. out[3] = 0x80 | ((codepoint >> 6) & 0x3F);
  158. out[4] = 0x80 | ((codepoint) & 0x3F);
  159. } else {
  160. out[0] = 0xF8 | (codepoint >> 30);
  161. out[1] = 0x80 | ((codepoint >> 24) & 0x3F);
  162. out[2] = 0x80 | ((codepoint >> 18) & 0x3F);
  163. out[3] = 0x80 | ((codepoint >> 12) & 0x3F);
  164. out[4] = 0x80 | ((codepoint >> 6) & 0x3F);
  165. out[5] = 0x80 | ((codepoint) & 0x3F);
  166. }
  167. return strlen(out);
  168. }
  169. /**
  170. * Obtain codepoint display width.
  171. *
  172. * This is copied from bim. Supports a few useful
  173. * things like rendering escapes as codepoints.
  174. */
  175. static int codepoint_width(wchar_t codepoint) {
  176. if (codepoint == '\t') {
  177. return 1; /* Recalculate later */
  178. }
  179. if (codepoint < 32) {
  180. /* We render these as ^@ */
  181. return 2;
  182. }
  183. if (codepoint == 0x7F) {
  184. /* Renders as ^? */
  185. return 2;
  186. }
  187. if (codepoint > 0x7f && codepoint < 0xa0) {
  188. /* Upper control bytes <xx> */
  189. return 4;
  190. }
  191. if (codepoint == 0xa0) {
  192. /* Non-breaking space _ */
  193. return 1;
  194. }
  195. /* Skip wcwidth for anything under 256 */
  196. if (codepoint > 256) {
  197. /* Higher codepoints may be wider (eg. Japanese) */
  198. int out = wcwidth(codepoint);
  199. if (out >= 1) return out;
  200. /* Invalid character, render as [U+ABCD] or [U+ABCDEF] */
  201. return (codepoint < 0x10000) ? 8 : 10;
  202. }
  203. return 1;
  204. }
  205. void recalculate_tabs(line_t * line) {
  206. int j = 0;
  207. for (int i = 0; i < line->actual; ++i) {
  208. if (line->text[i].codepoint == '\t') {
  209. line->text[i].display_width = 4 - (j % 4);
  210. }
  211. j += line->text[i].display_width;
  212. }
  213. }
  214. /**
  215. * Color themes have also been copied from bim.
  216. *
  217. * Slimmed down to only the ones we use for syntax
  218. * highlighting; the UI colors have been removed.
  219. */
  220. static const char * COLOR_FG = "@9";
  221. static const char * COLOR_BG = "@9";
  222. static const char * COLOR_ALT_FG = "@5";
  223. static const char * COLOR_ALT_BG = "@9";
  224. static const char * COLOR_KEYWORD = "@4";
  225. static const char * COLOR_STRING = "@2";
  226. static const char * COLOR_COMMENT = "@5";
  227. static const char * COLOR_TYPE = "@3";
  228. static const char * COLOR_PRAGMA = "@1";
  229. static const char * COLOR_NUMERAL = "@1";
  230. static const char * COLOR_RED = "@1";
  231. static const char * COLOR_GREEN = "@2";
  232. /**
  233. * Themes are selected from the $RLINE_THEME
  234. * environment variable.
  235. */
  236. static void rline_exp_load_colorscheme_default(void) {
  237. COLOR_FG = "@9";
  238. COLOR_BG = "@9";
  239. COLOR_ALT_FG = "@10";
  240. COLOR_ALT_BG = "@9";
  241. COLOR_KEYWORD = "@14";
  242. COLOR_STRING = "@2";
  243. COLOR_COMMENT = "@10";
  244. COLOR_TYPE = "@3";
  245. COLOR_PRAGMA = "@1";
  246. COLOR_NUMERAL = "@1";
  247. COLOR_RED = "@1";
  248. COLOR_GREEN = "@2";
  249. }
  250. static void rline_exp_load_colorscheme_sunsmoke(void) {
  251. COLOR_FG = "2;230;230;230";
  252. COLOR_BG = "@9";
  253. COLOR_ALT_FG = "2;122;122;122";
  254. COLOR_ALT_BG = "2;46;43;46";
  255. COLOR_KEYWORD = "2;51;162;230";
  256. COLOR_STRING = "2;72;176;72";
  257. COLOR_COMMENT = "2;158;153;129;3";
  258. COLOR_TYPE = "2;230;206;110";
  259. COLOR_PRAGMA = "2;194;70;54";
  260. COLOR_NUMERAL = "2;230;43;127";
  261. COLOR_RED = "2;222;53;53";
  262. COLOR_GREEN = "2;55;167;0";
  263. }
  264. /**
  265. * Syntax highlighting flags.
  266. */
  267. #define FLAG_NONE 0
  268. #define FLAG_KEYWORD 1
  269. #define FLAG_STRING 2
  270. #define FLAG_COMMENT 3
  271. #define FLAG_TYPE 4
  272. #define FLAG_PRAGMA 5
  273. #define FLAG_NUMERAL 6
  274. #define FLAG_SELECT 7
  275. #define FLAG_STRING2 8
  276. #define FLAG_DIFFPLUS 9
  277. #define FLAG_DIFFMINUS 10
  278. #define FLAG_CONTINUES (1 << 6)
  279. /**
  280. * Syntax definition for ToaruOS shell
  281. */
  282. static char * syn_sh_keywords[] = {
  283. "cd","exit","export","help","history","if",
  284. "empty?","equals?","return","export-cmd",
  285. "source","exec","not","while","then","else",
  286. NULL,
  287. };
  288. static int variable_char(uint8_t c) {
  289. if (c >= 'A' && c <= 'Z') return 1;
  290. if (c >= 'a' && c <= 'z') return 1;
  291. if (c >= '0' && c <= '9') return 1;
  292. if (c == '_') return 1;
  293. if (c == '?') return 1;
  294. return 0;
  295. }
  296. static int syn_sh_extended(line_t * line, int i, int c, int last, int * out_left) {
  297. (void)last;
  298. if (c == '#' && last != '\\') {
  299. *out_left = (line->actual + 1) - i;
  300. return FLAG_COMMENT;
  301. }
  302. if (line->text[i].codepoint == '\'' && last != '\\') {
  303. int last = 0;
  304. for (int j = i+1; j < line->actual + 1; ++j) {
  305. int c = line->text[j].codepoint;
  306. if (last != '\\' && c == '\'') {
  307. *out_left = j - i;
  308. return FLAG_STRING;
  309. }
  310. if (last == '\\' && c == '\\') {
  311. last = 0;
  312. }
  313. last = c;
  314. }
  315. *out_left = (line->actual + 1) - i; /* unterminated string */
  316. return FLAG_STRING;
  317. }
  318. if (line->text[i].codepoint == '$' && last != '\\') {
  319. if (i < line->actual - 1 && line->text[i+1].codepoint == '{') {
  320. int j = i + 2;
  321. for (; j < line->actual+1; ++j) {
  322. if (line->text[j].codepoint == '}') break;
  323. }
  324. *out_left = (j - i);
  325. return FLAG_NUMERAL;
  326. }
  327. int j = i + 1;
  328. for (; j < line->actual + 1; ++j) {
  329. if (!variable_char(line->text[j].codepoint)) break;
  330. }
  331. *out_left = (j - i) - 1;
  332. return FLAG_NUMERAL;
  333. }
  334. if (line->text[i].codepoint == '"' && last != '\\') {
  335. int last = 0;
  336. for (int j = i+1; j < line->actual + 1; ++j) {
  337. int c = line->text[j].codepoint;
  338. if (last != '\\' && c == '"') {
  339. *out_left = j - i;
  340. return FLAG_STRING;
  341. }
  342. if (last == '\\' && c == '\\') {
  343. last = 0;
  344. }
  345. last = c;
  346. }
  347. *out_left = (line->actual + 1) - i; /* unterminated string */
  348. return FLAG_STRING;
  349. }
  350. return 0;
  351. }
  352. static int syn_sh_iskeywordchar(int c) {
  353. if (isalnum(c)) return 1;
  354. if (c == '-') return 1;
  355. if (c == '_') return 1;
  356. if (c == '?') return 1;
  357. if (c == '/') return 1;
  358. if (c == '.') return 1;
  359. return 0;
  360. }
  361. static char * syn_py_keywords[] = {
  362. "class","def","return","del","if","else","elif",
  363. "for","while","continue","break","assert",
  364. "as","and","or","except","finally","from",
  365. "global","import","in","is","lambda","with",
  366. "nonlocal","not","pass","raise","try","yield",
  367. NULL
  368. };
  369. static char * syn_py_types[] = {
  370. "True","False","None",
  371. "object","set","dict","int","str","bytes",
  372. NULL
  373. };
  374. int syn_c_iskeywordchar(int c) {
  375. if (isalnum(c)) return 1;
  376. if (c == '_') return 1;
  377. return 0;
  378. }
  379. static int syn_py_extended(line_t * line, int i, int c, int last, int * out_left) {
  380. if (i == 0 && c == 'i') {
  381. /* Check for import */
  382. char * import = "import ";
  383. for (int j = 0; j < line->actual + 1; ++j) {
  384. if (import[j] == '\0') {
  385. *out_left = j - 2;
  386. return FLAG_PRAGMA;
  387. }
  388. if (line->text[j].codepoint != import[j]) break;
  389. }
  390. }
  391. if (c == '#') {
  392. *out_left = (line->actual + 1) - i;
  393. return FLAG_COMMENT;
  394. }
  395. if (c == '@') {
  396. for (int j = i+1; j < line->actual + 1; ++j) {
  397. if (!syn_c_iskeywordchar(line->text[j].codepoint)) {
  398. *out_left = j - i - 1;
  399. return FLAG_PRAGMA;
  400. }
  401. *out_left = (line->actual + 1) - i;
  402. return FLAG_PRAGMA;
  403. }
  404. }
  405. if ((!last || !syn_c_iskeywordchar(last)) && isdigit(c)) {
  406. if (c == '0' && i < line->actual - 1 && line->text[i+1].codepoint == 'x') {
  407. int j = 2;
  408. for (; i + j < line->actual && isxdigit(line->text[i+j].codepoint); ++j);
  409. if (i + j < line->actual && syn_c_iskeywordchar(line->text[i+j].codepoint)) {
  410. return FLAG_NONE;
  411. }
  412. *out_left = j - 1;
  413. return FLAG_NUMERAL;
  414. } else {
  415. int j = 1;
  416. while (i + j < line->actual && isdigit(line->text[i+j].codepoint)) {
  417. j++;
  418. }
  419. if (i + j < line->actual && syn_c_iskeywordchar(line->text[i+j].codepoint)) {
  420. return FLAG_NONE;
  421. }
  422. *out_left = j - 1;
  423. return FLAG_NUMERAL;
  424. }
  425. }
  426. if (line->text[i].codepoint == '\'') {
  427. if (i + 2 < line->actual && line->text[i+1].codepoint == '\'' && line->text[i+2].codepoint == '\'') {
  428. /* Begin multiline */
  429. for (int j = i + 3; j < line->actual - 2; ++j) {
  430. if (line->text[j].codepoint == '\'' &&
  431. line->text[j+1].codepoint == '\'' &&
  432. line->text[j+2].codepoint == '\'') {
  433. *out_left = (j+2) - i;
  434. return FLAG_STRING;
  435. }
  436. }
  437. return FLAG_STRING | FLAG_CONTINUES;
  438. }
  439. int last = 0;
  440. for (int j = i+1; j < line->actual; ++j) {
  441. int c = line->text[j].codepoint;
  442. if (last != '\\' && c == '\'') {
  443. *out_left = j - i;
  444. return FLAG_STRING;
  445. }
  446. if (last == '\\' && c == '\\') {
  447. last = 0;
  448. }
  449. last = c;
  450. }
  451. *out_left = (line->actual + 1) - i; /* unterminated string */
  452. return FLAG_STRING;
  453. }
  454. if (line->text[i].codepoint == '"') {
  455. if (i + 2 < line->actual && line->text[i+1].codepoint == '"' && line->text[i+2].codepoint == '"') {
  456. /* Begin multiline */
  457. for (int j = i + 3; j < line->actual - 2; ++j) {
  458. if (line->text[j].codepoint == '"' &&
  459. line->text[j+1].codepoint == '"' &&
  460. line->text[j+2].codepoint == '"') {
  461. *out_left = (j+2) - i;
  462. return FLAG_STRING;
  463. }
  464. }
  465. return FLAG_STRING2 | FLAG_CONTINUES;
  466. }
  467. int last = 0;
  468. for (int j = i+1; j < line->actual; ++j) {
  469. int c = line->text[j].codepoint;
  470. if (last != '\\' && c == '"') {
  471. *out_left = j - i;
  472. return FLAG_STRING;
  473. }
  474. if (last == '\\' && c == '\\') {
  475. last = 0;
  476. }
  477. last = c;
  478. }
  479. *out_left = (line->actual + 1) - i; /* unterminated string */
  480. return FLAG_STRING;
  481. }
  482. return 0;
  483. }
  484. static int syn_py_finish(line_t * line, int * left, int state) {
  485. /* TODO support multiline quotes */
  486. if (state == (FLAG_STRING | FLAG_CONTINUES)) {
  487. for (int j = 0; j < line->actual - 2; ++j) {
  488. if (line->text[j].codepoint == '\'' &&
  489. line->text[j+1].codepoint == '\'' &&
  490. line->text[j+2].codepoint == '\'') {
  491. *left = (j+3);
  492. return FLAG_STRING;
  493. }
  494. }
  495. return FLAG_STRING | FLAG_CONTINUES;
  496. }
  497. if (state == (FLAG_STRING2 | FLAG_CONTINUES)) {
  498. for (int j = 0; j < line->actual - 2; ++j) {
  499. if (line->text[j].codepoint == '"' &&
  500. line->text[j+1].codepoint == '"' &&
  501. line->text[j+2].codepoint == '"') {
  502. *left = (j+3);
  503. return FLAG_STRING2;
  504. }
  505. }
  506. return FLAG_STRING2 | FLAG_CONTINUES;
  507. }
  508. return 0;
  509. }
  510. /**
  511. * Convert syntax hilighting flag to color code
  512. */
  513. static const char * flag_to_color(int _flag) {
  514. int flag = _flag & 0x3F;
  515. switch (flag) {
  516. case FLAG_KEYWORD:
  517. return COLOR_KEYWORD;
  518. case FLAG_STRING:
  519. case FLAG_STRING2: /* allows python to differentiate " and ' */
  520. return COLOR_STRING;
  521. case FLAG_COMMENT:
  522. return COLOR_COMMENT;
  523. case FLAG_TYPE:
  524. return COLOR_TYPE;
  525. case FLAG_NUMERAL:
  526. return COLOR_NUMERAL;
  527. case FLAG_PRAGMA:
  528. return COLOR_PRAGMA;
  529. case FLAG_DIFFPLUS:
  530. return COLOR_GREEN;
  531. case FLAG_DIFFMINUS:
  532. return COLOR_RED;
  533. case FLAG_SELECT:
  534. return COLOR_FG;
  535. default:
  536. return COLOR_FG;
  537. }
  538. }
  539. static struct syntax_definition {
  540. char * name;
  541. char ** keywords;
  542. char ** types;
  543. int (*extended)(line_t *, int, int, int, int *);
  544. int (*iskwchar)(int);
  545. int (*finishml)(line_t *, int *, int); /* TODO: How do we use this here? */
  546. } syntaxes[] = {
  547. {"python",syn_py_keywords,syn_py_types,syn_py_extended,syn_c_iskeywordchar,syn_py_finish},
  548. {"esh",syn_sh_keywords,NULL,syn_sh_extended,syn_sh_iskeywordchar,NULL},
  549. {NULL}
  550. };
  551. static struct syntax_definition * syntax;
  552. int rline_exp_set_syntax(char * name) {
  553. for (struct syntax_definition * s = syntaxes; s->name; ++s) {
  554. if (!strcmp(name,s->name)) {
  555. syntax = s;
  556. return 0;
  557. }
  558. }
  559. return 1;
  560. }
  561. /**
  562. * Compare a line against a list of keywords
  563. */
  564. static int check_line(line_t * line, int c, char * str, int last) {
  565. if (syntax->iskwchar(last)) return 0;
  566. for (int i = c; i < line->actual; ++i, ++str) {
  567. if (*str == '\0' && !syntax->iskwchar(line->text[i].codepoint)) return 1;
  568. if (line->text[i].codepoint == *str) continue;
  569. return 0;
  570. }
  571. if (*str == '\0') return 1;
  572. return 0;
  573. }
  574. /**
  575. * Syntax highlighting
  576. * Slimmed down from the bim implementation a bit,
  577. * but generally compatible with the same definitions.
  578. *
  579. * Type highlighting has been removed as the sh highlighter
  580. * didn't use it. This should be made pluggable again, and
  581. * the bim syntax highlighters should probably be broken
  582. * out into dynamically-loaded libraries?
  583. */
  584. static void recalculate_syntax(line_t * line) {
  585. if (!syntax) return;
  586. /* Start from the line's stored in initial state */
  587. int state = line->istate;
  588. int left = 0;
  589. int last = 0;
  590. for (int i = 0; i < line->actual; last = line->text[i++].codepoint) {
  591. if (!left) state = 0;
  592. if (state) {
  593. /* Currently hilighting, have `left` characters remaining with this state */
  594. left--;
  595. line->text[i].flags = state;
  596. if (!left) {
  597. /* Done hilighting this state, go back to parsing on next character */
  598. state = 0;
  599. }
  600. /* If we are hilighting something, don't parse */
  601. continue;
  602. }
  603. int c = line->text[i].codepoint;
  604. line->text[i].flags = FLAG_NONE;
  605. /* Language-specific syntax hilighting */
  606. int s = syntax->extended(line,i,c,last,&left);
  607. if (s) {
  608. state = s;
  609. goto _continue;
  610. }
  611. /* Keywords */
  612. if (syntax->keywords) {
  613. for (char ** kw = syntax->keywords; *kw; kw++) {
  614. int c = check_line(line, i, *kw, last);
  615. if (c == 1) {
  616. left = strlen(*kw)-1;
  617. state = FLAG_KEYWORD;
  618. goto _continue;
  619. }
  620. }
  621. }
  622. for (int s = 0; s < shell_commands_len; ++s) {
  623. int c = check_line(line, i, shell_commands[s], last);
  624. if (c == 1) {
  625. left = strlen(shell_commands[s])-1;
  626. state = FLAG_KEYWORD;
  627. goto _continue;
  628. }
  629. }
  630. if (syntax->types) {
  631. for (char ** kw = syntax->types; *kw; kw++) {
  632. int c = check_line(line, i, *kw, last);
  633. if (c == 1) {
  634. left = strlen(*kw)-1;
  635. state = FLAG_TYPE;
  636. goto _continue;
  637. }
  638. }
  639. }
  640. _continue:
  641. line->text[i].flags = state;
  642. }
  643. state = 0;
  644. }
  645. /**
  646. * Set colors
  647. */
  648. static void set_colors(const char * fg, const char * bg) {
  649. printf("\033[22;23;");
  650. if (*bg == '@') {
  651. int _bg = atoi(bg+1);
  652. if (_bg < 10) {
  653. printf("4%d;", _bg);
  654. } else {
  655. printf("10%d;", _bg-10);
  656. }
  657. } else {
  658. printf("48;%s;", bg);
  659. }
  660. if (*fg == '@') {
  661. int _fg = atoi(fg+1);
  662. if (_fg < 10) {
  663. printf("3%dm", _fg);
  664. } else {
  665. printf("9%dm", _fg-10);
  666. }
  667. } else {
  668. printf("38;%sm", fg);
  669. }
  670. fflush(stdout);
  671. }
  672. /**
  673. * Set just the foreground color
  674. *
  675. * (See set_colors above)
  676. */
  677. static void set_fg_color(const char * fg) {
  678. printf("\033[22;23;");
  679. if (*fg == '@') {
  680. int _fg = atoi(fg+1);
  681. if (_fg < 10) {
  682. printf("3%dm", _fg);
  683. } else {
  684. printf("9%dm", _fg-10);
  685. }
  686. } else {
  687. printf("38;%sm", fg);
  688. }
  689. fflush(stdout);
  690. }
  691. /**
  692. * Mostly copied from bim, but with some minor
  693. * alterations and removal of selection support.
  694. */
  695. static void render_line(void) {
  696. printf("\033[?25l");
  697. if (show_left_side) {
  698. printf("\033[0m\r%s", prompt);
  699. } else {
  700. printf("\033[0m\r$");
  701. }
  702. if (offset && prompt_width_calc) {
  703. set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
  704. printf("\b<");
  705. }
  706. int i = 0; /* Offset in char_t line data entries */
  707. int j = 0; /* Offset in terminal cells */
  708. const char * last_color = NULL;
  709. /* Set default text colors */
  710. set_colors(COLOR_FG, COLOR_BG);
  711. /*
  712. * When we are rendering in the middle of a wide character,
  713. * we render -'s to fill the remaining amount of the
  714. * charater's width
  715. */
  716. int remainder = 0;
  717. line_t * line = the_line;
  718. /* For each character in the line ... */
  719. while (i < line->actual) {
  720. /* If there is remaining text... */
  721. if (remainder) {
  722. /* If we should be drawing by now... */
  723. if (j >= offset) {
  724. /* Fill remainder with -'s */
  725. set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
  726. printf("-");
  727. set_colors(COLOR_FG, COLOR_BG);
  728. }
  729. /* One less remaining width cell to fill */
  730. remainder--;
  731. /* Terminal offset moves forward */
  732. j++;
  733. /*
  734. * If this was the last remaining character, move to
  735. * the next codepoint in the line
  736. */
  737. if (remainder == 0) {
  738. i++;
  739. }
  740. continue;
  741. }
  742. /* Get the next character to draw */
  743. char_t c = line->text[i];
  744. /* If we should be drawing by now... */
  745. if (j >= offset) {
  746. /* If this character is going to fall off the edge of the screen... */
  747. if (j - offset + c.display_width >= width - prompt_width_calc) {
  748. /* We draw this with special colors so it isn't ambiguous */
  749. set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
  750. /* If it's wide, draw ---> as needed */
  751. while (j - offset < width - prompt_width_calc - 1) {
  752. printf("-");
  753. j++;
  754. }
  755. /* End the line with a > to show it overflows */
  756. printf(">");
  757. set_colors(COLOR_FG, COLOR_BG);
  758. j++;
  759. break;
  760. }
  761. /* Syntax hilighting */
  762. const char * color = flag_to_color(c.flags);
  763. if (!last_color || strcmp(color, last_color)) {
  764. set_fg_color(color);
  765. last_color = color;
  766. }
  767. /* Render special characters */
  768. if (c.codepoint == '\t') {
  769. set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
  770. printf("»");
  771. for (int i = 1; i < c.display_width; ++i) {
  772. printf("·");
  773. }
  774. set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);
  775. } else if (c.codepoint < 32) {
  776. /* Codepoints under 32 to get converted to ^@ escapes */
  777. set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
  778. printf("^%c", '@' + c.codepoint);
  779. set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);
  780. } else if (c.codepoint == 0x7f) {
  781. set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
  782. printf("^?");
  783. set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);
  784. } else if (c.codepoint > 0x7f && c.codepoint < 0xa0) {
  785. set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
  786. printf("<%2x>", c.codepoint);
  787. set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);
  788. } else if (c.codepoint == 0xa0) {
  789. set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
  790. printf("_");
  791. set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);
  792. } else if (c.display_width == 8) {
  793. set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
  794. printf("[U+%04x]", c.codepoint);
  795. set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);
  796. } else if (c.display_width == 10) {
  797. set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
  798. printf("[U+%06x]", c.codepoint);
  799. set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);
  800. #if 0
  801. } else if (c.codepoint == ' ' && i == line->actual - 1) {
  802. /* Special case: space at end of line */
  803. set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
  804. printf("·");
  805. set_colors(COLOR_FG, COLOR_BG);
  806. #endif
  807. } else {
  808. /* Normal characters get output */
  809. char tmp[7]; /* Max six bytes, use 7 to ensure last is always nil */
  810. to_eight(c.codepoint, tmp);
  811. printf("%s", tmp);
  812. }
  813. /* Advance the terminal cell offset by the render width of this character */
  814. j += c.display_width;
  815. /* Advance to the next character */
  816. i++;
  817. } else if (c.display_width > 1) {
  818. /*
  819. * If this is a wide character but we aren't ready to render yet,
  820. * we may need to draw some filler text for the remainder of its
  821. * width to ensure we don't jump around when horizontally scrolling
  822. * past wide characters.
  823. */
  824. remainder = c.display_width - 1;
  825. j++;
  826. } else {
  827. /* Regular character, not ready to draw, advance without doing anything */
  828. j++;
  829. i++;
  830. }
  831. }
  832. /* Fill to end right hand side */
  833. for (; j < width + offset - prompt_width_calc; ++j) {
  834. printf(" ");
  835. }
  836. /* Print right hand side */
  837. if (show_right_side) {
  838. printf("\033[0m%s", prompt_right);
  839. }
  840. }
  841. /**
  842. * Create a line_t
  843. */
  844. static line_t * line_create(void) {
  845. line_t * line = malloc(sizeof(line_t) + sizeof(char_t) * 32);
  846. line->available = 32;
  847. line->actual = 0;
  848. line->istate = 0;
  849. return line;
  850. }
  851. /**
  852. * Insert a character into a line
  853. */
  854. static line_t * line_insert(line_t * line, char_t c, int offset) {
  855. /* If there is not enough space... */
  856. if (line->actual == line->available) {
  857. /* Expand the line buffer */
  858. line->available *= 2;
  859. line = realloc(line, sizeof(line_t) + sizeof(char_t) * line->available);
  860. }
  861. /* If this was not the last character, then shift remaining characters forward. */
  862. if (offset < line->actual) {
  863. memmove(&line->text[offset+1], &line->text[offset], sizeof(char_t) * (line->actual - offset));
  864. }
  865. /* Insert the new character */
  866. line->text[offset] = c;
  867. /* There is one new character in the line */
  868. line->actual += 1;
  869. if (!loading) {
  870. recalculate_tabs(line);
  871. recalculate_syntax(line);
  872. }
  873. return line;
  874. }
  875. /**
  876. * Update terminal size
  877. *
  878. * We don't listen for sigwinch for various reasons...
  879. */
  880. static void get_size(void) {
  881. struct winsize w;
  882. ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
  883. full_width = w.ws_col;
  884. if (full_width - prompt_right_width - prompt_width > MINIMUM_SIZE) {
  885. show_right_side = 1;
  886. show_left_side = 1;
  887. prompt_width_calc = prompt_width;
  888. width = full_width - prompt_right_width;
  889. } else {
  890. show_right_side = 0;
  891. if (full_width - prompt_width > MINIMUM_SIZE) {
  892. show_left_side = 1;
  893. prompt_width_calc = prompt_width;
  894. } else {
  895. show_left_side = 0;
  896. prompt_width_calc = 1;
  897. }
  898. width = full_width;
  899. }
  900. }
  901. /**
  902. * Place the cursor within the line
  903. */
  904. static void place_cursor_actual(void) {
  905. int x = prompt_width_calc + 1 - offset;
  906. for (int i = 0; i < column; ++i) {
  907. char_t * c = &the_line->text[i];
  908. x += c->display_width;
  909. }
  910. if (x > width - 1) {
  911. /* Adjust the offset appropriately to scroll horizontally */
  912. int diff = x - (width - 1);
  913. offset += diff;
  914. x -= diff;
  915. render_line();
  916. }
  917. /* Same for scrolling horizontally to the left */
  918. if (x < prompt_width_calc + 1) {
  919. int diff = (prompt_width_calc + 1) - x;
  920. offset -= diff;
  921. x += diff;
  922. render_line();
  923. }
  924. printf("\033[?25h\033[%dG", x);
  925. fflush(stdout);
  926. }
  927. /**
  928. * Delete a character
  929. */
  930. static void line_delete(line_t * line, int offset) {
  931. /* Can't delete character before start of line. */
  932. if (offset == 0) return;
  933. /* If this isn't the last character, we need to move all subsequent characters backwards */
  934. if (offset < line->actual) {
  935. memmove(&line->text[offset-1], &line->text[offset], sizeof(char_t) * (line->actual - offset));
  936. }
  937. /* The line is one character shorter */
  938. line->actual -= 1;
  939. if (!loading) {
  940. recalculate_tabs(line);
  941. recalculate_syntax(line);
  942. }
  943. }
  944. /**
  945. * Backspace from the cursor position
  946. */
  947. static void delete_at_cursor(void) {
  948. if (column > 0) {
  949. line_delete(the_line, column);
  950. column--;
  951. if (offset > 0) offset--;
  952. }
  953. }
  954. /**
  955. * Delete whole word
  956. */
  957. static void delete_word(void) {
  958. if (!the_line->actual) return;
  959. if (!column) return;
  960. do {
  961. if (column > 0) {
  962. line_delete(the_line, column);
  963. column--;
  964. if (offset > 0) offset--;
  965. }
  966. } while (column && the_line->text[column-1].codepoint != ' ');
  967. }
  968. /**
  969. * Insert at cursor position
  970. */
  971. static void insert_char(uint32_t c) {
  972. char_t _c;
  973. _c.codepoint = c;
  974. _c.flags = 0;
  975. _c.display_width = codepoint_width(c);
  976. the_line = line_insert(the_line, _c, column);
  977. column++;
  978. }
  979. /**
  980. * Move cursor left
  981. */
  982. static void cursor_left(void) {
  983. if (column > 0) column--;
  984. place_cursor_actual();
  985. }
  986. /**
  987. * Move cursor right
  988. */
  989. static void cursor_right(void) {
  990. if (column < the_line->actual) column++;
  991. place_cursor_actual();
  992. }
  993. /**
  994. * Move cursor one whole word left
  995. */
  996. static void word_left(void) {
  997. if (column == 0) return;
  998. column--;
  999. while (column && the_line->text[column].codepoint == ' ') {
  1000. column--;
  1001. }
  1002. while (column > 0) {
  1003. if (the_line->text[column-1].codepoint == ' ') break;
  1004. column--;
  1005. }
  1006. place_cursor_actual();
  1007. }
  1008. /**
  1009. * Move cursor one whole word right
  1010. */
  1011. static void word_right(void) {
  1012. while (column < the_line->actual && the_line->text[column].codepoint == ' ') {
  1013. column++;
  1014. }
  1015. while (column < the_line->actual) {
  1016. column++;
  1017. if (the_line->text[column].codepoint == ' ') break;
  1018. }
  1019. place_cursor_actual();
  1020. }
  1021. /**
  1022. * Move cursor to start of line
  1023. */
  1024. static void cursor_home(void) {
  1025. column = 0;
  1026. place_cursor_actual();
  1027. }
  1028. /*
  1029. * Move cursor to end of line
  1030. */
  1031. static void cursor_end(void) {
  1032. column = the_line->actual;
  1033. place_cursor_actual();
  1034. }
  1035. /**
  1036. * Temporary buffer for holding utf-8 data
  1037. */
  1038. static char temp_buffer[1024];
  1039. /**
  1040. * Cycle to previous history entry
  1041. */
  1042. static void history_previous(void) {
  1043. if (rline_scroll == 0) {
  1044. /* Convert to temporaary buffer */
  1045. unsigned int off = 0;
  1046. memset(temp_buffer, 0, sizeof(temp_buffer));
  1047. for (int j = 0; j < the_line->actual; j++) {
  1048. char_t c = the_line->text[j];
  1049. off += to_eight(c.codepoint, &temp_buffer[off]);
  1050. }
  1051. }
  1052. if (rline_scroll < rline_history_count) {
  1053. rline_scroll++;
  1054. /* Copy in from history */
  1055. the_line->actual = 0;
  1056. column = 0;
  1057. loading = 1;
  1058. unsigned char * buf = (unsigned char *)rline_history_prev(rline_scroll);
  1059. uint32_t istate = 0, c = 0;
  1060. for (unsigned int i = 0; i < strlen((char *)buf); ++i) {
  1061. if (!decode(&istate, &c, buf[i])) {
  1062. insert_char(c);
  1063. }
  1064. }
  1065. loading = 0;
  1066. }
  1067. /* Set cursor at end */
  1068. column = the_line->actual;
  1069. offset = 0;
  1070. recalculate_tabs(the_line);
  1071. recalculate_syntax(the_line);
  1072. render_line();
  1073. place_cursor_actual();
  1074. }
  1075. /**
  1076. * Cycle to next history entry
  1077. */
  1078. static void history_next(void) {
  1079. if (rline_scroll > 1) {
  1080. rline_scroll--;
  1081. /* Copy in from history */
  1082. the_line->actual = 0;
  1083. column = 0;
  1084. loading = 1;
  1085. unsigned char * buf = (unsigned char *)rline_history_prev(rline_scroll);
  1086. uint32_t istate = 0, c = 0;
  1087. for (unsigned int i = 0; i < strlen((char *)buf); ++i) {
  1088. if (!decode(&istate, &c, buf[i])) {
  1089. insert_char(c);
  1090. }
  1091. }
  1092. loading = 0;
  1093. } else if (rline_scroll == 1) {
  1094. /* Copy in from temp */
  1095. rline_scroll = 0;
  1096. the_line->actual = 0;
  1097. column = 0;
  1098. loading = 1;
  1099. char * buf = temp_buffer;
  1100. uint32_t istate = 0, c = 0;
  1101. for (unsigned int i = 0; i < strlen(buf); ++i) {
  1102. if (!decode(&istate, &c, buf[i])) {
  1103. insert_char(c);
  1104. }
  1105. }
  1106. loading = 0;
  1107. }
  1108. /* Set cursor at end */
  1109. column = the_line->actual;
  1110. offset = 0;
  1111. recalculate_tabs(the_line);
  1112. recalculate_syntax(the_line);
  1113. render_line();
  1114. place_cursor_actual();
  1115. }
  1116. /**
  1117. * Handle escape sequences (arrow keys, etc.)
  1118. */
  1119. static int handle_escape(int * this_buf, int * timeout, int c) {
  1120. if (*timeout >= 1 && this_buf[*timeout-1] == '\033' && c == '\033') {
  1121. this_buf[*timeout] = c;
  1122. (*timeout)++;
  1123. return 1;
  1124. }
  1125. if (*timeout >= 1 && this_buf[*timeout-1] == '\033' && c != '[') {
  1126. *timeout = 0;
  1127. _ungetc(c);
  1128. return 1;
  1129. }
  1130. if (*timeout >= 1 && this_buf[*timeout-1] == '\033' && c == '[') {
  1131. *timeout = 1;
  1132. this_buf[*timeout] = c;
  1133. (*timeout)++;
  1134. return 0;
  1135. }
  1136. if (*timeout >= 2 && this_buf[0] == '\033' && this_buf[1] == '[' &&
  1137. (isdigit(c) || c == ';')) {
  1138. this_buf[*timeout] = c;
  1139. (*timeout)++;
  1140. return 0;
  1141. }
  1142. if (*timeout >= 2 && this_buf[0] == '\033' && this_buf[1] == '[') {
  1143. switch (c) {
  1144. case 'A': // up
  1145. history_previous();
  1146. break;
  1147. case 'B': // down
  1148. history_next();
  1149. break;
  1150. case 'C': // right
  1151. if (this_buf[*timeout-1] == '5') {
  1152. word_right();
  1153. } else {
  1154. cursor_right();
  1155. }
  1156. break;
  1157. case 'D': // left
  1158. if (this_buf[*timeout-1] == '5') {
  1159. word_left();
  1160. } else {
  1161. cursor_left();
  1162. }
  1163. break;
  1164. case 'H': // home
  1165. cursor_home();
  1166. break;
  1167. case 'F': // end
  1168. cursor_end();
  1169. break;
  1170. case '~':
  1171. switch (this_buf[*timeout-1]) {
  1172. case '1':
  1173. cursor_home();
  1174. break;
  1175. case '3':
  1176. /* Delete forward */
  1177. if (column < the_line->actual) {
  1178. line_delete(the_line, column+1);
  1179. if (offset > 0) offset--;
  1180. }
  1181. break;
  1182. case '4':
  1183. cursor_end();
  1184. break;
  1185. }
  1186. break;
  1187. default:
  1188. break;
  1189. }
  1190. *timeout = 0;
  1191. return 0;
  1192. }
  1193. *timeout = 0;
  1194. return 0;
  1195. }
  1196. static unsigned int _INTR, _EOF;
  1197. static struct termios old;
  1198. static void get_initial_termios(void) {
  1199. tcgetattr(STDOUT_FILENO, &old);
  1200. _INTR = old.c_cc[VINTR];
  1201. _EOF = old.c_cc[VEOF];
  1202. }
  1203. static void set_unbuffered(void) {
  1204. struct termios new = old;
  1205. new.c_lflag &= (~ICANON & ~ECHO & ~ISIG);
  1206. tcsetattr(STDOUT_FILENO, TCSAFLUSH, &new);
  1207. }
  1208. static void set_buffered(void) {
  1209. tcsetattr(STDOUT_FILENO, TCSAFLUSH, &old);
  1210. }
  1211. static int tabbed;
  1212. static void dummy_redraw(rline_context_t * context) {
  1213. /* Do nothing */
  1214. }
  1215. /**
  1216. * Juggle our buffer with an rline context so we can
  1217. * call original rline functions such as a tab-completion callback
  1218. * or reverse search.
  1219. */
  1220. static void call_rline_func(rline_callback_t func, rline_context_t * context) {
  1221. /* Unicode parser state */
  1222. uint32_t istate = 0;
  1223. uint32_t c;
  1224. /* Don't let rline draw things */
  1225. context->quiet = 1;
  1226. /* Allocate a temporary buffer */
  1227. context->buffer = malloc(buf_size_max);
  1228. memset(context->buffer,0,buf_size_max);
  1229. /* Convert current data to utf-8 */
  1230. unsigned int off = 0;
  1231. for (int j = 0; j < the_line->actual; j++) {
  1232. if (j == column) {
  1233. /* Track cursor position */
  1234. context->offset = off;
  1235. }
  1236. char_t c = the_line->text[j];
  1237. off += to_eight(c.codepoint, &context->buffer[off]);
  1238. }
  1239. /* If the cursor was at the end, the loop above didn't catch it */
  1240. if (column == the_line->actual) context->offset = off;
  1241. /*
  1242. * Did we just press tab before this? This is actually managed
  1243. * by the tab-completion function.
  1244. */
  1245. context->tabbed = tabbed;
  1246. /* Empty callbacks */
  1247. rline_callbacks_t tmp = {0};
  1248. /*
  1249. * Because some clients expect this to be set...
  1250. * (we don't need it, we'll redraw ourselves later)
  1251. */
  1252. tmp.redraw_prompt = dummy_redraw;
  1253. /* Setup context */
  1254. context->callbacks = &tmp;
  1255. context->collected = off;
  1256. context->buffer[off] = '\0';
  1257. context->requested = 1024;
  1258. /* Reset colors (for tab completion candidates, etc. */
  1259. printf("\033[0m");
  1260. /* Call the function */
  1261. func(context);
  1262. /* Now convert back */
  1263. loading = 1;
  1264. int final_column = 0;
  1265. the_line->actual = 0;
  1266. column = 0;
  1267. istate = 0;
  1268. for (int i = 0; i < context->collected; ++i) {
  1269. if (i == context->offset) {
  1270. final_column = column;
  1271. }
  1272. if (!decode(&istate, &c, ((unsigned char *)context->buffer)[i])) {
  1273. insert_char(c);
  1274. }
  1275. }
  1276. free(context->buffer);
  1277. /* Position cursor */
  1278. if (context->offset == context->collected) {
  1279. column = the_line->actual;
  1280. } else {
  1281. column = final_column;
  1282. }
  1283. tabbed = context->tabbed;
  1284. loading = 0;
  1285. /* Recalculate + redraw */
  1286. recalculate_tabs(the_line);
  1287. recalculate_syntax(the_line);
  1288. render_line();
  1289. place_cursor_actual();
  1290. }
  1291. /**
  1292. * Perform actual interactive line editing.
  1293. *
  1294. * This is mostly a reimplementation of bim's
  1295. * INSERT mode, but with some cleanups and fixes
  1296. * to work on a single line and to add some new
  1297. * key bindings we don't have in bim.
  1298. */
  1299. static int read_line(void) {
  1300. int cin;
  1301. uint32_t c;
  1302. int timeout = 0;
  1303. int this_buf[20];
  1304. uint32_t istate = 0;
  1305. int immediate = 1;
  1306. set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
  1307. fprintf(stdout, "◄\033[0m"); /* TODO: This could be retrieved from an envvar */
  1308. for (int i = 0; i < full_width - 1; ++i) {
  1309. fprintf(stdout, " ");
  1310. }
  1311. render_line();
  1312. place_cursor_actual();
  1313. while ((cin = getch(immediate))) {
  1314. if (cin == -1) {
  1315. immediate = 1;
  1316. render_line();
  1317. place_cursor_actual();
  1318. continue;
  1319. }
  1320. get_size();
  1321. if (!decode(&istate, &c, cin)) {
  1322. if (timeout == 0) {
  1323. if (c != '\t') tabbed = 0;
  1324. if (_INTR && c == _INTR) {
  1325. set_colors(COLOR_ALT_FG, COLOR_ALT_BG);
  1326. printf("^%c", (int)('@' + c));
  1327. printf("\033[0m");
  1328. loading = 1;
  1329. the_line->actual = 0;
  1330. column = 0;
  1331. insert_char('\n');
  1332. immediate = 0;
  1333. raise(SIGINT);
  1334. return 1;
  1335. }
  1336. if (_EOF && c == _EOF) {
  1337. if (column == 0 && the_line->actual == 0) {
  1338. for (char *_c = rline_exit_string; *_c; ++_c) {
  1339. insert_char(*_c);
  1340. }
  1341. render_line();
  1342. place_cursor_actual();
  1343. return 1;
  1344. } else { /* Otherwise act like delete */
  1345. if (column < the_line->actual) {
  1346. line_delete(the_line, column+1);
  1347. if (offset > 0) offset--;
  1348. immediate = 0;
  1349. }
  1350. continue;
  1351. }
  1352. }
  1353. switch (c) {
  1354. case '\033':
  1355. if (timeout == 0) {
  1356. this_buf[timeout] = c;
  1357. timeout++;
  1358. }
  1359. break;
  1360. case DELETE_KEY:
  1361. case BACKSPACE_KEY:
  1362. delete_at_cursor();
  1363. immediate = 0;
  1364. break;
  1365. case ENTER_KEY:
  1366. /* Finished */
  1367. loading = 1;
  1368. column = the_line->actual;
  1369. render_line();
  1370. insert_char('\n');
  1371. immediate = 0;
  1372. return 1;
  1373. case 22: /* ^V */
  1374. /* Don't bother with unicode, just take the next byte */
  1375. place_cursor_actual();
  1376. printf("^\b");
  1377. insert_char(getc(stdin));
  1378. immediate = 0;
  1379. break;
  1380. case 23: /* ^W */
  1381. delete_word();
  1382. immediate = 0;
  1383. break;
  1384. case 12: /* ^L - Repaint the whole screen */
  1385. printf("\033[2J\033[H");
  1386. render_line();
  1387. place_cursor_actual();
  1388. break;
  1389. case 11: /* ^K - Clear to end */
  1390. the_line->actual = column;
  1391. immediate = 0;
  1392. break;
  1393. case 21: /* ^U - Kill to beginning */
  1394. while (column) {
  1395. delete_at_cursor();
  1396. }
  1397. immediate = 0;
  1398. break;
  1399. case '\t':
  1400. if (tab_complete_func) {
  1401. /* Tab complete */
  1402. rline_context_t context = {0};
  1403. call_rline_func(tab_complete_func, &context);
  1404. immediate = 0;
  1405. } else {
  1406. /* Insert tab character */
  1407. insert_char('\t');
  1408. immediate = 0;
  1409. }
  1410. break;
  1411. case 18:
  1412. {
  1413. rline_context_t context = {0};
  1414. call_rline_func(rline_reverse_search, &context);
  1415. if (!context.cancel) {
  1416. return 1;
  1417. }
  1418. immediate = 0;
  1419. }
  1420. break;
  1421. default:
  1422. insert_char(c);
  1423. immediate = 0;
  1424. break;
  1425. }
  1426. } else {
  1427. if (handle_escape(this_buf,&timeout,c)) {
  1428. continue;
  1429. }
  1430. immediate = 0;
  1431. }
  1432. } else if (istate == UTF8_REJECT) {
  1433. istate = 0;
  1434. }
  1435. }
  1436. return 0;
  1437. }
  1438. /**
  1439. * Read a line of text with interactive editing.
  1440. */
  1441. int rline_experimental(char * buffer, int buf_size) {
  1442. get_initial_termios();
  1443. set_unbuffered();
  1444. get_size();
  1445. column = 0;
  1446. offset = 0;
  1447. buf_size_max = buf_size;
  1448. char * theme = getenv("RLINE_THEME");
  1449. if (theme && !strcmp(theme,"sunsmoke")) { /* TODO bring back theme tables */
  1450. rline_exp_load_colorscheme_sunsmoke();
  1451. } else {
  1452. rline_exp_load_colorscheme_default();
  1453. }
  1454. the_line = line_create();
  1455. loading = 0;
  1456. read_line();
  1457. printf("\033[?25h\033[0m\n");
  1458. unsigned int off = 0;
  1459. for (int j = 0; j < the_line->actual; j++) {
  1460. char_t c = the_line->text[j];
  1461. off += to_eight(c.codepoint, &buffer[off]);
  1462. }
  1463. free(the_line);
  1464. set_buffered();
  1465. return strlen(buffer);
  1466. }
  1467. void * rline_exp_for_python(void * _stdin, void * _stdout, char * prompt) {
  1468. rline_exp_set_prompts(prompt, "", strlen(prompt), 0);
  1469. char * buf = malloc(1024);
  1470. memset(buf, 0, 1024);
  1471. rline_exp_set_syntax("python");
  1472. rline_exit_string = "";
  1473. rline_experimental(buf, 1024);
  1474. rline_history_insert(strdup(buf));
  1475. rline_scroll = 0;
  1476. return buf;
  1477. }