usermap.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. /* cooledit.bindings file parser
  2. Authors: 2005 Vitja Makarov
  3. This program is free software; you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation; either version 2 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program; if not, write to the Free Software
  13. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
  14. 02111-1307, USA.
  15. */
  16. /** \file
  17. * \brief Source: cooledit.bindings file parser
  18. * \author Vitja Makarov
  19. * \date 2005
  20. */
  21. #include <config.h>
  22. #include <ctype.h>
  23. #include <errno.h>
  24. #include <stdarg.h>
  25. #include <stdio.h>
  26. #include <stdlib.h>
  27. #include <string.h>
  28. #include <sys/types.h>
  29. #include <sys/stat.h>
  30. #include <unistd.h>
  31. #include "../src/global.h"
  32. #include "../src/tty/tty.h" /* keys */
  33. #include "../src/tty/key.h" /* KEY_M_* */
  34. #include "edit-impl.h"
  35. #include "edit-widget.h"
  36. #include "../src/cmddef.h" /* list of commands */
  37. #include "usermap.h"
  38. #include "../src/wtools.h"
  39. #include "../src/widget.h" /* buttonbar_redraw() */
  40. typedef struct NameMap {
  41. const char *name;
  42. int val;
  43. } name_map_t;
  44. typedef struct Config {
  45. time_t mtime; /* mtime at the moment we read config file */
  46. GArray *keymap;
  47. GArray *ext_keymap;
  48. gchar *labels[10];
  49. } config_t;
  50. typedef struct Command {
  51. const char *name;
  52. int (*handler) (config_t *cfg, int argc, char *argv[]);
  53. } command_t;
  54. static char error_msg[200] = "Nobody see this";
  55. static const name_map_t key_names[] = {
  56. {"backspace", KEY_BACKSPACE},
  57. {"end", KEY_END},
  58. {"up", KEY_UP},
  59. {"down", KEY_DOWN},
  60. {"left", KEY_LEFT},
  61. {"right", KEY_RIGHT},
  62. {"home", KEY_HOME},
  63. {"a1", KEY_A1},
  64. {"c1", KEY_C1},
  65. {"npage", KEY_NPAGE},
  66. {"ppage", KEY_PPAGE},
  67. {"ic", KEY_IC},
  68. {"enter", KEY_ENTER},
  69. {"dc", KEY_DC},
  70. {"scancel", KEY_SCANCEL},
  71. {"btab", KEY_BTAB},
  72. {"f11", KEY_F(11)},
  73. {"f12", KEY_F(12)},
  74. {"f13", KEY_F(13)},
  75. {"f14", KEY_F(14)},
  76. {"f15", KEY_F(15)},
  77. {"f16", KEY_F(16)},
  78. {"f17", KEY_F(17)},
  79. {"f18", KEY_F(18)},
  80. {"f19", KEY_F(19)},
  81. {"f20", KEY_F(20)},
  82. {"tab", '\t'},
  83. {"return", '\n'},
  84. {NULL, 0}
  85. };
  86. static const name_map_t command_names[] = {
  87. {"No-Command", CK_Ignore_Key},
  88. {"Ignore-Key", CK_Ignore_Key},
  89. {"BackSpace", CK_BackSpace},
  90. {"Delete", CK_Delete},
  91. {"Enter", CK_Enter},
  92. {"Page-Up", CK_Page_Up},
  93. {"Page-Down", CK_Page_Down},
  94. {"Left", CK_Left},
  95. {"Right", CK_Right},
  96. {"Word-Left", CK_Word_Left},
  97. {"Word-Right", CK_Word_Right},
  98. {"Up", CK_Up},
  99. {"Down", CK_Down},
  100. {"Home", CK_Home},
  101. {"End", CK_End},
  102. {"Tab", CK_Tab},
  103. {"Undo", CK_Undo},
  104. {"Beginning-Of-Text", CK_Beginning_Of_Text},
  105. {"End-Of-Text", CK_End_Of_Text},
  106. {"Scroll-Up", CK_Scroll_Up},
  107. {"Scroll-Down", CK_Scroll_Down},
  108. {"Return", CK_Return},
  109. {"Begin-Page", CK_Begin_Page},
  110. {"End-Page", CK_End_Page},
  111. {"Delete-Word-Left", CK_Delete_Word_Left},
  112. {"Delete-Word-Right", CK_Delete_Word_Right},
  113. {"Paragraph-Up", CK_Paragraph_Up},
  114. {"Paragraph-Down", CK_Paragraph_Down},
  115. {"Save", CK_Save},
  116. {"Load", CK_Load},
  117. {"New", CK_New},
  118. {"Save-as", CK_Save_As},
  119. {"Mark", CK_Mark},
  120. {"Copy", CK_Copy},
  121. {"Move", CK_Move},
  122. {"Remove", CK_Remove},
  123. {"Unmark", CK_Unmark},
  124. {"Save-Block", CK_Save_Block},
  125. {"Column-Mark", CK_Column_Mark},
  126. {"Find", CK_Find},
  127. {"Find-Again", CK_Find_Again},
  128. {"Replace", CK_Replace},
  129. {"Replace-Again", CK_Replace_Again},
  130. {"Complete-Word", CK_Complete_Word},
  131. {"Debug-Start", CK_Debug_Start},
  132. {"Debug-Stop", CK_Debug_Stop},
  133. {"Debug-Toggle-Break", CK_Debug_Toggle_Break},
  134. {"Debug-Clear", CK_Debug_Clear},
  135. {"Debug-Next", CK_Debug_Next},
  136. {"Debug-Step", CK_Debug_Step},
  137. {"Debug-Back-Trace", CK_Debug_Back_Trace},
  138. {"Debug-Continue", CK_Debug_Continue},
  139. {"Debug-Enter-Command", CK_Debug_Enter_Command},
  140. {"Debug-Until-Curser", CK_Debug_Until_Curser},
  141. {"Insert-File", CK_Insert_File},
  142. {"Exit", CK_Exit},
  143. {"Toggle-Insert", CK_Toggle_Insert},
  144. {"Help", CK_Help},
  145. {"Date", CK_Date},
  146. {"Refresh", CK_Refresh},
  147. {"Goto", CK_Goto},
  148. {"Delete-Line", CK_Delete_Line},
  149. {"Delete-To-Line-End", CK_Delete_To_Line_End},
  150. {"Delete-To-Line-Begin", CK_Delete_To_Line_Begin},
  151. {"Man-Page", CK_Man_Page},
  152. {"Sort", CK_Sort},
  153. {"Mail", CK_Mail},
  154. {"Cancel", CK_Cancel},
  155. {"Complete", CK_Complete},
  156. {"Paragraph-Format", CK_Paragraph_Format},
  157. {"Util", CK_Util},
  158. {"Type-Load-Python", CK_Type_Load_Python},
  159. {"Find-File", CK_Find_File},
  160. {"Ctags", CK_Ctags},
  161. {"Match-Bracket", CK_Match_Bracket},
  162. {"Terminal", CK_Terminal},
  163. {"Terminal-App", CK_Terminal_App},
  164. {"ExtCmd", CK_ExtCmd},
  165. {"User-Menu", CK_User_Menu},
  166. {"Save-Desktop", CK_Save_Desktop},
  167. {"New-Window", CK_New_Window},
  168. {"Cycle", CK_Cycle},
  169. {"Menu", CK_Menu},
  170. {"Save-And-Quit", CK_Save_And_Quit},
  171. {"Run-Another", CK_Run_Another},
  172. {"Check-Save-And-Quit", CK_Check_Save_And_Quit},
  173. {"Maximize", CK_Maximize},
  174. {"Begin-Record-Macro", CK_Begin_Record_Macro},
  175. {"End-Record-Macro", CK_End_Record_Macro},
  176. {"Delete-Macro", CK_Delete_Macro},
  177. {"Toggle-Bookmark", CK_Toggle_Bookmark},
  178. {"Flush-Bookmarks", CK_Flush_Bookmarks},
  179. {"Next-Bookmark", CK_Next_Bookmark},
  180. {"Prev-Bookmark", CK_Prev_Bookmark},
  181. {"Page-Up-Highlight", CK_Page_Up_Highlight},
  182. {"Page-Down-Highlight", CK_Page_Down_Highlight},
  183. {"Left-Highlight", CK_Left_Highlight},
  184. {"Right-Highlight", CK_Right_Highlight},
  185. {"Word-Left-Highlight", CK_Word_Left_Highlight},
  186. {"Word-Right-Highlight", CK_Word_Right_Highlight},
  187. {"Up-Highlight", CK_Up_Highlight},
  188. {"Down-Highlight", CK_Down_Highlight},
  189. {"Home-Highlight", CK_Home_Highlight},
  190. {"End-Highlight", CK_End_Highlight},
  191. {"Beginning-Of-Text-Highlight", CK_Beginning_Of_Text_Highlight},
  192. {"End-Of-Text_Highlight", CK_End_Of_Text_Highlight},
  193. {"Begin-Page-Highlight", CK_Begin_Page_Highlight},
  194. {"End-Page-Highlight", CK_End_Page_Highlight},
  195. {"Scroll-Up-Highlight", CK_Scroll_Up_Highlight},
  196. {"Scroll-Down-Highlight", CK_Scroll_Down_Highlight},
  197. {"Paragraph-Up-Highlight", CK_Paragraph_Up_Highlight},
  198. {"Paragraph-Down-Highlight", CK_Paragraph_Down_Highlight},
  199. {"XStore", CK_XStore},
  200. {"XCut", CK_XCut},
  201. {"XPaste", CK_XPaste},
  202. {"Selection-History", CK_Selection_History},
  203. {"Shell", CK_Shell},
  204. {"Select-Codepage", CK_Select_Codepage},
  205. {"Insert-Literal", CK_Insert_Literal},
  206. {"Execute-Macro", CK_Execute_Macro},
  207. {"Begin-or-End-Macro", CK_Begin_End_Macro},
  208. {"Ext-mode", CK_Ext_Mode},
  209. {"Toggle-Line-State", CK_Toggle_Line_State},
  210. #if 0
  211. {"Focus-Next", CK_Focus_Next},
  212. {"Focus-Prev", CK_Focus_Prev},
  213. {"Height-Inc", CK_Height_Inc},
  214. {"Height-Dec", CK_Height_Dec},
  215. {"Make", CK_Make},
  216. {"Error-Next", CK_Error_Next},
  217. {"Error-Prev", CK_Error_Prev},
  218. #endif
  219. {NULL, 0}
  220. };
  221. static void
  222. cfg_free_maps(config_t *cfg)
  223. {
  224. int i;
  225. if (cfg->keymap)
  226. g_array_free(cfg->keymap, TRUE);
  227. cfg->keymap = NULL;
  228. if (cfg->ext_keymap)
  229. g_array_free(cfg->ext_keymap, TRUE);
  230. cfg->ext_keymap = NULL;
  231. for (i = 0; i < 10; i++)
  232. g_free(cfg->labels[i]);
  233. }
  234. /* Returns an array containing the words in str. WARNING: As long as
  235. * the result is used, str[...] must be valid memory. This function
  236. * modifies str[...] and uses it, so the caller should not use it until
  237. * g_ptr_array_free() is called for the returned result.
  238. */
  239. static GPtrArray *
  240. split_line(char *str)
  241. {
  242. gboolean inside_quote = FALSE;
  243. int move = 0;
  244. GPtrArray *args;
  245. args = g_ptr_array_new();
  246. /* skip spaces */
  247. while (isspace((unsigned char) *str))
  248. str++;
  249. g_ptr_array_add(args, str);
  250. for (;; str++) {
  251. switch (*str) {
  252. case '#': /* cut off comments */
  253. if (inside_quote)
  254. break;
  255. /* FALLTHROUGH */
  256. case '\n': /* end of line */
  257. case '\r':
  258. case '\0':
  259. if (str == g_ptr_array_index(args, args->len - 1))
  260. g_ptr_array_remove_index(args, args->len - 1);
  261. else
  262. *(str - move) = '\0';
  263. return args;
  264. case '"': /* quote */
  265. case '\'':
  266. inside_quote = !inside_quote;
  267. move++;
  268. continue;
  269. case ' ': /* spaces */
  270. case '\t':
  271. if (inside_quote)
  272. break;
  273. *(str++ - move) = '\0';
  274. move = 0;
  275. /* skip spaces */
  276. while (isspace((unsigned char) *str))
  277. str++;
  278. g_ptr_array_add(args, str--);
  279. break;
  280. case '\\':
  281. switch (*(++str)) {
  282. case 'n':
  283. *str = '\n';
  284. break;
  285. case 'r':
  286. *str = '\r';
  287. break;
  288. case 't':
  289. *str = '\t';
  290. break;
  291. }
  292. move++;
  293. break;
  294. }
  295. if (move != 0)
  296. *(str - move) = *str;
  297. }
  298. /* never be here */
  299. }
  300. static void
  301. keymap_add(GArray *keymap, int key, int cmd)
  302. {
  303. global_key_map_t new_one, *map;
  304. guint i;
  305. map = &(g_array_index(keymap, global_key_map_t, 0));
  306. for (i = 0; i < keymap->len; i++) {
  307. if (map[i].key == key) {
  308. map[i].command = cmd;
  309. return;
  310. }
  311. }
  312. new_one.key = key;
  313. new_one.command = cmd;
  314. g_array_append_val(keymap, new_one);
  315. }
  316. /* bind <key> <command> */
  317. static gboolean
  318. cmd_bind(config_t *cfg, int argc, char *argv[])
  319. {
  320. char *keyname, *command;
  321. const name_map_t *key = key_names;
  322. const name_map_t *cmd = command_names;
  323. int mod = 0, k = -1, m = 0;
  324. if (argc != 3) {
  325. snprintf(error_msg, sizeof(error_msg),
  326. _("bind: Wrong argument number, bind <key> <command>"));
  327. return FALSE;
  328. }
  329. keyname = argv[1];
  330. command = argv[2];
  331. while (*keyname) {
  332. switch (*keyname++) {
  333. case 'C':
  334. m = KEY_M_CTRL;
  335. continue;
  336. case 'M':
  337. case 'A':
  338. m = KEY_M_ALT;
  339. continue;
  340. case 'S':
  341. m = KEY_M_SHIFT;
  342. continue;
  343. case '-':
  344. if (!m) { /* incorrect key */
  345. snprintf(error_msg, sizeof(error_msg),
  346. _("bind: Bad key value `%s'"), keyname);
  347. return FALSE;
  348. }
  349. mod |= m;
  350. m = 0;
  351. continue;
  352. }
  353. keyname--;
  354. break;
  355. }
  356. /* no key */
  357. if (keyname[0] == '\0') {
  358. snprintf(error_msg, sizeof(error_msg), _("bind: Ehh...no key?"));
  359. return FALSE;
  360. }
  361. /* ordinary key */
  362. if (keyname[1] == '\0') {
  363. k = keyname[0];
  364. } else {
  365. while (key->name && strcasecmp(key->name, keyname) != 0)
  366. key++;
  367. }
  368. if (k < 0 && !key->name) {
  369. snprintf(error_msg, sizeof(error_msg),
  370. _("bind: Unknown key: `%s'"), keyname);
  371. return FALSE;
  372. }
  373. while (cmd->name && strcasecmp(cmd->name, command) != 0)
  374. cmd++;
  375. if (!cmd->name) {
  376. snprintf(error_msg, sizeof(error_msg),
  377. _("bind: Unknown command: `%s'"), command);
  378. return FALSE;
  379. }
  380. if (mod & KEY_M_CTRL) {
  381. if (k < 256)
  382. k = XCTRL(k);
  383. else
  384. k |= KEY_M_CTRL;
  385. }
  386. if (mod & KEY_M_ALT)
  387. k |= KEY_M_ALT;
  388. if (mod & KEY_M_SHIFT)
  389. k |= KEY_M_SHIFT;
  390. if (!strcasecmp("bind-ext", argv[0]))
  391. keymap_add(cfg->ext_keymap, k, cmd->val);
  392. else
  393. keymap_add(cfg->keymap, k, cmd->val);
  394. return TRUE;
  395. }
  396. #if 0
  397. #define CMD_F(x) \
  398. static void cmd_F ## x (WEdit * edit) \
  399. { \
  400. send_message ((Widget *) edit, WIDGET_KEY, KEY_F (x)); \
  401. }
  402. CMD_F(1)
  403. CMD_F(2)
  404. CMD_F(3)
  405. CMD_F(4)
  406. CMD_F(5)
  407. CMD_F(6)
  408. CMD_F(7)
  409. CMD_F(8)
  410. CMD_F(9)
  411. CMD_F(10)
  412. void (*cmd_Fx[]) (WEdit *) = {
  413. cmd_F1, cmd_F2, cmd_F3, cmd_F4, cmd_F5,
  414. cmd_F6, cmd_F7, cmd_F8, cmd_F9, cmd_F10
  415. } ;
  416. /* move me! */
  417. static void edit_my_define (Dlg_head * h, int idx, const char *text,
  418. void (*fn) (WEdit *), WEdit * edit)
  419. {
  420. /* function-cast ok */
  421. buttonbar_set_label_data (h, idx, text, (buttonbarfn) fn, edit);
  422. }
  423. #endif
  424. /* label <number> <command> <label> */
  425. static gboolean
  426. cmd_label(config_t *cfg, int argc, char *argv[])
  427. {
  428. const name_map_t *cmd = command_names;
  429. const char *command, *label;
  430. int fn;
  431. if (argc != 4) {
  432. snprintf(error_msg, sizeof(error_msg),
  433. _("%s: Syntax: %s <n> <command> <label>"),
  434. argv[0], argv[0]);
  435. return FALSE;
  436. }
  437. fn = strtol(argv[1], NULL, 0);
  438. command = argv[2];
  439. label = argv[3];
  440. while (cmd->name && strcasecmp(cmd->name, command) != 0)
  441. cmd++;
  442. if (!cmd->name) {
  443. snprintf(error_msg, sizeof(error_msg),
  444. _("%s: Unknown command: `%s'"), argv[0], command);
  445. return FALSE;
  446. }
  447. if (fn < 1 || fn > 10) {
  448. snprintf(error_msg, sizeof(error_msg),
  449. _("%s: fn should be 1-10"), argv[0]);
  450. return FALSE;
  451. }
  452. keymap_add(cfg->keymap, KEY_F(fn), cmd->val);
  453. cfg->labels[fn - 1] = g_strdup(label);
  454. return TRUE;
  455. }
  456. static gboolean
  457. parse_file(config_t *cfg, const char *file, const command_t *cmd)
  458. {
  459. char buf[200];
  460. int line = 0;
  461. FILE *fp = fopen(file, "r");
  462. if (!fp) {
  463. snprintf(error_msg, sizeof(error_msg), _("%s: fopen(): %s"),
  464. file, strerror(errno));
  465. return FALSE;
  466. }
  467. while (fgets(buf, sizeof(buf), fp)) {
  468. const command_t *c = cmd;
  469. GPtrArray *args;
  470. char **argv;
  471. line++;
  472. args = split_line(buf);
  473. argv = (char **) args->pdata;
  474. if (args->len == 0) {
  475. g_ptr_array_free(args, TRUE);
  476. continue;
  477. }
  478. while (c->name != NULL && strcasecmp(c->name, argv[0]) != 0)
  479. c++;
  480. if (c->name == NULL) {
  481. snprintf(error_msg, sizeof(error_msg),
  482. _("%s:%d: unknown command `%s'"), file, line,
  483. argv[0]);
  484. g_ptr_array_free(args, TRUE);
  485. fclose(fp);
  486. return FALSE;
  487. }
  488. if (!(c->handler(cfg, args->len, argv))) {
  489. char *ss = g_strdup(error_msg);
  490. snprintf(error_msg, sizeof(error_msg),
  491. _("%s:%d: %s"), file, line, ss);
  492. g_free(ss);
  493. g_ptr_array_free(args, TRUE);
  494. fclose(fp);
  495. return FALSE;
  496. }
  497. g_ptr_array_free(args, TRUE);
  498. }
  499. fclose(fp);
  500. return TRUE;
  501. }
  502. static gboolean
  503. load_user_keymap(config_t *cfg, const char *file)
  504. {
  505. const command_t cmd[] = {
  506. {"bind", cmd_bind},
  507. {"bind-ext", cmd_bind},
  508. {"label", cmd_label},
  509. {0, 0}
  510. };
  511. cfg->keymap = g_array_new(TRUE, FALSE, sizeof(global_key_map_t));
  512. cfg->ext_keymap = g_array_new(TRUE, FALSE, sizeof(global_key_map_t));
  513. if (!parse_file(cfg, file, cmd)) {
  514. return FALSE;
  515. }
  516. return TRUE;
  517. }
  518. gboolean
  519. edit_load_user_map(WEdit *edit)
  520. {
  521. static config_t cfg;
  522. config_t new_cfg;
  523. char *file;
  524. struct stat s;
  525. if (edit_key_emulation != EDIT_KEY_EMULATION_USER)
  526. return TRUE;
  527. file = concat_dir_and_file(home_dir, MC_USERMAP);
  528. if (stat(file, &s) < 0) {
  529. char *msg = g_strdup_printf(_("%s not found!"), file);
  530. edit_error_dialog(_("Error"), msg);
  531. g_free(msg);
  532. g_free(file);
  533. return FALSE;
  534. }
  535. if (s.st_mtime != cfg.mtime) {
  536. memset(&new_cfg, 0, sizeof(new_cfg));
  537. new_cfg.mtime = s.st_mtime;
  538. if (!load_user_keymap(&new_cfg, file)) {
  539. edit_error_dialog(_("Error"), error_msg);
  540. cfg_free_maps(&new_cfg);
  541. g_free(file);
  542. return FALSE;
  543. } else {
  544. cfg_free_maps(&cfg);
  545. cfg = new_cfg;
  546. }
  547. }
  548. edit->user_map = (global_key_map_t *) cfg.keymap->data;
  549. edit->ext_map = (global_key_map_t *) cfg.ext_keymap->data;
  550. memcpy(edit->labels, cfg.labels, sizeof(edit->labels));
  551. g_free(file);
  552. return TRUE;
  553. }