build: include dvtm terminfo entries in standalone builds
[vis.git] / vis-cmds.c
blobf38a96f7627ffda63290cadc410480a83e0ff0c2
1 /* this file is included from sam.c */
3 #include <termkey.h>
4 #include "vis-lua.h"
6 // FIXME: avoid this redirection?
7 typedef struct {
8 CommandDef def;
9 CmdFunc *func;
10 void *data;
11 } CmdUser;
13 static void cmdfree(CmdUser *cmd) {
14 if (!cmd)
15 return;
16 free((char*)cmd->def.name);
17 free(VIS_HELP_USE((char*)cmd->def.help));
18 free(cmd);
21 bool vis_cmd_register(Vis *vis, const char *name, const char *help, void *data, CmdFunc *func) {
22 if (!name)
23 return false;
24 if (!vis->usercmds && !(vis->usercmds = map_new()))
25 return false;
26 CmdUser *cmd = calloc(1, sizeof *cmd);
27 if (!cmd)
28 return false;
29 if (!(cmd->def.name = strdup(name)))
30 goto err;
31 #if CONFIG_HELP
32 if (help && !(cmd->def.help = strdup(help)))
33 goto err;
34 #endif
35 cmd->def.flags = CMD_ARGV|CMD_FORCE|CMD_ONCE|CMD_ADDRESS_ALL;
36 cmd->def.func = cmd_user;
37 cmd->func = func;
38 cmd->data = data;
39 if (!map_put(vis->cmds, name, &cmd->def))
40 goto err;
41 if (!map_put(vis->usercmds, name, cmd)) {
42 map_delete(vis->cmds, name);
43 goto err;
45 return true;
46 err:
47 cmdfree(cmd);
48 return false;
51 bool vis_cmd_unregister(Vis *vis, const char *name) {
52 if (!name)
53 return true;
54 CmdUser *cmd = map_get(vis->usercmds, name);
55 if (!cmd)
56 return false;
57 if (!map_delete(vis->cmds, name))
58 return false;
59 if (!map_delete(vis->usercmds, name))
60 return false;
61 cmdfree(cmd);
62 return true;
65 static bool cmd_user(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
66 CmdUser *user = map_get(vis->usercmds, argv[0]);
67 return user && user->func(vis, win, user->data, cmd->flags == '!', argv, cur, range);
70 static void windows_arrange(Vis *vis, enum UiLayout layout) {
71 vis->ui->arrange(vis->ui, layout);
74 static void tabwidth_set(Vis *vis, int tabwidth) {
75 if (tabwidth < 1 || tabwidth > 8)
76 return;
77 for (Win *win = vis->windows; win; win = win->next)
78 view_tabwidth_set(win->view, tabwidth);
79 vis->tabwidth = tabwidth;
82 /* parse human-readable boolean value in s. If successful, store the result in
83 * outval and return true. Else return false and leave outval alone. */
84 static bool parse_bool(const char *s, bool *outval) {
85 for (const char **t = (const char*[]){"1", "true", "yes", "on", NULL}; *t; t++) {
86 if (!strcasecmp(s, *t)) {
87 *outval = true;
88 return true;
91 for (const char **f = (const char*[]){"0", "false", "no", "off", NULL}; *f; f++) {
92 if (!strcasecmp(s, *f)) {
93 *outval = false;
94 return true;
97 return false;
100 static bool cmd_set(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
102 if (!argv[1] || !argv[1][0] || argv[3]) {
103 vis_info_show(vis, "Expecting: set option [value]");
104 return false;
107 char name[256];
108 strncpy(name, argv[1], sizeof(name)-1);
109 char *lastchar = &name[strlen(name)-1];
110 bool toggle = (*lastchar == '!');
111 if (toggle)
112 *lastchar = '\0';
114 OptionDef *opt = map_closest(vis->options, name);
115 if (!opt) {
116 vis_info_show(vis, "Unknown option: `%s'", name);
117 return false;
120 if (!win && (opt->flags & OPTION_FLAG_WINDOW)) {
121 vis_info_show(vis, "Need active window for `:set %s'", name);
122 return false;
125 if (toggle) {
126 if (opt->type != OPTION_TYPE_BOOL) {
127 vis_info_show(vis, "Only boolean options can be toggled");
128 return false;
130 if (argv[2]) {
131 vis_info_show(vis, "Can not specify option value when toggling");
132 return false;
136 Arg arg;
137 switch (opt->type) {
138 case OPTION_TYPE_STRING:
139 if (!(opt->flags & OPTION_FLAG_OPTIONAL) && !argv[2]) {
140 vis_info_show(vis, "Expecting string option value");
141 return false;
143 arg.s = argv[2];
144 break;
145 case OPTION_TYPE_BOOL:
146 if (!argv[2]) {
147 arg.b = !toggle;
148 } else if (!parse_bool(argv[2], &arg.b)) {
149 vis_info_show(vis, "Expecting boolean option value not: `%s'", argv[2]);
150 return false;
152 break;
153 case OPTION_TYPE_NUMBER:
154 if (!argv[2]) {
155 vis_info_show(vis, "Expecting number");
156 return false;
158 char *ep;
159 errno = 0;
160 long lval = strtol(argv[2], &ep, 10);
161 if (argv[2][0] == '\0' || *ep != '\0') {
162 vis_info_show(vis, "Invalid number");
163 return false;
166 if ((errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN)) ||
167 (lval > INT_MAX || lval < INT_MIN)) {
168 vis_info_show(vis, "Number overflow");
169 return false;
172 if (lval < 0) {
173 vis_info_show(vis, "Expecting positive number");
174 return false;
176 arg.i = lval;
177 break;
178 default:
179 return false;
182 size_t opt_index = opt - options;
183 switch (opt_index) {
184 case OPTION_SHELL:
186 char *shell = strdup(arg.s);
187 if (!shell) {
188 vis_info_show(vis, "Failed to change shell");
189 return false;
191 free(vis->shell);
192 vis->shell = shell;
193 break;
195 case OPTION_ESCDELAY:
197 TermKey *termkey = vis->ui->termkey_get(vis->ui);
198 termkey_set_waittime(termkey, arg.i);
199 break;
201 case OPTION_EXPANDTAB:
202 vis->expandtab = toggle ? !vis->expandtab : arg.b;
203 break;
204 case OPTION_AUTOINDENT:
205 vis->autoindent = toggle ? !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 = vis_window_syntax_get(win);
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 vis_window_syntax_set(win, NULL);
222 if (!vis_window_syntax_set(win, argv[2])) {
223 vis_info_show(vis, "Unknown syntax definition: `%s'", argv[2]);
224 return false;
226 break;
227 case OPTION_SHOW_SPACES:
228 case OPTION_SHOW_TABS:
229 case OPTION_SHOW_NEWLINES:
231 const int values[] = {
232 [OPTION_SHOW_SPACES] = UI_OPTION_SYMBOL_SPACE,
233 [OPTION_SHOW_TABS] = UI_OPTION_SYMBOL_TAB|UI_OPTION_SYMBOL_TAB_FILL,
234 [OPTION_SHOW_NEWLINES] = UI_OPTION_SYMBOL_EOL,
236 int flags = view_options_get(win->view);
237 if (arg.b || (toggle && !(flags & values[opt_index])))
238 flags |= values[opt_index];
239 else
240 flags &= ~values[opt_index];
241 view_options_set(win->view, flags);
242 break;
244 case OPTION_NUMBER: {
245 enum UiOption opt = view_options_get(win->view);
246 if (arg.b || (toggle && !(opt & UI_OPTION_LINE_NUMBERS_ABSOLUTE))) {
247 opt &= ~UI_OPTION_LINE_NUMBERS_RELATIVE;
248 opt |= UI_OPTION_LINE_NUMBERS_ABSOLUTE;
249 } else {
250 opt &= ~UI_OPTION_LINE_NUMBERS_ABSOLUTE;
252 view_options_set(win->view, opt);
253 break;
255 case OPTION_NUMBER_RELATIVE: {
256 enum UiOption opt = view_options_get(win->view);
257 if (arg.b || (toggle && !(opt & UI_OPTION_LINE_NUMBERS_RELATIVE))) {
258 opt &= ~UI_OPTION_LINE_NUMBERS_ABSOLUTE;
259 opt |= UI_OPTION_LINE_NUMBERS_RELATIVE;
260 } else {
261 opt &= ~UI_OPTION_LINE_NUMBERS_RELATIVE;
263 view_options_set(win->view, opt);
264 break;
266 case OPTION_CURSOR_LINE: {
267 enum UiOption opt = view_options_get(win->view);
268 if (arg.b || (toggle && !(opt & UI_OPTION_CURSOR_LINE)))
269 opt |= UI_OPTION_CURSOR_LINE;
270 else
271 opt &= ~UI_OPTION_CURSOR_LINE;
272 view_options_set(win->view, opt);
273 break;
275 case OPTION_THEME:
276 if (!vis_theme_load(vis, arg.s)) {
277 vis_info_show(vis, "Failed to load theme: `%s'", arg.s);
278 return false;
280 break;
281 case OPTION_COLOR_COLUMN:
282 view_colorcolumn_set(win->view, arg.i);
283 break;
284 case OPTION_HORIZON:
285 win->horizon = arg.i;
286 break;
287 case OPTION_SAVE_METHOD:
288 if (strcmp("auto", arg.s) == 0) {
289 win->file->save_method = TEXT_SAVE_AUTO;
290 } else if (strcmp("atomic", arg.s) == 0) {
291 win->file->save_method = TEXT_SAVE_ATOMIC;
292 } else if (strcmp("inplace", arg.s) == 0) {
293 win->file->save_method = TEXT_SAVE_INPLACE;
294 } else {
295 vis_info_show(vis, "Invalid save method `%s', expected "
296 "'auto', 'atomic' or 'inplace'", arg.s);
297 return false;
299 break;
300 case OPTION_CHANGE_256COLORS:
301 vis->change_colors = toggle ? !vis->change_colors : arg.b;
302 break;
303 default:
304 return false;
307 return true;
310 static bool is_file_pattern(const char *pattern) {
311 if (!pattern)
312 return false;
313 struct stat meta;
314 if (stat(pattern, &meta) == 0 && S_ISDIR(meta.st_mode))
315 return true;
316 for (char special[] = "*?[{$~", *s = special; *s; s++) {
317 if (strchr(pattern, *s))
318 return true;
320 return false;
323 static const char *file_open_dialog(Vis *vis, const char *pattern) {
324 static char name[PATH_MAX];
325 name[0] = '\0';
327 if (!is_file_pattern(pattern))
328 return pattern;
330 Buffer bufcmd, bufout, buferr;
331 buffer_init(&bufcmd);
332 buffer_init(&bufout);
333 buffer_init(&buferr);
335 if (!buffer_put0(&bufcmd, VIS_OPEN " ") || !buffer_append0(&bufcmd, pattern ? pattern : ""))
336 return NULL;
338 Filerange empty = text_range_new(0,0);
339 int status = vis_pipe(vis, vis->win->file, &empty,
340 (const char*[]){ buffer_content0(&bufcmd), NULL },
341 &bufout, read_buffer, &buferr, read_buffer);
343 if (status == 0)
344 strncpy(name, buffer_content0(&bufout), sizeof(name)-1);
345 else
346 vis_info_show(vis, "Command failed %s", buffer_content0(&buferr));
348 buffer_release(&bufcmd);
349 buffer_release(&bufout);
350 buffer_release(&buferr);
352 for (char *end = name+strlen(name)-1; end >= name && isspace((unsigned char)*end); end--)
353 *end = '\0';
355 return name[0] ? name : NULL;
358 static bool openfiles(Vis *vis, const char **files) {
359 for (; *files; files++) {
360 const char *file = file_open_dialog(vis, *files);
361 if (!file)
362 return false;
363 errno = 0;
364 if (!vis_window_new(vis, file)) {
365 vis_info_show(vis, "Could not open `%s' %s", file,
366 errno ? strerror(errno) : "");
367 return false;
370 return true;
373 static bool cmd_open(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
374 if (!argv[1])
375 return vis_window_new(vis, NULL);
376 return openfiles(vis, &argv[1]);
379 static void info_unsaved_changes(Vis *vis) {
380 vis_info_show(vis, "No write since last change (add ! to override)");
383 static bool cmd_edit(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
384 if (argv[2]) {
385 vis_info_show(vis, "Only 1 filename allowed");
386 return false;
388 Win *oldwin = win;
389 if (!oldwin)
390 return false;
391 if (cmd->flags != '!' && !vis_window_closable(oldwin)) {
392 info_unsaved_changes(vis);
393 return false;
395 if (!argv[1]) {
396 if (oldwin->file->refcount > 1) {
397 vis_info_show(vis, "Can not reload file being opened multiple times");
398 return false;
400 return vis_window_reload(oldwin);
402 if (!openfiles(vis, &argv[1]))
403 return false;
404 if (vis->win != oldwin) {
405 Win *newwin = vis->win;
406 vis_window_swap(oldwin, newwin);
407 vis_window_close(oldwin);
408 vis_window_focus(newwin);
410 return vis->win != oldwin;
413 static bool cmd_read(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
414 bool ret = false;
415 const size_t first_file = 3;
416 const char *args[MAX_ARGV] = { argv[0], "cat", "--" };
417 const char **name = argv[1] ? &argv[1] : (const char*[]){ ".", NULL };
418 for (size_t i = first_file; *name && i < LENGTH(args)-1; name++, i++) {
419 const char *file = file_open_dialog(vis, *name);
420 if (!file || !(args[i] = strdup(file)))
421 goto err;
423 args[LENGTH(args)-1] = NULL;
424 ret = cmd_pipein(vis, win, cmd, args, cur, range);
425 err:
426 for (size_t i = first_file; i < LENGTH(args); i++)
427 free((char*)args[i]);
428 return ret;
431 static bool has_windows(Vis *vis) {
432 for (Win *win = vis->windows; win; win = win->next) {
433 if (!win->file->internal)
434 return true;
436 return false;
439 static bool cmd_quit(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
440 if (cmd->flags != '!' && !vis_window_closable(win)) {
441 info_unsaved_changes(vis);
442 return false;
444 vis_window_close(win);
445 if (!has_windows(vis))
446 vis_exit(vis, EXIT_SUCCESS);
447 return true;
450 static bool cmd_qall(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
451 for (Win *next, *win = vis->windows; win; win = next) {
452 next = win->next;
453 if (!win->file->internal && (!text_modified(win->file->text) || cmd->flags == '!'))
454 vis_window_close(win);
456 if (!has_windows(vis)) {
457 vis_exit(vis, EXIT_SUCCESS);
458 return true;
459 } else {
460 info_unsaved_changes(vis);
461 return false;
465 static bool cmd_split(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
466 if (!win)
467 return false;
468 enum UiOption options = view_options_get(win->view);
469 windows_arrange(vis, UI_LAYOUT_HORIZONTAL);
470 if (!argv[1])
471 return vis_window_split(win);
472 bool ret = openfiles(vis, &argv[1]);
473 if (ret)
474 view_options_set(vis->win->view, options);
475 return ret;
478 static bool cmd_vsplit(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
479 if (!win)
480 return false;
481 enum UiOption options = view_options_get(win->view);
482 windows_arrange(vis, UI_LAYOUT_VERTICAL);
483 if (!argv[1])
484 return vis_window_split(win);
485 bool ret = openfiles(vis, &argv[1]);
486 if (ret)
487 view_options_set(vis->win->view, options);
488 return ret;
491 static bool cmd_new(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
492 windows_arrange(vis, UI_LAYOUT_HORIZONTAL);
493 return vis_window_new(vis, NULL);
496 static bool cmd_vnew(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
497 windows_arrange(vis, UI_LAYOUT_VERTICAL);
498 return vis_window_new(vis, NULL);
501 static bool cmd_wq(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
502 if (!win)
503 return false;
504 File *file = win->file;
505 bool unmodified = file->fd == -1 && !file->name && !text_modified(file->text);
506 if (unmodified || cmd_write(vis, win, cmd, argv, cur, range))
507 return cmd_quit(vis, win, cmd, argv, cur, range);
508 return false;
511 static bool cmd_earlier_later(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
512 if (!win)
513 return false;
514 Text *txt = win->file->text;
515 char *unit = "";
516 long count = 1;
517 size_t pos = EPOS;
518 if (argv[1]) {
519 errno = 0;
520 count = strtol(argv[1], &unit, 10);
521 if (errno || unit == argv[1] || count < 0) {
522 vis_info_show(vis, "Invalid number");
523 return false;
526 if (*unit) {
527 while (*unit && isspace((unsigned char)*unit))
528 unit++;
529 switch (*unit) {
530 case 'd': count *= 24; /* fall through */
531 case 'h': count *= 60; /* fall through */
532 case 'm': count *= 60; /* fall through */
533 case 's': break;
534 default:
535 vis_info_show(vis, "Unknown time specifier (use: s,m,h or d)");
536 return false;
539 if (argv[0][0] == 'e')
540 count = -count; /* earlier, move back in time */
542 pos = text_restore(txt, text_state(txt) + count);
546 if (!*unit) {
547 if (argv[0][0] == 'e')
548 pos = text_earlier(txt, count);
549 else
550 pos = text_later(txt, count);
553 time_t state = text_state(txt);
554 char buf[32];
555 strftime(buf, sizeof buf, "State from %H:%M", localtime(&state));
556 vis_info_show(vis, "%s", buf);
558 return pos != EPOS;
561 static bool print_keylayout(const char *key, void *value, void *data) {
562 return text_appendf(data, " %-18s\t%s\n", key[0] == ' ' ? "␣" : key, (char*)value);
565 static bool print_keybinding(const char *key, void *value, void *data) {
566 KeyBinding *binding = value;
567 const char *desc = binding->alias;
568 if (!desc && binding->action)
569 desc = VIS_HELP_USE(binding->action->help);
570 return text_appendf(data, " %-18s\t%s\n", key[0] == ' ' ? "␣" : key, desc ? desc : "");
573 static void print_mode(Mode *mode, Text *txt) {
574 if (!map_empty(mode->bindings))
575 text_appendf(txt, "\n %s\n\n", mode->name);
576 map_iterate(mode->bindings, print_keybinding, txt);
579 static bool print_action(const char *key, void *value, void *data) {
580 const char *help = VIS_HELP_USE(((KeyAction*)value)->help);
581 return text_appendf(data, " %-30s\t%s\n", key, help ? help : "");
584 static bool print_cmd(const char *key, void *value, void *data) {
585 CommandDef *cmd = value;
586 const char *help = VIS_HELP_USE(cmd->help);
587 char usage[256];
588 snprintf(usage, sizeof usage, "%s%s%s%s%s%s%s",
589 cmd->name,
590 (cmd->flags & CMD_FORCE) ? "[!]" : "",
591 (cmd->flags & CMD_TEXT) ? "/text/" : "",
592 (cmd->flags & CMD_REGEX) ? "/regexp/" : "",
593 (cmd->flags & CMD_CMD) ? " command" : "",
594 (cmd->flags & CMD_SHELL) ? (!strcmp(cmd->name, "s") ? "/regexp/text/" : " shell-command") : "",
595 (cmd->flags & CMD_ARGV) ? " [args...]" : "");
596 return text_appendf(data, " %-30s %s\n", usage, help ? help : "");
599 static void print_symbolic_keys(Vis *vis, Text *txt) {
600 static const int keys[] = {
601 TERMKEY_SYM_BACKSPACE,
602 TERMKEY_SYM_TAB,
603 TERMKEY_SYM_ENTER,
604 TERMKEY_SYM_ESCAPE,
605 //TERMKEY_SYM_SPACE,
606 TERMKEY_SYM_DEL,
607 TERMKEY_SYM_UP,
608 TERMKEY_SYM_DOWN,
609 TERMKEY_SYM_LEFT,
610 TERMKEY_SYM_RIGHT,
611 TERMKEY_SYM_BEGIN,
612 TERMKEY_SYM_FIND,
613 TERMKEY_SYM_INSERT,
614 TERMKEY_SYM_DELETE,
615 TERMKEY_SYM_SELECT,
616 TERMKEY_SYM_PAGEUP,
617 TERMKEY_SYM_PAGEDOWN,
618 TERMKEY_SYM_HOME,
619 TERMKEY_SYM_END,
620 TERMKEY_SYM_CANCEL,
621 TERMKEY_SYM_CLEAR,
622 TERMKEY_SYM_CLOSE,
623 TERMKEY_SYM_COMMAND,
624 TERMKEY_SYM_COPY,
625 TERMKEY_SYM_EXIT,
626 TERMKEY_SYM_HELP,
627 TERMKEY_SYM_MARK,
628 TERMKEY_SYM_MESSAGE,
629 TERMKEY_SYM_MOVE,
630 TERMKEY_SYM_OPEN,
631 TERMKEY_SYM_OPTIONS,
632 TERMKEY_SYM_PRINT,
633 TERMKEY_SYM_REDO,
634 TERMKEY_SYM_REFERENCE,
635 TERMKEY_SYM_REFRESH,
636 TERMKEY_SYM_REPLACE,
637 TERMKEY_SYM_RESTART,
638 TERMKEY_SYM_RESUME,
639 TERMKEY_SYM_SAVE,
640 TERMKEY_SYM_SUSPEND,
641 TERMKEY_SYM_UNDO,
642 TERMKEY_SYM_KP0,
643 TERMKEY_SYM_KP1,
644 TERMKEY_SYM_KP2,
645 TERMKEY_SYM_KP3,
646 TERMKEY_SYM_KP4,
647 TERMKEY_SYM_KP5,
648 TERMKEY_SYM_KP6,
649 TERMKEY_SYM_KP7,
650 TERMKEY_SYM_KP8,
651 TERMKEY_SYM_KP9,
652 TERMKEY_SYM_KPENTER,
653 TERMKEY_SYM_KPPLUS,
654 TERMKEY_SYM_KPMINUS,
655 TERMKEY_SYM_KPMULT,
656 TERMKEY_SYM_KPDIV,
657 TERMKEY_SYM_KPCOMMA,
658 TERMKEY_SYM_KPPERIOD,
659 TERMKEY_SYM_KPEQUALS,
662 TermKey *termkey = vis->ui->termkey_get(vis->ui);
663 text_appendf(txt, " ␣ (a literal \" \" space symbol must be used to refer to <Space>)\n");
664 for (size_t i = 0; i < LENGTH(keys); i++) {
665 text_appendf(txt, " <%s>\n", termkey_get_keyname(termkey, keys[i]));
669 static bool cmd_help(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
670 if (!vis_window_new(vis, NULL))
671 return false;
673 Text *txt = vis->win->file->text;
675 text_appendf(txt, "vis %s (PID: %ld)\n\n", VERSION, (long)getpid());
677 text_appendf(txt, " Modes\n\n");
678 for (int i = 0; i < LENGTH(vis_modes); i++) {
679 Mode *mode = &vis_modes[i];
680 if (mode->help)
681 text_appendf(txt, " %-18s\t%s\n", mode->name, mode->help);
684 if (!map_empty(vis->keymap)) {
685 text_appendf(txt, "\n Layout specific mappings (affects all modes except INSERT/REPLACE)\n\n");
686 map_iterate(vis->keymap, print_keylayout, txt);
689 print_mode(&vis_modes[VIS_MODE_NORMAL], txt);
690 print_mode(&vis_modes[VIS_MODE_OPERATOR_PENDING], txt);
691 print_mode(&vis_modes[VIS_MODE_VISUAL], txt);
692 print_mode(&vis_modes[VIS_MODE_INSERT], txt);
694 text_appendf(txt, "\n :-Commands\n\n");
695 map_iterate(vis->cmds, print_cmd, txt);
697 text_appendf(txt, "\n Marks\n\n");
698 text_appendf(txt, " a-z General purpose marks\n");
699 for (size_t i = 0; i < LENGTH(vis_marks); i++) {
700 const char *help = VIS_HELP_USE(vis_marks[i].help);
701 text_appendf(txt, " %c %s\n", vis_marks[i].name, help ? help : "");
704 text_appendf(txt, "\n Registers\n\n");
705 text_appendf(txt, " a-z General purpose registers\n");
706 text_appendf(txt, " A-Z Append to corresponding general purpose register\n");
707 for (size_t i = 0; i < LENGTH(vis_registers); i++) {
708 const char *help = VIS_HELP_USE(vis_registers[i].help);
709 text_appendf(txt, " %c %s\n", vis_registers[i].name, help ? help : "");
712 text_appendf(txt, "\n :set command options\n\n");
713 for (int i = 0; i < LENGTH(options); i++) {
714 char names[256];
715 const OptionDef *opt = &options[i];
716 const char *help = VIS_HELP_USE(opt->help);
717 snprintf(names, sizeof names, "%s%s%s%s%s",
718 opt->names[0],
719 opt->names[1] ? "|" : "",
720 opt->names[1] ? opt->names[1] : "",
721 opt->type == OPTION_TYPE_BOOL ? " on|off" : "",
722 opt->type == OPTION_TYPE_NUMBER ? " nn" : "");
723 text_appendf(txt, " %-30s %s\n", names, help ? help : "");
726 text_appendf(txt, "\n Key binding actions\n\n");
727 map_iterate(vis->actions, print_action, txt);
729 text_appendf(txt, "\n Symbolic keys usable for key bindings "
730 "(prefix with C-, S-, and M- for Ctrl, Shift and Alt respectively)\n\n");
731 print_symbolic_keys(vis, txt);
733 char *paths[] = { NULL, NULL };
734 char *paths_description[] = {
735 "Lua paths used to load runtime files (? will be replaced by filename):",
736 "Lua paths used to load C libraries (? will be replaced by filename):",
739 if (vis_lua_paths_get(vis, &paths[0], &paths[1])) {
740 for (size_t i = 0; i < LENGTH(paths); i++) {
741 text_appendf(txt, "\n %s\n\n", paths_description[i]);
742 for (char *elem = paths[i], *next; elem; elem = next) {
743 if ((next = strstr(elem, ";")))
744 *next++ = '\0';
745 if (*elem)
746 text_appendf(txt, " %s\n", elem);
748 free(paths[i]);
752 text_appendf(txt, "\n Compile time configuration\n\n");
754 const struct {
755 const char *name;
756 bool enabled;
757 } configs[] = {
758 { "Curses support: ", CONFIG_CURSES },
759 { "Lua support: ", CONFIG_LUA },
760 { "Lua LPeg statically built-in: ", CONFIG_LPEG },
761 { "TRE based regex support: ", CONFIG_TRE },
762 { "POSIX ACL support: ", CONFIG_ACL },
763 { "SELinux support: ", CONFIG_SELINUX },
766 for (size_t i = 0; i < LENGTH(configs); i++)
767 text_appendf(txt, " %-32s\t%s\n", configs[i].name, configs[i].enabled ? "yes" : "no");
769 text_save(txt, NULL);
770 view_cursor_to(vis->win->view, 0);
772 if (argv[1])
773 vis_motion(vis, VIS_MOVE_SEARCH_FORWARD, argv[1]);
774 return true;
777 static bool cmd_langmap(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
778 const char *nonlatin = argv[1];
779 const char *latin = argv[2];
780 bool mapped = true;
782 if (!latin || !nonlatin) {
783 vis_info_show(vis, "usage: langmap <non-latin keys> <latin keys>");
784 return false;
787 while (*latin && *nonlatin) {
788 size_t i = 0, j = 0;
789 char latin_key[8], nonlatin_key[8];
790 do {
791 if (i < sizeof(latin_key)-1)
792 latin_key[i++] = *latin;
793 latin++;
794 } while (!ISUTF8(*latin));
795 do {
796 if (j < sizeof(nonlatin_key)-1)
797 nonlatin_key[j++] = *nonlatin;
798 nonlatin++;
799 } while (!ISUTF8(*nonlatin));
800 latin_key[i] = '\0';
801 nonlatin_key[j] = '\0';
802 mapped &= vis_keymap_add(vis, nonlatin_key, strdup(latin_key));
805 return mapped;
808 static bool cmd_map(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
809 bool mapped = false;
810 bool local = strstr(argv[0], "-") != NULL;
811 enum VisMode mode = vis_mode_from(vis, argv[1]);
813 if (local && !win) {
814 vis_info_show(vis, "Invalid window for :%s", argv[0]);
815 return false;
818 if (mode == VIS_MODE_INVALID || !argv[2] || !argv[3]) {
819 vis_info_show(vis, "usage: %s mode lhs rhs", argv[0]);
820 return false;
823 const char *lhs = argv[2];
824 KeyBinding *binding = vis_binding_new(vis);
825 if (!binding || !(binding->alias = strdup(argv[3])))
826 goto err;
828 if (local)
829 mapped = vis_window_mode_map(win, mode, cmd->flags == '!', lhs, binding);
830 else
831 mapped = vis_mode_map(vis, mode, cmd->flags == '!', lhs, binding);
833 err:
834 if (!mapped) {
835 vis_info_show(vis, "Failed to map `%s' in %s mode%s", lhs, argv[1],
836 cmd->flags != '!' ? ", mapping already exists, "
837 "override with `!'" : "");
838 vis_binding_free(vis, binding);
840 return mapped;
843 static bool cmd_unmap(Vis *vis, Win *win, Command *cmd, const char *argv[], Cursor *cur, Filerange *range) {
844 bool unmapped = false;
845 bool local = strstr(argv[0], "-") != NULL;
846 enum VisMode mode = vis_mode_from(vis, argv[1]);
847 const char *lhs = argv[2];
849 if (local && !win) {
850 vis_info_show(vis, "Invalid window for :%s", argv[0]);
851 return false;
854 if (mode == VIS_MODE_INVALID || !lhs) {
855 vis_info_show(vis, "usage: %s mode lhs", argv[0]);
856 return false;
859 if (local)
860 unmapped = vis_window_mode_unmap(win, mode, lhs);
861 else
862 unmapped = vis_mode_unmap(vis, mode, lhs);
863 if (!unmapped)
864 vis_info_show(vis, "Failed to unmap `%s' in %s mode", lhs, argv[1]);
865 return unmapped;