1 /* Yash: yet another shell */
2 /* keymap.c: mappings from keys to functions */
3 /* (C) 2007-2018 magicant */
4 
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.
9  *
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.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
17 
18 
19 #include "../common.h"
20 #include "keymap.h"
21 #include <assert.h>
22 #include <errno.h>
23 #if HAVE_GETTEXT
24 # include <libintl.h>
25 #endif
26 #include <stdbool.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include "../builtin.h"
30 #include "../exec.h"
31 #include "../expand.h"
32 #include "../util.h"
33 #include "../xfnmatch.h"
34 #include "complete.h"
35 #include "editing.h"
36 #include "key.h"
37 #include "trie.h"
38 
39 
40 /* Definition of editing modes. */
41 le_mode_T le_modes[LE_MODE_N];
42 
43 /* The current editing mode.
44  * Points to one of the modes in `le_modes'. */
45 le_mode_T *le_current_mode;
46 
47 /* Array of pairs of a command name and function.
48  * Sorted by name. */
49 static const struct command_name_pair {
50     const char *name;
51     le_command_func_T *command;
52 } commands[] = {
53 #include "commands.in"
54 };
55 
56 
57 /* Initializes `le_modes' if not yet initialized.
58  * May be called more than once but does nothing if so. */
le_keymap_init(void)59 void le_keymap_init(void)
60 {
61     static bool initialized = false;
62     if (initialized)
63 	return;
64     initialized = true;
65 
66     trie_T *t;
67 
68 #define Set(key, cmd) \
69     (t = trie_setw(t, key, (trievalue_T) { .cmdfunc = (cmd) }))
70 
71     le_modes[LE_MODE_VI_INSERT].default_command = cmd_self_insert;
72     t = trie_create();
73     Set(Key_backslash, cmd_self_insert);
74     Set(Key_c_v,       cmd_expect_verbatim);
75     Set(Key_right,     cmd_forward_char);
76     Set(Key_left,      cmd_backward_char);
77     Set(Key_home,      cmd_beginning_of_line);
78     Set(Key_end,       cmd_end_of_line);
79     Set(Key_c_j,       cmd_accept_line);
80     Set(Key_c_m,       cmd_accept_line);
81     Set(Key_interrupt, cmd_abort_line);
82     Set(Key_c_c,       cmd_abort_line);
83     Set(Key_eof,       cmd_eof_if_empty);
84     Set(Key_c_d,       cmd_eof_if_empty);
85     Set(Key_escape,    cmd_setmode_vicommand);
86     Set(Key_c_l,       cmd_redraw_all);
87     Set(Key_delete,    cmd_delete_char);
88     Set(Key_backspace, cmd_backward_delete_char);
89     Set(Key_erase,     cmd_backward_delete_char);
90     Set(Key_c_h,       cmd_backward_delete_char);
91     Set(Key_c_w,       cmd_backward_delete_semiword);
92     Set(Key_kill,      cmd_backward_delete_line);
93     Set(Key_c_u,       cmd_backward_delete_line);
94     Set(Key_tab,       cmd_complete_next_candidate);
95     Set(Key_btab,      cmd_complete_prev_candidate);
96     Set(Key_down,      cmd_next_history_eol);
97     Set(Key_c_n,       cmd_next_history_eol);
98     Set(Key_up,        cmd_prev_history_eol);
99     Set(Key_c_p,       cmd_prev_history_eol);
100     le_modes[LE_MODE_VI_INSERT].keymap = t;
101 
102     le_modes[LE_MODE_VI_COMMAND].default_command = cmd_alert;
103     t = trie_create();
104     Set(Key_escape,    cmd_noop);
105     Set(L"1",          cmd_digit_argument);
106     Set(L"2",          cmd_digit_argument);
107     Set(L"3",          cmd_digit_argument);
108     Set(L"4",          cmd_digit_argument);
109     Set(L"5",          cmd_digit_argument);
110     Set(L"6",          cmd_digit_argument);
111     Set(L"7",          cmd_digit_argument);
112     Set(L"8",          cmd_digit_argument);
113     Set(L"9",          cmd_digit_argument);
114     Set(L"0",          cmd_bol_or_digit);
115     Set(Key_c_j,       cmd_accept_line);
116     Set(Key_c_m,       cmd_accept_line);
117     Set(Key_interrupt, cmd_abort_line);
118     Set(Key_c_c,       cmd_abort_line);
119     Set(Key_eof,       cmd_eof_if_empty);
120     Set(Key_c_d,       cmd_eof_if_empty);
121     Set(L"#",          cmd_accept_with_hash);
122     Set(L"i",          cmd_setmode_viinsert);
123     Set(Key_insert,    cmd_setmode_viinsert);
124     Set(Key_c_l,       cmd_redraw_all);
125     Set(L"l",          cmd_forward_char);
126     Set(L" ",          cmd_forward_char);
127     Set(Key_right,     cmd_forward_char);
128     Set(L"h",          cmd_backward_char);
129     Set(Key_left,      cmd_backward_char);
130     Set(Key_backspace, cmd_backward_char);
131     Set(Key_erase,     cmd_backward_char);
132     Set(Key_c_h,       cmd_backward_char);
133     Set(L"W",          cmd_forward_bigword);
134     Set(L"E",          cmd_end_of_bigword);
135     Set(L"B",          cmd_backward_bigword);
136     Set(L"w",          cmd_forward_viword);
137     Set(L"e",          cmd_end_of_viword);
138     Set(L"b",          cmd_backward_viword);
139     Set(Key_home,      cmd_beginning_of_line);
140     Set(L"$",          cmd_end_of_line);
141     Set(Key_end,       cmd_end_of_line);
142     Set(L"^",          cmd_first_nonblank);
143     Set(L"f",          cmd_find_char);
144     Set(L"F",          cmd_find_char_rev);
145     Set(L"t",          cmd_till_char);
146     Set(L"T",          cmd_till_char_rev);
147     Set(L";",          cmd_refind_char);
148     Set(L",",          cmd_refind_char_rev);
149     Set(L"x",          cmd_kill_char);
150     Set(Key_delete,    cmd_kill_char);
151     Set(L"X",          cmd_backward_kill_char);
152     Set(L"P",          cmd_put_before);
153     Set(L"p",          cmd_put);
154     Set(L"u",          cmd_undo);
155     Set(L"U",          cmd_undo_all);
156     Set(Key_c_r,       cmd_cancel_undo);
157     Set(L".",          cmd_redo);
158     Set(L"|",          cmd_go_to_column);
159     Set(L"r",          cmd_vi_replace_char);
160     Set(L"I",          cmd_vi_insert_beginning);
161     Set(L"a",          cmd_vi_append);
162     Set(L"A",          cmd_vi_append_to_eol);
163     Set(L"R",          cmd_vi_replace);
164     Set(L"~",          cmd_vi_switch_case_char);
165     Set(L"y",          cmd_vi_yank);
166     Set(L"Y",          cmd_vi_yank_to_eol);
167     Set(L"d",          cmd_vi_delete);
168     Set(L"D",          cmd_vi_delete_to_eol);
169     Set(L"c",          cmd_vi_change);
170     Set(L"C",          cmd_vi_change_to_eol);
171     Set(L"S",          cmd_vi_change_line);
172     Set(L"s",          cmd_vi_substitute);
173     Set(L"_",          cmd_vi_append_last_bigword);
174     Set(L"@",          cmd_vi_exec_alias);
175     Set(L"v",          cmd_vi_edit_and_accept);
176     Set(L"=",          cmd_vi_complete_list);
177     Set(L"*",          cmd_vi_complete_all);
178     Set(Key_backslash, cmd_vi_complete_max);
179     Set(L"?",          cmd_vi_search_forward);
180     Set(L"/",          cmd_vi_search_backward);
181     Set(L"n",          cmd_search_again);
182     Set(L"N",          cmd_search_again_rev);
183     Set(L"G",          cmd_oldest_history_bol);
184     Set(L"g",          cmd_return_history_bol);
185     Set(L"j",          cmd_next_history_bol);
186     Set(L"+",          cmd_next_history_bol);
187     Set(Key_down,      cmd_next_history_bol);
188     Set(Key_c_n,       cmd_next_history_bol);
189     Set(L"k",          cmd_prev_history_bol);
190     Set(L"-",          cmd_prev_history_bol);
191     Set(Key_up,        cmd_prev_history_bol);
192     Set(Key_c_p,       cmd_prev_history_bol);
193     le_modes[LE_MODE_VI_COMMAND].keymap = t;
194 
195     le_modes[LE_MODE_VI_SEARCH].default_command = cmd_srch_self_insert;
196     t = trie_create();
197     Set(Key_c_v,       cmd_expect_verbatim);
198     Set(Key_interrupt, cmd_abort_line);
199     Set(Key_c_c,       cmd_abort_line);
200     Set(Key_c_l,       cmd_redraw_all);
201     Set(Key_backslash, cmd_srch_self_insert);
202     Set(Key_backspace, cmd_srch_backward_delete_char);
203     Set(Key_erase,     cmd_srch_backward_delete_char);
204     Set(Key_c_h,       cmd_srch_backward_delete_char);
205     Set(Key_kill,      cmd_srch_backward_delete_line);
206     Set(Key_c_u,       cmd_srch_backward_delete_line);
207     Set(Key_c_j,       cmd_srch_accept_search);
208     Set(Key_c_m,       cmd_srch_accept_search);
209     Set(Key_escape,    cmd_srch_abort_search);
210     le_modes[LE_MODE_VI_SEARCH].keymap = t;
211 
212     le_modes[LE_MODE_EMACS].default_command = cmd_self_insert;
213     t = trie_create();
214     Set(Key_backslash,      cmd_self_insert);
215     Set(Key_escape Key_c_i, cmd_insert_tab);
216     Set(Key_c_q,            cmd_expect_verbatim);
217     Set(Key_c_v,            cmd_expect_verbatim);
218     Set(Key_escape "0",     cmd_digit_argument);
219     Set(Key_escape "1",     cmd_digit_argument);
220     Set(Key_escape "2",     cmd_digit_argument);
221     Set(Key_escape "3",     cmd_digit_argument);
222     Set(Key_escape "4",     cmd_digit_argument);
223     Set(Key_escape "5",     cmd_digit_argument);
224     Set(Key_escape "6",     cmd_digit_argument);
225     Set(Key_escape "7",     cmd_digit_argument);
226     Set(Key_escape "8",     cmd_digit_argument);
227     Set(Key_escape "9",     cmd_digit_argument);
228     Set(Key_escape "-",     cmd_digit_argument);
229     Set(Key_c_j,            cmd_accept_line);
230     Set(Key_c_m,            cmd_accept_line);
231     Set(Key_interrupt,      cmd_abort_line);
232     Set(Key_c_c,            cmd_abort_line);
233     Set(Key_eof,            cmd_eof_or_delete);
234     Set(Key_c_d,            cmd_eof_or_delete);
235     Set(Key_escape "#",     cmd_accept_with_hash);
236     Set(Key_c_l,            cmd_redraw_all);
237     Set(Key_right,          cmd_forward_char);
238     Set(Key_c_f,            cmd_forward_char);
239     Set(Key_left,           cmd_backward_char);
240     Set(Key_c_b,            cmd_backward_char);
241     Set(Key_escape "f",     cmd_forward_emacsword);
242     Set(Key_escape "F",     cmd_forward_emacsword);
243     Set(Key_escape "b",     cmd_backward_emacsword);
244     Set(Key_escape "B",     cmd_backward_emacsword);
245     Set(Key_home,           cmd_beginning_of_line);
246     Set(Key_c_a,            cmd_beginning_of_line);
247     Set(Key_end,            cmd_end_of_line);
248     Set(Key_c_e,            cmd_end_of_line);
249     Set(Key_c_rb,           cmd_find_char);
250     Set(Key_escape Key_c_rb, cmd_find_char_rev);
251     Set(Key_delete,         cmd_delete_char);
252     Set(Key_backspace,      cmd_backward_delete_char);
253     Set(Key_erase,          cmd_backward_delete_char);
254     Set(Key_c_h,            cmd_backward_delete_char);
255     Set(Key_escape "d",     cmd_kill_emacsword);
256     Set(Key_escape "D",     cmd_kill_emacsword);
257     Set(Key_escape Key_backspace, cmd_backward_kill_emacsword);
258     Set(Key_escape Key_erase, cmd_backward_kill_emacsword);
259     Set(Key_escape Key_c_h, cmd_backward_kill_emacsword);
260     Set(Key_c_k,            cmd_forward_kill_line);
261     Set(Key_kill,           cmd_backward_kill_line);
262     Set(Key_c_u,            cmd_backward_kill_line);
263     Set(Key_c_x Key_backspace, cmd_backward_kill_line);
264     Set(Key_c_x Key_erase,  cmd_backward_kill_line);
265     Set(Key_c_y,            cmd_put_left);
266     Set(Key_escape "y",     cmd_put_pop);
267     Set(Key_escape "Y",     cmd_put_pop);
268     Set(Key_c_w,            cmd_backward_kill_bigword);
269     Set(Key_c_ul,           cmd_undo);
270     Set(Key_c_x Key_c_u,    cmd_undo);
271     Set(Key_c_x Key_kill,   cmd_undo);
272     Set(Key_escape Key_c_r, cmd_undo_all);
273     Set(Key_escape "r",     cmd_undo_all);
274     Set(Key_escape "R",     cmd_undo_all);
275     Set(Key_tab,            cmd_complete_next_candidate);
276     Set(Key_btab,           cmd_complete_prev_candidate);
277     Set(Key_escape "=",     cmd_complete_list);
278     Set(Key_escape "?",     cmd_complete_list);
279     Set(Key_escape "*",     cmd_complete_all);
280     Set(Key_c_t,            cmd_emacs_transpose_chars);
281     Set(Key_escape "t",     cmd_emacs_transpose_words);
282     Set(Key_escape "T",     cmd_emacs_transpose_words);
283     Set(Key_escape "l",     cmd_emacs_downcase_word);
284     Set(Key_escape "L",     cmd_emacs_downcase_word);
285     Set(Key_escape "u",     cmd_emacs_upcase_word);
286     Set(Key_escape "U",     cmd_emacs_upcase_word);
287     Set(Key_escape "c",     cmd_emacs_capitalize_word);
288     Set(Key_escape "C",     cmd_emacs_capitalize_word);
289     Set(Key_escape Key_backslash, cmd_emacs_delete_horizontal_space);
290     Set(Key_escape " ",     cmd_emacs_just_one_space);
291     Set(Key_c_s,            cmd_emacs_search_forward);
292     Set(Key_c_r,            cmd_emacs_search_backward);
293     Set(Key_escape "<",     cmd_oldest_history_eol);
294     Set(Key_escape ">",     cmd_return_history_eol);
295     Set(Key_down,           cmd_next_history_eol);
296     Set(Key_c_n,            cmd_next_history_eol);
297     Set(Key_up,             cmd_prev_history_eol);
298     Set(Key_c_p,            cmd_prev_history_eol);
299     le_modes[LE_MODE_EMACS].keymap = t;
300 
301     le_modes[LE_MODE_EMACS_SEARCH].default_command = cmd_srch_self_insert;
302     t = trie_create();
303     Set(Key_c_v,       cmd_expect_verbatim);
304     Set(Key_c_m,       cmd_accept_line);
305     Set(Key_interrupt, cmd_abort_line);
306     Set(Key_c_c,       cmd_abort_line);
307     Set(Key_c_l,       cmd_redraw_all);
308     Set(Key_backslash, cmd_srch_self_insert);
309     Set(Key_backspace, cmd_srch_backward_delete_char);
310     Set(Key_erase,     cmd_srch_backward_delete_char);
311     Set(Key_c_h,       cmd_srch_backward_delete_char);
312     Set(Key_kill,      cmd_srch_backward_delete_line);
313     Set(Key_c_u,       cmd_srch_backward_delete_line);
314     Set(Key_c_s,       cmd_srch_continue_forward);
315     Set(Key_c_r,       cmd_srch_continue_backward);
316     Set(Key_c_j,       cmd_srch_accept_search);
317     Set(Key_escape,    cmd_srch_accept_search);
318     Set(Key_c_g,       cmd_srch_abort_search);
319     le_modes[LE_MODE_EMACS_SEARCH].keymap = t;
320 
321     le_modes[LE_MODE_CHAR_EXPECT].default_command = cmd_expect_char;
322     t = trie_create();
323     Set(Key_c_v,       cmd_expect_verbatim);
324     Set(Key_interrupt, cmd_abort_line);
325     Set(Key_c_c,       cmd_abort_line);
326     Set(Key_backslash, cmd_expect_char);
327     Set(Key_escape,    cmd_abort_expect_char);
328     le_modes[LE_MODE_CHAR_EXPECT].keymap = t;
329 
330 #undef Set
331 }
332 
333 /* Sets the editing mode to the one specified by `id'. */
le_set_mode(le_mode_id_T id)334 void le_set_mode(le_mode_id_T id)
335 {
336     assert(id < LE_MODE_N);
337     le_current_mode = le_id_to_mode(id);
338 }
339 
340 /* Generates completion candidates for editing command names matching the
341  * pattern. */
342 /* The prototype of this function is declared in "complete.h". */
generate_bindkey_candidates(const le_compopt_T * compopt)343 void generate_bindkey_candidates(const le_compopt_T *compopt)
344 {
345     if (!(compopt->type & CGT_BINDKEY))
346 	return;
347 
348     le_compdebug("adding lineedit command name candidates");
349     if (!le_compile_cpatterns(compopt))
350 	return;
351 
352     for (size_t i = 0; i < sizeof commands / sizeof *commands; i++)
353 	if (le_match_comppatterns(compopt, commands[i].name))
354 	    le_new_candidate(CT_BINDKEY,
355 		    malloc_mbstowcs(commands[i].name), NULL, compopt);
356 }
357 
358 
359 /********** Built-in **********/
360 
361 static int print_all_commands(void);
362 static int set_key_binding(
363 	le_mode_id_T mode, const wchar_t *keyseq, const wchar_t *commandname)
364     __attribute__((nonnull));
365 static le_command_func_T *get_command_from_name(const char *name)
366     __attribute__((nonnull,pure));
367 static int command_name_compare(const void *p1, const void *p2)
368     __attribute__((nonnull,pure));
369 static int print_binding(le_mode_id_T mode, const wchar_t *keyseq)
370     __attribute__((nonnull));
371 static int print_binding_main(
372 	void *mode, const wchar_t *keyseq, le_command_func_T *cmd)
373     __attribute__((nonnull));
374 static const char *get_command_name(le_command_func_T *command)
375     __attribute__((nonnull,const));
376 
377 /* Options for the "bindkey" built-in. */
378 const struct xgetopt_T bindkey_options[] = {
379     { L'v', L"vi-insert",  OPTARG_NONE, false, NULL, },
380     { L'a', L"vi-command", OPTARG_NONE, false, NULL, },
381     { L'e', L"emacs",      OPTARG_NONE, false, NULL, },
382     { L'l', L"list",       OPTARG_NONE, false, NULL, },
383 #if YASH_ENABLE_HELP
384     { L'-', L"help",       OPTARG_NONE, false, NULL, },
385 #endif
386     { L'\0', NULL, 0, false, NULL, },
387 };
388 
389 /* The "bindkey" built-in, which accepts the following options:
390  *  -v: select the "vi-insert" mode
391  *  -a: select the "vi-command" mode
392  *  -e: select the "emacs" mode
393  *  -l: list names of available commands */
bindkey_builtin(int argc,void ** argv)394 int bindkey_builtin(int argc, void **argv)
395 {
396     bool list = false;
397     le_mode_id_T mode = LE_MODE_N;
398 
399     const struct xgetopt_T *opt;
400     xoptind = 0;
401     while ((opt = xgetopt(argv, bindkey_options, 0)) != NULL) {
402 	switch (opt->shortopt) {
403 	    case L'a':  mode = LE_MODE_VI_COMMAND;  break;
404 	    case L'e':  mode = LE_MODE_EMACS;       break;
405 	    case L'v':  mode = LE_MODE_VI_INSERT;   break;
406 	    case L'l':  list = true;                break;
407 #if YASH_ENABLE_HELP
408 	    case L'-':
409 		return print_builtin_help(ARGV(0));
410 #endif
411 	    default:
412 		return Exit_ERROR;
413 	}
414     }
415 
416     le_keymap_init();
417 
418     if (list) {
419 	if (!validate_operand_count(argc - xoptind, 0, 0))
420 	    return Exit_ERROR;
421 	if (mode != LE_MODE_N) {
422 	    xerror(0, Ngt("option combination is invalid"));
423 	    return Exit_ERROR;
424 	}
425 	return print_all_commands();
426     }
427 
428     if (mode == LE_MODE_N) {
429 	xerror(0, Ngt("no option is specified"));
430 	return Exit_ERROR;
431     }
432 
433     switch (argc - xoptind) {
434 	case 0:;
435 	    /* print all key bindings */
436 	    le_mode_T *m = le_id_to_mode(mode);
437 	    return trie_foreachw(m->keymap, print_binding_main, m);
438 	case 1:
439 	    return print_binding(mode, ARGV(xoptind));
440 	case 2:
441 	    return set_key_binding(mode, ARGV(xoptind), ARGV(xoptind + 1));
442 	default:
443 	    return too_many_operands_error(2);
444     }
445 }
446 
447 /* Prints all available commands to the standard output. */
print_all_commands(void)448 int print_all_commands(void)
449 {
450     for (size_t i = 0; i < sizeof commands / sizeof *commands; i++)
451 	if (!xprintf("%s\n", commands[i].name))
452 	    return Exit_FAILURE;
453     return Exit_SUCCESS;
454 }
455 
456 /* Binds the specified key sequence to the specified command.
457  * If `commandname' is L"-", the binding is removed.
458  * If the specified command is not found, it is an error. */
set_key_binding(le_mode_id_T mode,const wchar_t * keyseq,const wchar_t * commandname)459 int set_key_binding(
460 	le_mode_id_T mode, const wchar_t *keyseq, const wchar_t *commandname)
461 {
462     if (keyseq[0] == L'\0') {
463 	xerror(0, Ngt("cannot bind an empty key sequence"));
464 	return Exit_FAILURE;
465     }
466 
467     if (wcscmp(commandname, L"-") == 0) {
468 	/* delete key binding */
469 	register trie_T *t = le_modes[mode].keymap;
470 	t = trie_removew(t, keyseq);
471 	le_modes[mode].keymap = t;
472     } else {
473 	/* set key binding */
474 	char *mbsname = malloc_wcstombs(commandname);
475 	if (mbsname == NULL) {
476 	    xerror(EILSEQ, Ngt("unexpected error"));
477 	    return Exit_FAILURE;
478 	}
479 
480 	le_command_func_T *cmd = get_command_from_name(mbsname);
481 	free(mbsname);
482 	if (cmd) {
483 	    register trie_T *t = le_modes[mode].keymap;
484 	    t = trie_setw(t, keyseq, (trievalue_T) { .cmdfunc = cmd });
485 	    le_modes[mode].keymap = t;
486 	} else {
487 	    xerror(0, Ngt("no such editing command `%ls'"), commandname);
488 	    return Exit_FAILURE;
489 	}
490     }
491     return Exit_SUCCESS;
492 }
493 
494 /* Returns the command function of the specified name.
495  * Returns a null pointer if no such command is found. */
get_command_from_name(const char * name)496 le_command_func_T *get_command_from_name(const char *name)
497 {
498     struct command_name_pair *cnp = bsearch(name, commands,
499 	    sizeof commands / sizeof *commands,
500 	    sizeof *commands,
501 	    command_name_compare);
502 
503     return (cnp != NULL) ? cnp->command : 0;
504 }
505 
command_name_compare(const void * p1,const void * p2)506 int command_name_compare(const void *p1, const void *p2)
507 {
508     return strcmp((const char *) p1,
509 	    ((const struct command_name_pair *) p2)->name);
510 }
511 
512 /* Prints the binding for the given key sequence. */
print_binding(le_mode_id_T mode,const wchar_t * keyseq)513 int print_binding(le_mode_id_T mode, const wchar_t *keyseq)
514 {
515     trieget_T tg = trie_getw(le_modes[mode].keymap, keyseq);
516 
517     if ((tg.type & TG_EXACTMATCH) && tg.matchlength == wcslen(keyseq)) {
518 	return print_binding_main(
519 		le_id_to_mode(mode), keyseq, tg.value.cmdfunc);
520     } else {
521 	xerror(0, Ngt("key sequence `%ls' is not bound"), keyseq);
522 	return Exit_FAILURE;
523     }
524 }
525 
526 /* Prints a command to restore the specified key binding.
527  * `mode' must be a pointer to one of the modes in `le_modes'. */
print_binding_main(void * mode,const wchar_t * keyseq,le_command_func_T * cmd)528 int print_binding_main(
529 	void *mode, const wchar_t *keyseq, le_command_func_T *cmd)
530 {
531     const char *format;
532     char modechar;
533     wchar_t *keyseqquote;
534     const char *commandname;
535 
536     switch (le_mode_to_id(mode)) {
537 	case LE_MODE_VI_INSERT:     modechar = 'v';  break;
538 	case LE_MODE_VI_COMMAND:    modechar = 'a';  break;
539 	case LE_MODE_VI_SEARCH:     modechar = 'V';  break;
540 	case LE_MODE_EMACS:         modechar = 'e';  break;
541 	case LE_MODE_EMACS_SEARCH:  modechar = 'E';  break;
542 	case LE_MODE_CHAR_EXPECT:   modechar = 'c';  break;
543 	default:                    assert(false);
544     }
545     if (keyseq[0] == L'-')
546 	format = "bindkey -%c -- %ls %s\n";
547     else
548 	format = "bindkey -%c %ls %s\n";
549     keyseqquote = quote_as_word(keyseq);
550     commandname = get_command_name(cmd);
551     xprintf(format, modechar, keyseqquote, commandname);
552     free(keyseqquote);
553     return (yash_error_message_count == 0) ? Exit_SUCCESS : Exit_FAILURE;
554 }
555 
556 /* Returns the name of the specified command. */
get_command_name(le_command_func_T * command)557 const char *get_command_name(le_command_func_T *command)
558 {
559     for (size_t i = 0; i < sizeof commands / sizeof *commands; i++)
560 	if (commands[i].command == command)
561 	    return commands[i].name;
562     return NULL;
563 }
564 
565 #if YASH_ENABLE_HELP
566 const char bindkey_help[] = Ngt(
567 "set or print key bindings for line-editing"
568 );
569 const char bindkey_syntax[] = Ngt(
570 "\tbindkey -aev [key_sequence [command]]\n"
571 "\tbindkey -l\n"
572 );
573 #endif
574 
575 
576 /* vim: set ts=8 sts=4 sw=4 noet tw=80: */
577