rline_exp.c 38 KB

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