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