rline_exp.c 43 KB

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