lexer: add fstab
[vis.git] / vis-cmds.c
blobe52072ddb44902ee9ab3396c47d3bd1da0f0c8fa
1 /* this file is included from sam.c */
3 #include <termkey.h>
4 #include "vis-lua.h"
6 #ifndef VIS_OPEN
7 #define VIS_OPEN "vis-open"
8 #endif
10 typedef struct {
11 CmdFunc func;
12 void *data;
13 } CmdUser;
15 bool vis_cmd_register(Vis *vis, const char *name, void *data, CmdFunc func) {
16 if (!name)
17 return false;
18 if (!vis->usercmds && !(vis->usercmds = map_new()))
19 return false;
20 CmdUser *cmd = calloc(1, sizeof *cmd);
21 if (!cmd)
22 return false;
23 cmd->func = func;
24 cmd->data = data;
25 if (!map_put(vis->cmds, name, &cmddef_user))
26 goto err;
27 if (!map_put(vis->usercmds, name, cmd)) {
28 map_delete(vis->cmds, name);
29 goto err;
31 return true;
32 err:
33 free(cmd);
34 return false;
37 bool vis_cmd_unregister(Vis *vis, const char *name) {
38 if (!name)
39 return true;
40 CmdUser *cmd = map_delete(vis->usercmds, name);
41 if (!cmd)
42 return false;
43 if (!map_delete(vis->cmds, name))
44 return false;
45 free(cmd);
46 return true;
49 static bool cmd_user(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
50 CmdUser *user = map_get(vis->usercmds, argv[0]);
51 return user && user->func(vis, win, user->data, cmd->flags == '!', argv, cur, range);
54 static void windows_arrange(Vis *vis, enum UiLayout layout) {
55 vis->ui->arrange(vis->ui, layout);
58 static void tabwidth_set(Vis *vis, int tabwidth) {
59 if (tabwidth < 1 || tabwidth > 8)
60 return;
61 for (Win *win = vis->windows; win; win = win->next)
62 view_tabwidth_set(win->view, tabwidth);
63 vis->tabwidth = tabwidth;
66 /* parse human-readable boolean value in s. If successful, store the result in
67 * outval and return true. Else return false and leave outval alone. */
68 static bool parse_bool(const char *s, bool *outval) {
69 for (const char **t = (const char*[]){"1", "true", "yes", "on", NULL}; *t; t++) {
70 if (!strcasecmp(s, *t)) {
71 *outval = true;
72 return true;
75 for (const char **f = (const char*[]){"0", "false", "no", "off", NULL}; *f; f++) {
76 if (!strcasecmp(s, *f)) {
77 *outval = false;
78 return true;
81 return false;
84 static bool cmd_set(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
86 typedef struct {
87 const char *names[3];
88 enum {
89 OPTION_TYPE_STRING,
90 OPTION_TYPE_BOOL,
91 OPTION_TYPE_NUMBER,
92 OPTION_TYPE_UNSIGNED,
93 } type;
94 enum {
95 OPTION_FLAG_OPTIONAL = 1 << 0,
96 OPTION_FLAG_WINDOW = 1 << 1,
97 } flags;
98 int index;
99 } OptionDef;
101 enum {
102 OPTION_AUTOINDENT,
103 OPTION_EXPANDTAB,
104 OPTION_TABWIDTH,
105 OPTION_THEME,
106 OPTION_SYNTAX,
107 OPTION_SHOW,
108 OPTION_NUMBER,
109 OPTION_NUMBER_RELATIVE,
110 OPTION_CURSOR_LINE,
111 OPTION_COLOR_COLUMN,
112 OPTION_HORIZON,
115 /* definitions have to be in the same order as the enum above */
116 static OptionDef options[] = {
117 [OPTION_AUTOINDENT] = { { "autoindent", "ai" }, OPTION_TYPE_BOOL },
118 [OPTION_EXPANDTAB] = { { "expandtab", "et" }, OPTION_TYPE_BOOL },
119 [OPTION_TABWIDTH] = { { "tabwidth", "tw" }, OPTION_TYPE_NUMBER },
120 [OPTION_THEME] = { { "theme" }, OPTION_TYPE_STRING, },
121 [OPTION_SYNTAX] = { { "syntax" }, OPTION_TYPE_STRING, OPTION_FLAG_WINDOW|OPTION_FLAG_OPTIONAL },
122 [OPTION_SHOW] = { { "show" }, OPTION_TYPE_STRING, OPTION_FLAG_WINDOW },
123 [OPTION_NUMBER] = { { "numbers", "nu" }, OPTION_TYPE_BOOL, OPTION_FLAG_WINDOW },
124 [OPTION_NUMBER_RELATIVE] = { { "relativenumbers", "rnu" }, OPTION_TYPE_BOOL, OPTION_FLAG_WINDOW },
125 [OPTION_CURSOR_LINE] = { { "cursorline", "cul" }, OPTION_TYPE_BOOL, OPTION_FLAG_WINDOW },
126 [OPTION_COLOR_COLUMN] = { { "colorcolumn", "cc" }, OPTION_TYPE_NUMBER, OPTION_FLAG_WINDOW },
127 [OPTION_HORIZON] = { { "horizon" }, OPTION_TYPE_UNSIGNED, OPTION_FLAG_WINDOW },
130 if (!vis->options) {
131 if (!(vis->options = map_new()))
132 return false;
133 for (int i = 0; i < LENGTH(options); i++) {
134 options[i].index = i;
135 for (const char **name = options[i].names; *name; name++) {
136 if (!map_put(vis->options, *name, &options[i]))
137 return false;
142 if (!argv[1]) {
143 vis_info_show(vis, "Expecting: set option [value]");
144 return false;
147 Arg arg;
148 bool invert = false;
149 OptionDef *opt = NULL;
151 if (!strncasecmp(argv[1], "no", 2)) {
152 opt = map_closest(vis->options, argv[1]+2);
153 if (opt && opt->type == OPTION_TYPE_BOOL)
154 invert = true;
155 else
156 opt = NULL;
159 if (!opt)
160 opt = map_closest(vis->options, argv[1]);
161 if (!opt) {
162 vis_info_show(vis, "Unknown option: `%s'", argv[1]);
163 return false;
166 if (!win && (opt->flags & OPTION_FLAG_WINDOW)) {
167 vis_info_show(vis, "Need active window for :set command");
168 return false;
171 switch (opt->type) {
172 case OPTION_TYPE_STRING:
173 if (!(opt->flags & OPTION_FLAG_OPTIONAL) && !argv[2]) {
174 vis_info_show(vis, "Expecting string option value");
175 return false;
177 arg.s = argv[2];
178 break;
179 case OPTION_TYPE_BOOL:
180 if (!argv[2]) {
181 arg.b = true;
182 } else if (!parse_bool(argv[2], &arg.b)) {
183 vis_info_show(vis, "Expecting boolean option value not: `%s'", argv[2]);
184 return false;
186 if (invert)
187 arg.b = !arg.b;
188 break;
189 case OPTION_TYPE_NUMBER:
190 case OPTION_TYPE_UNSIGNED:
191 if (!argv[2]) {
192 vis_info_show(vis, "Expecting number");
193 return false;
195 /* TODO: error checking? long type */
196 arg.u = strtoul(argv[2], NULL, 10);
197 break;
200 switch (opt->index) {
201 case OPTION_EXPANDTAB:
202 vis->expandtab = arg.b;
203 break;
204 case OPTION_AUTOINDENT:
205 vis->autoindent = arg.b;
206 break;
207 case OPTION_TABWIDTH:
208 tabwidth_set(vis, arg.i);
209 break;
210 case OPTION_SYNTAX:
211 if (!argv[2]) {
212 const char *syntax = view_syntax_get(win->view);
213 if (syntax)
214 vis_info_show(vis, "Syntax definition in use: `%s'", syntax);
215 else
216 vis_info_show(vis, "No syntax definition in use");
217 return true;
220 if (parse_bool(argv[2], &arg.b) && !arg.b)
221 return view_syntax_set(win->view, NULL);
222 if (!view_syntax_set(win->view, argv[2])) {
223 vis_info_show(vis, "Unknown syntax definition: `%s'", argv[2]);
224 return false;
226 break;
227 case OPTION_SHOW:
228 if (!argv[2]) {
229 vis_info_show(vis, "Expecting: spaces, tabs, newlines");
230 return false;
232 const char *keys[] = { "spaces", "tabs", "newlines" };
233 const int values[] = {
234 UI_OPTION_SYMBOL_SPACE,
235 UI_OPTION_SYMBOL_TAB|UI_OPTION_SYMBOL_TAB_FILL,
236 UI_OPTION_SYMBOL_EOL,
238 int flags = view_options_get(win->view);
239 for (const char **args = &argv[2]; *args; args++) {
240 for (int i = 0; i < LENGTH(keys); i++) {
241 if (strcmp(*args, keys[i]) == 0) {
242 flags |= values[i];
243 } else if (strstr(*args, keys[i]) == *args) {
244 bool show;
245 const char *v = *args + strlen(keys[i]);
246 if (*v == '=' && parse_bool(v+1, &show)) {
247 if (show)
248 flags |= values[i];
249 else
250 flags &= ~values[i];
255 view_options_set(win->view, flags);
256 break;
257 case OPTION_NUMBER: {
258 enum UiOption opt = view_options_get(win->view);
259 if (arg.b) {
260 opt &= ~UI_OPTION_LINE_NUMBERS_RELATIVE;
261 opt |= UI_OPTION_LINE_NUMBERS_ABSOLUTE;
262 } else {
263 opt &= ~UI_OPTION_LINE_NUMBERS_ABSOLUTE;
265 view_options_set(win->view, opt);
266 break;
268 case OPTION_NUMBER_RELATIVE: {
269 enum UiOption opt = view_options_get(win->view);
270 if (arg.b) {
271 opt &= ~UI_OPTION_LINE_NUMBERS_ABSOLUTE;
272 opt |= UI_OPTION_LINE_NUMBERS_RELATIVE;
273 } else {
274 opt &= ~UI_OPTION_LINE_NUMBERS_RELATIVE;
276 view_options_set(win->view, opt);
277 break;
279 case OPTION_CURSOR_LINE: {
280 enum UiOption opt = view_options_get(win->view);
281 if (arg.b)
282 opt |= UI_OPTION_CURSOR_LINE;
283 else
284 opt &= ~UI_OPTION_CURSOR_LINE;
285 view_options_set(win->view, opt);
286 break;
288 case OPTION_THEME:
289 if (!vis_theme_load(vis, arg.s)) {
290 vis_info_show(vis, "Failed to load theme: `%s'", arg.s);
291 return false;
293 break;
294 case OPTION_COLOR_COLUMN:
295 view_colorcolumn_set(win->view, arg.i);
296 break;
297 case OPTION_HORIZON:
298 view_horizon_set(win->view, arg.u);
299 break;
302 return true;
305 static bool is_file_pattern(const char *pattern) {
306 if (!pattern)
307 return false;
308 struct stat meta;
309 if (stat(pattern, &meta) == 0 && S_ISDIR(meta.st_mode))
310 return true;
311 for (char special[] = "*?[{$~", *s = special; *s; s++) {
312 if (strchr(pattern, *s))
313 return true;
315 return false;
318 static const char *file_open_dialog(Vis *vis, const char *pattern) {
319 static char name[PATH_MAX];
320 name[0] = '\0';
322 if (!is_file_pattern(pattern))
323 return pattern;
325 Buffer bufcmd, bufout, buferr;
326 buffer_init(&bufcmd);
327 buffer_init(&bufout);
328 buffer_init(&buferr);
330 if (!buffer_put0(&bufcmd, VIS_OPEN " ") || !buffer_append0(&bufcmd, pattern ? pattern : ""))
331 return NULL;
333 Filerange empty = text_range_empty();
334 int status = vis_pipe(vis, &empty, (const char*[]){ buffer_content0(&bufcmd), NULL },
335 &bufout, read_buffer, &buferr, read_buffer);
337 if (status == 0)
338 strncpy(name, buffer_content0(&bufout), sizeof(name)-1);
339 else
340 vis_info_show(vis, "Command failed %s", buffer_content0(&buferr));
342 buffer_release(&bufcmd);
343 buffer_release(&bufout);
344 buffer_release(&buferr);
346 for (char *end = name+strlen(name)-1; end >= name && isspace((unsigned char)*end); end--)
347 *end = '\0';
349 return name[0] ? name : NULL;
352 static bool openfiles(Vis *vis, const char **files) {
353 for (; *files; files++) {
354 const char *file = file_open_dialog(vis, *files);
355 if (!file)
356 return false;
357 errno = 0;
358 if (!vis_window_new(vis, file)) {
359 vis_info_show(vis, "Could not open `%s' %s", file,
360 errno ? strerror(errno) : "");
361 return false;
364 return true;
367 static bool cmd_open(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
368 if (!argv[1])
369 return vis_window_new(vis, NULL);
370 return openfiles(vis, &argv[1]);
373 static void info_unsaved_changes(Vis *vis) {
374 vis_info_show(vis, "No write since last change (add ! to override)");
377 static bool cmd_edit(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
378 Win *oldwin = win;
379 if (!oldwin)
380 return false;
381 if (cmd->flags != '!' && !vis_window_closable(oldwin)) {
382 info_unsaved_changes(vis);
383 return false;
385 if (!argv[1])
386 return vis_window_reload(oldwin);
387 if (!openfiles(vis, &argv[1]))
388 return false;
389 if (vis->win != oldwin) {
390 Win *newwin = vis->win;
391 vis_window_swap(oldwin, newwin);
392 vis_window_close(oldwin);
393 vis_window_focus(newwin);
395 return vis->win != oldwin;
398 static bool has_windows(Vis *vis) {
399 for (Win *win = vis->windows; win; win = win->next) {
400 if (!win->file->internal)
401 return true;
403 return false;
406 static bool cmd_quit(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
407 if (cmd->flags != '!' && !vis_window_closable(win)) {
408 info_unsaved_changes(vis);
409 return false;
411 vis_window_close(win);
412 if (!has_windows(vis))
413 vis_exit(vis, EXIT_SUCCESS);
414 return true;
417 static bool cmd_bdelete(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
418 if (!win)
419 return false;
420 Text *txt = win->file->text;
421 if (text_modified(txt) && cmd->flags != '!') {
422 info_unsaved_changes(vis);
423 return false;
425 for (Win *next, *win = vis->windows; win; win = next) {
426 next = win->next;
427 if (win->file->text == txt)
428 vis_window_close(win);
430 if (!has_windows(vis))
431 vis_exit(vis, EXIT_SUCCESS);
432 return true;
435 static bool cmd_qall(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
436 for (Win *next, *win = vis->windows; win; win = next) {
437 next = win->next;
438 if (!win->file->internal && (!text_modified(win->file->text) || cmd->flags == '!'))
439 vis_window_close(win);
441 if (!has_windows(vis)) {
442 vis_exit(vis, EXIT_SUCCESS);
443 return true;
444 } else {
445 info_unsaved_changes(vis);
446 return false;
450 static bool cmd_split(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
451 if (!win)
452 return false;
453 enum UiOption options = view_options_get(win->view);
454 windows_arrange(vis, UI_LAYOUT_HORIZONTAL);
455 if (!argv[1])
456 return vis_window_split(win);
457 bool ret = openfiles(vis, &argv[1]);
458 if (ret)
459 view_options_set(vis->win->view, options);
460 return ret;
463 static bool cmd_vsplit(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
464 if (!win)
465 return false;
466 enum UiOption options = view_options_get(win->view);
467 windows_arrange(vis, UI_LAYOUT_VERTICAL);
468 if (!argv[1])
469 return vis_window_split(win);
470 bool ret = openfiles(vis, &argv[1]);
471 if (ret)
472 view_options_set(vis->win->view, options);
473 return ret;
476 static bool cmd_new(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
477 windows_arrange(vis, UI_LAYOUT_HORIZONTAL);
478 return vis_window_new(vis, NULL);
481 static bool cmd_vnew(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
482 windows_arrange(vis, UI_LAYOUT_VERTICAL);
483 return vis_window_new(vis, NULL);
486 static bool cmd_wq(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
487 if (!win)
488 return false;
489 File *file = win->file;
490 bool unmodified = !file->is_stdin && !file->name && !text_modified(file->text);
491 if (unmodified || cmd_write(vis, win, cmd, argv, cur, range))
492 return cmd_quit(vis, win, cmd, argv, cur, range);
493 return false;
496 static bool cmd_earlier_later(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
497 if (!win)
498 return false;
499 Text *txt = win->file->text;
500 char *unit = "";
501 long count = 1;
502 size_t pos = EPOS;
503 if (argv[1]) {
504 errno = 0;
505 count = strtol(argv[1], &unit, 10);
506 if (errno || unit == argv[1] || count < 0) {
507 vis_info_show(vis, "Invalid number");
508 return false;
511 if (*unit) {
512 while (*unit && isspace((unsigned char)*unit))
513 unit++;
514 switch (*unit) {
515 case 'd': count *= 24; /* fall through */
516 case 'h': count *= 60; /* fall through */
517 case 'm': count *= 60; /* fall through */
518 case 's': break;
519 default:
520 vis_info_show(vis, "Unknown time specifier (use: s,m,h or d)");
521 return false;
524 if (argv[0][0] == 'e')
525 count = -count; /* earlier, move back in time */
527 pos = text_restore(txt, text_state(txt) + count);
531 if (!*unit) {
532 if (argv[0][0] == 'e')
533 pos = text_earlier(txt, count);
534 else
535 pos = text_later(txt, count);
538 time_t state = text_state(txt);
539 char buf[32];
540 strftime(buf, sizeof buf, "State from %H:%M", localtime(&state));
541 vis_info_show(vis, "%s", buf);
543 return pos != EPOS;
546 static bool print_keylayout(const char *key, void *value, void *data) {
547 return text_appendf(data, " %-18s\t%s\n", key[0] == ' ' ? "␣" : key, (char*)value);
550 static bool print_keybinding(const char *key, void *value, void *data) {
551 KeyBinding *binding = value;
552 const char *desc = binding->alias;
553 if (!desc && binding->action)
554 desc = binding->action->help;
555 return text_appendf(data, " %-18s\t%s\n", key[0] == ' ' ? "␣" : key, desc ? desc : "");
558 static void print_mode(Mode *mode, Text *txt) {
559 if (!map_empty(mode->bindings))
560 text_appendf(txt, "\n %s\n\n", mode->name);
561 map_iterate(mode->bindings, print_keybinding, txt);
564 static bool print_action(const char *key, void *value, void *data) {
565 KeyAction *action = value;
566 return text_appendf(data, " %-30s\t%s\n", key, action->help);
569 static bool print_cmd(const char *key, void *value, void *data) {
570 return text_appendf(data, " %s\n", key);
573 static void print_symbolic_keys(Vis *vis, Text *txt) {
574 static const int keys[] = {
575 TERMKEY_SYM_BACKSPACE,
576 TERMKEY_SYM_TAB,
577 TERMKEY_SYM_ENTER,
578 TERMKEY_SYM_ESCAPE,
579 //TERMKEY_SYM_SPACE,
580 TERMKEY_SYM_DEL,
581 TERMKEY_SYM_UP,
582 TERMKEY_SYM_DOWN,
583 TERMKEY_SYM_LEFT,
584 TERMKEY_SYM_RIGHT,
585 TERMKEY_SYM_BEGIN,
586 TERMKEY_SYM_FIND,
587 TERMKEY_SYM_INSERT,
588 TERMKEY_SYM_DELETE,
589 TERMKEY_SYM_SELECT,
590 TERMKEY_SYM_PAGEUP,
591 TERMKEY_SYM_PAGEDOWN,
592 TERMKEY_SYM_HOME,
593 TERMKEY_SYM_END,
594 TERMKEY_SYM_CANCEL,
595 TERMKEY_SYM_CLEAR,
596 TERMKEY_SYM_CLOSE,
597 TERMKEY_SYM_COMMAND,
598 TERMKEY_SYM_COPY,
599 TERMKEY_SYM_EXIT,
600 TERMKEY_SYM_HELP,
601 TERMKEY_SYM_MARK,
602 TERMKEY_SYM_MESSAGE,
603 TERMKEY_SYM_MOVE,
604 TERMKEY_SYM_OPEN,
605 TERMKEY_SYM_OPTIONS,
606 TERMKEY_SYM_PRINT,
607 TERMKEY_SYM_REDO,
608 TERMKEY_SYM_REFERENCE,
609 TERMKEY_SYM_REFRESH,
610 TERMKEY_SYM_REPLACE,
611 TERMKEY_SYM_RESTART,
612 TERMKEY_SYM_RESUME,
613 TERMKEY_SYM_SAVE,
614 TERMKEY_SYM_SUSPEND,
615 TERMKEY_SYM_UNDO,
616 TERMKEY_SYM_KP0,
617 TERMKEY_SYM_KP1,
618 TERMKEY_SYM_KP2,
619 TERMKEY_SYM_KP3,
620 TERMKEY_SYM_KP4,
621 TERMKEY_SYM_KP5,
622 TERMKEY_SYM_KP6,
623 TERMKEY_SYM_KP7,
624 TERMKEY_SYM_KP8,
625 TERMKEY_SYM_KP9,
626 TERMKEY_SYM_KPENTER,
627 TERMKEY_SYM_KPPLUS,
628 TERMKEY_SYM_KPMINUS,
629 TERMKEY_SYM_KPMULT,
630 TERMKEY_SYM_KPDIV,
631 TERMKEY_SYM_KPCOMMA,
632 TERMKEY_SYM_KPPERIOD,
633 TERMKEY_SYM_KPEQUALS,
636 TermKey *termkey = vis->ui->termkey_get(vis->ui);
637 text_appendf(txt, " ␣ (a literal \" \" space symbol must be used to refer to <Space>)\n");
638 for (size_t i = 0; i < LENGTH(keys); i++) {
639 text_appendf(txt, " <%s>\n", termkey_get_keyname(termkey, keys[i]));
643 static bool cmd_help(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
644 if (!vis_window_new(vis, NULL))
645 return false;
647 Text *txt = vis->win->file->text;
649 text_appendf(txt, "vis %s\n\n", VERSION);
651 text_appendf(txt, " Modes\n\n");
652 for (int i = 0; i < LENGTH(vis_modes); i++) {
653 Mode *mode = &vis_modes[i];
654 if (mode->help)
655 text_appendf(txt, " %-18s\t%s\n", mode->name, mode->help);
658 if (!map_empty(vis->keymap)) {
659 text_appendf(txt, "\n Layout specific mappings (affects all modes except INSERT/REPLACE)\n\n");
660 map_iterate(vis->keymap, print_keylayout, txt);
663 print_mode(&vis_modes[VIS_MODE_NORMAL], txt);
664 print_mode(&vis_modes[VIS_MODE_OPERATOR_PENDING], txt);
665 print_mode(&vis_modes[VIS_MODE_VISUAL], txt);
666 print_mode(&vis_modes[VIS_MODE_INSERT], txt);
668 text_appendf(txt, "\n :-Commands\n\n");
669 map_iterate(vis->cmds, print_cmd, txt);
671 text_appendf(txt, "\n Key binding actions\n\n");
672 map_iterate(vis->actions, print_action, txt);
674 text_appendf(txt, "\n Symbolic keys usable for key bindings "
675 "(prefix with C-, S-, and M- for Ctrl, Shift and Alt respectively)\n\n");
676 print_symbolic_keys(vis, txt);
678 const char *paths = vis_lua_paths_get(vis);
679 if (paths) {
680 char *copy = strdup(paths);
681 text_appendf(txt, "\n Lua paths used to load runtime files "
682 "(? will be replaced by filename):\n\n");
683 for (char *elem = copy, *next; elem; elem = next) {
684 if ((next = strstr(elem, ";")))
685 *next++ = '\0';
686 if (*elem)
687 text_appendf(txt, " %s\n", elem);
689 free (copy);
692 text_save(txt, NULL);
693 return true;
696 static enum VisMode str2vismode(const char *mode) {
697 const char *modes[] = {
698 [VIS_MODE_NORMAL] = "normal",
699 [VIS_MODE_OPERATOR_PENDING] = "operator-pending",
700 [VIS_MODE_VISUAL] = "visual",
701 [VIS_MODE_VISUAL_LINE] = "visual-line",
702 [VIS_MODE_INSERT] = "insert",
703 [VIS_MODE_REPLACE] = "replace",
706 for (size_t i = 0; i < LENGTH(modes); i++) {
707 if (mode && modes[i] && strcmp(mode, modes[i]) == 0)
708 return i;
710 return VIS_MODE_INVALID;
713 static bool cmd_langmap(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
714 const char *nonlatin = argv[1];
715 const char *latin = argv[2];
716 bool mapped = true;
718 if (!latin || !nonlatin) {
719 vis_info_show(vis, "usage: langmap <non-latin keys> <latin keys>");
720 return false;
723 while (*latin && *nonlatin) {
724 size_t i = 0, j = 0;
725 char latin_key[8], nonlatin_key[8];
726 do {
727 if (i < sizeof(latin_key)-1)
728 latin_key[i++] = *latin;
729 latin++;
730 } while (!ISUTF8(*latin));
731 do {
732 if (j < sizeof(nonlatin_key)-1)
733 nonlatin_key[j++] = *nonlatin;
734 nonlatin++;
735 } while (!ISUTF8(*nonlatin));
736 latin_key[i] = '\0';
737 nonlatin_key[j] = '\0';
738 mapped &= vis_keymap_add(vis, nonlatin_key, strdup(latin_key));
741 return mapped;
744 static bool cmd_map(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
745 KeyBinding *binding = NULL;
746 bool mapped = false;
747 bool local = strstr(argv[0], "-") != NULL;
748 enum VisMode mode = str2vismode(argv[1]);
750 if (local && !win)
751 return false;
753 if (mode == VIS_MODE_INVALID || !argv[2] || !argv[3]) {
754 vis_info_show(vis, "usage: map mode lhs rhs\n");
755 return false;
758 char *lhs = strdup(argv[2]);
759 char *rhs = strdup(argv[3]);
760 if (!lhs || !rhs || !(binding = calloc(1, sizeof *binding)))
761 goto err;
763 char *next = lhs;
764 while (cmd->flags == '!' && next) {
765 char tmp;
766 next = (char*)vis_keys_next(vis, next);
767 if (next) {
768 tmp = *next;
769 *next = '\0';
771 if (local)
772 vis_window_mode_unmap(win, mode, lhs);
773 else
774 vis_mode_unmap(vis, mode, lhs);
775 if (next)
776 *next = tmp;
779 binding->alias = rhs;
781 if (local)
782 mapped = vis_window_mode_map(win, mode, lhs, binding);
783 else
784 mapped = vis_mode_map(vis, mode, lhs, binding);
786 err:
787 free(lhs);
788 if (!mapped) {
789 free(rhs);
790 free(binding);
792 return mapped;
795 static bool cmd_unmap(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
796 bool local = strstr(argv[0], "-") != NULL;
797 enum VisMode mode = str2vismode(argv[1]);
798 const char *lhs = argv[2];
800 if (local && !win)
801 return false;
803 if (mode == VIS_MODE_INVALID || !lhs) {
804 vis_info_show(vis, "usage: unmap mode lhs\n");
805 return false;
808 if (local)
809 return vis_window_mode_unmap(win, mode, lhs);
810 else
811 return vis_mode_unmap(vis, mode, lhs);