rline_exp.c 35 KB

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