rline_exp.c 37 KB

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