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