Merge branch 'master' of https://github.com/KaneRoot/vis
[vis.git] / vis-cmds.c
blobf5221d14202af3978de75cf7f50ee0d5b805f045
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 VisCommandFunction *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, VisCommandFunction *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 void option_free(OptionDef *opt) {
66 if (!opt)
67 return;
68 for (size_t i = 0; i < LENGTH(options); i++) {
69 if (opt == &options[i])
70 return;
73 for (const char **name = opt->names; *name; name++)
74 free((char*)*name);
75 free(VIS_HELP_USE((char*)opt->help));
76 free(opt);
79 bool vis_option_register(Vis *vis, const char *names[], enum VisOption flags,
80 VisOptionFunction *func, void *context, const char *help) {
82 if (!names || !names[0])
83 return false;
85 for (const char **name = names; *name; name++) {
86 if (map_get(vis->options, *name))
87 return false;
89 OptionDef *opt = calloc(1, sizeof *opt);
90 if (!opt)
91 return false;
92 for (size_t i = 0; i < LENGTH(opt->names)-1 && names[i]; i++) {
93 if (!(opt->names[i] = strdup(names[i])))
94 goto err;
96 opt->flags = flags;
97 opt->func = func;
98 opt->context = context;
99 #if CONFIG_HELP
100 if (help && !(opt->help = strdup(help)))
101 goto err;
102 #endif
103 for (const char **name = names; *name; name++)
104 map_put(vis->options, *name, opt);
105 return true;
106 err:
107 option_free(opt);
108 return false;
111 bool vis_option_unregister(Vis *vis, const char *name) {
112 OptionDef *opt = map_get(vis->options, name);
113 if (!opt)
114 return false;
115 for (const char **alias = opt->names; *alias; alias++) {
116 if (!map_delete(vis->options, *alias))
117 return false;
119 option_free(opt);
120 return true;
123 static bool cmd_user(Vis *vis, Win *win, Command *cmd, const char *argv[], Selection *sel, Filerange *range) {
124 CmdUser *user = map_get(vis->usercmds, argv[0]);
125 return user && user->func(vis, win, user->data, cmd->flags == '!', argv, sel, range);
128 static void windows_arrange(Vis *vis, enum UiLayout layout) {
129 vis->ui->arrange(vis->ui, layout);
132 static void tabwidth_set(Vis *vis, int tabwidth) {
133 if (tabwidth < 1 || tabwidth > 8)
134 return;
135 for (Win *win = vis->windows; win; win = win->next)
136 view_tabwidth_set(win->view, tabwidth);
137 vis->tabwidth = tabwidth;
140 /* parse human-readable boolean value in s. If successful, store the result in
141 * outval and return true. Else return false and leave outval alone. */
142 static bool parse_bool(const char *s, bool *outval) {
143 for (const char **t = (const char*[]){"1", "true", "yes", "on", NULL}; *t; t++) {
144 if (!strcasecmp(s, *t)) {
145 *outval = true;
146 return true;
149 for (const char **f = (const char*[]){"0", "false", "no", "off", NULL}; *f; f++) {
150 if (!strcasecmp(s, *f)) {
151 *outval = false;
152 return true;
155 return false;
158 static bool cmd_set(Vis *vis, Win *win, Command *cmd, const char *argv[], Selection *sel, Filerange *range) {
160 if (!argv[1] || !argv[1][0] || argv[3]) {
161 vis_info_show(vis, "Expecting: set option [value]");
162 return false;
165 char name[256];
166 strncpy(name, argv[1], sizeof(name)-1);
167 char *lastchar = &name[strlen(name)-1];
168 bool toggle = (*lastchar == '!');
169 if (toggle)
170 *lastchar = '\0';
172 OptionDef *opt = map_closest(vis->options, name);
173 if (!opt) {
174 vis_info_show(vis, "Unknown option: `%s'", name);
175 return false;
178 if (!win && (opt->flags & VIS_OPTION_NEED_WINDOW)) {
179 vis_info_show(vis, "Need active window for `:set %s'", name);
180 return false;
183 if (toggle) {
184 if (!(opt->flags & VIS_OPTION_TYPE_BOOL)) {
185 vis_info_show(vis, "Only boolean options can be toggled");
186 return false;
188 if (argv[2]) {
189 vis_info_show(vis, "Can not specify option value when toggling");
190 return false;
194 Arg arg;
195 if (opt->flags & VIS_OPTION_TYPE_STRING) {
196 if (!(opt->flags & VIS_OPTION_VALUE_OPTIONAL) && !argv[2]) {
197 vis_info_show(vis, "Expecting string option value");
198 return false;
200 arg.s = argv[2];
201 } else if (opt->flags & VIS_OPTION_TYPE_BOOL) {
202 if (!argv[2]) {
203 arg.b = !toggle;
204 } else if (!parse_bool(argv[2], &arg.b)) {
205 vis_info_show(vis, "Expecting boolean option value not: `%s'", argv[2]);
206 return false;
208 } else if (opt->flags & VIS_OPTION_TYPE_NUMBER) {
209 if (!argv[2]) {
210 vis_info_show(vis, "Expecting number");
211 return false;
213 char *ep;
214 errno = 0;
215 long lval = strtol(argv[2], &ep, 10);
216 if (argv[2][0] == '\0' || *ep != '\0') {
217 vis_info_show(vis, "Invalid number");
218 return false;
221 if ((errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN)) ||
222 (lval > INT_MAX || lval < INT_MIN)) {
223 vis_info_show(vis, "Number overflow");
224 return false;
227 if (lval < 0) {
228 vis_info_show(vis, "Expecting positive number");
229 return false;
231 arg.i = lval;
232 } else {
233 return false;
236 size_t opt_index = 0;
237 for (; opt_index < LENGTH(options); opt_index++) {
238 if (opt == &options[opt_index])
239 break;
242 switch (opt_index) {
243 case OPTION_SHELL:
245 char *shell = strdup(arg.s);
246 if (!shell) {
247 vis_info_show(vis, "Failed to change shell");
248 return false;
250 free(vis->shell);
251 vis->shell = shell;
252 break;
254 case OPTION_ESCDELAY:
256 TermKey *termkey = vis->ui->termkey_get(vis->ui);
257 termkey_set_waittime(termkey, arg.i);
258 break;
260 case OPTION_EXPANDTAB:
261 vis->expandtab = toggle ? !vis->expandtab : arg.b;
262 break;
263 case OPTION_AUTOINDENT:
264 vis->autoindent = toggle ? !vis->autoindent : arg.b;
265 break;
266 case OPTION_TABWIDTH:
267 tabwidth_set(vis, arg.i);
268 break;
269 case OPTION_SHOW_SPACES:
270 case OPTION_SHOW_TABS:
271 case OPTION_SHOW_NEWLINES:
272 case OPTION_SHOW_EOF:
274 const int values[] = {
275 [OPTION_SHOW_SPACES] = UI_OPTION_SYMBOL_SPACE,
276 [OPTION_SHOW_TABS] = UI_OPTION_SYMBOL_TAB|UI_OPTION_SYMBOL_TAB_FILL,
277 [OPTION_SHOW_NEWLINES] = UI_OPTION_SYMBOL_EOL,
278 [OPTION_SHOW_EOF] = UI_OPTION_SYMBOL_EOF,
280 int flags = view_options_get(win->view);
281 if (arg.b || (toggle && !(flags & values[opt_index])))
282 flags |= values[opt_index];
283 else
284 flags &= ~values[opt_index];
285 view_options_set(win->view, flags);
286 break;
288 case OPTION_NUMBER: {
289 enum UiOption opt = view_options_get(win->view);
290 if (arg.b || (toggle && !(opt & UI_OPTION_LINE_NUMBERS_ABSOLUTE))) {
291 opt &= ~UI_OPTION_LINE_NUMBERS_RELATIVE;
292 opt |= UI_OPTION_LINE_NUMBERS_ABSOLUTE;
293 } else {
294 opt &= ~UI_OPTION_LINE_NUMBERS_ABSOLUTE;
296 view_options_set(win->view, opt);
297 break;
299 case OPTION_NUMBER_RELATIVE: {
300 enum UiOption opt = view_options_get(win->view);
301 if (arg.b || (toggle && !(opt & UI_OPTION_LINE_NUMBERS_RELATIVE))) {
302 opt &= ~UI_OPTION_LINE_NUMBERS_ABSOLUTE;
303 opt |= UI_OPTION_LINE_NUMBERS_RELATIVE;
304 } else {
305 opt &= ~UI_OPTION_LINE_NUMBERS_RELATIVE;
307 view_options_set(win->view, opt);
308 break;
310 case OPTION_CURSOR_LINE: {
311 enum UiOption opt = view_options_get(win->view);
312 if (arg.b || (toggle && !(opt & UI_OPTION_CURSOR_LINE)))
313 opt |= UI_OPTION_CURSOR_LINE;
314 else
315 opt &= ~UI_OPTION_CURSOR_LINE;
316 view_options_set(win->view, opt);
317 break;
319 case OPTION_COLOR_COLUMN:
320 view_colorcolumn_set(win->view, arg.i);
321 break;
322 case OPTION_SAVE_METHOD:
323 if (strcmp("auto", arg.s) == 0) {
324 win->file->save_method = TEXT_SAVE_AUTO;
325 } else if (strcmp("atomic", arg.s) == 0) {
326 win->file->save_method = TEXT_SAVE_ATOMIC;
327 } else if (strcmp("inplace", arg.s) == 0) {
328 win->file->save_method = TEXT_SAVE_INPLACE;
329 } else {
330 vis_info_show(vis, "Invalid save method `%s', expected "
331 "'auto', 'atomic' or 'inplace'", arg.s);
332 return false;
334 break;
335 case OPTION_LOAD_METHOD:
336 if (strcmp("auto", arg.s) == 0) {
337 vis->load_method = TEXT_LOAD_AUTO;
338 } else if (strcmp("read", arg.s) == 0) {
339 vis->load_method = TEXT_LOAD_READ;
340 } else if (strcmp("mmap", arg.s) == 0) {
341 vis->load_method = TEXT_LOAD_MMAP;
342 } else {
343 vis_info_show(vis, "Invalid load method `%s', expected "
344 "'auto', 'read' or 'mmap'", arg.s);
345 return false;
347 break;
348 case OPTION_CHANGE_256COLORS:
349 vis->change_colors = toggle ? !vis->change_colors : arg.b;
350 break;
351 case OPTION_LAYOUT: {
352 enum UiLayout layout;
353 if (strcmp("h", arg.s) == 0) {
354 layout = UI_LAYOUT_HORIZONTAL;
355 } else if (strcmp("v", arg.s) == 0) {
356 layout = UI_LAYOUT_VERTICAL;
357 } else {
358 vis_info_show(vis, "Invalid layout `%s', expected 'h' or 'v'", arg.s);
359 return false;
361 windows_arrange(vis, layout);
362 break;
364 case OPTION_IGNORECASE:
365 vis->ignorecase = toggle ? !vis->ignorecase : arg.b;
366 break;
367 default:
368 if (!opt->func)
369 return false;
370 return opt->func(vis, win, opt->context, toggle, opt->flags, name, &arg);
373 return true;
376 static bool is_file_pattern(const char *pattern) {
377 if (!pattern)
378 return false;
379 struct stat meta;
380 if (stat(pattern, &meta) == 0 && S_ISDIR(meta.st_mode))
381 return true;
382 for (char special[] = "*?[{$~", *s = special; *s; s++) {
383 if (strchr(pattern, *s))
384 return true;
386 return false;
389 static const char *file_open_dialog(Vis *vis, const char *pattern) {
390 static char name[PATH_MAX];
391 name[0] = '\0';
393 if (!is_file_pattern(pattern))
394 return pattern;
396 Buffer bufcmd, bufout, buferr;
397 buffer_init(&bufcmd);
398 buffer_init(&bufout);
399 buffer_init(&buferr);
401 if (!buffer_put0(&bufcmd, VIS_OPEN " ") || !buffer_append0(&bufcmd, pattern ? pattern : ""))
402 return NULL;
404 Filerange empty = text_range_new(0,0);
405 int status = vis_pipe(vis, vis->win->file, &empty,
406 (const char*[]){ buffer_content0(&bufcmd), NULL },
407 &bufout, read_buffer, &buferr, read_buffer);
409 if (status == 0)
410 strncpy(name, buffer_content0(&bufout), sizeof(name)-1);
411 else if (status != 1)
412 vis_info_show(vis, "Command failed %s", buffer_content0(&buferr));
414 buffer_release(&bufcmd);
415 buffer_release(&bufout);
416 buffer_release(&buferr);
418 for (char *end = name+strlen(name)-1; end >= name && isspace((unsigned char)*end); end--)
419 *end = '\0';
421 return name[0] ? name : NULL;
424 static bool openfiles(Vis *vis, const char **files) {
425 for (; *files; files++) {
426 const char *file = file_open_dialog(vis, *files);
427 if (!file)
428 return false;
429 errno = 0;
430 if (!vis_window_new(vis, file)) {
431 vis_info_show(vis, "Could not open `%s' %s", file,
432 errno ? strerror(errno) : "");
433 return false;
436 return true;
439 static bool cmd_open(Vis *vis, Win *win, Command *cmd, const char *argv[], Selection *sel, Filerange *range) {
440 if (!argv[1])
441 return vis_window_new(vis, NULL);
442 return openfiles(vis, &argv[1]);
445 static void info_unsaved_changes(Vis *vis) {
446 vis_info_show(vis, "No write since last change (add ! to override)");
449 static bool cmd_edit(Vis *vis, Win *win, Command *cmd, const char *argv[], Selection *sel, Filerange *range) {
450 if (argv[2]) {
451 vis_info_show(vis, "Only 1 filename allowed");
452 return false;
454 Win *oldwin = win;
455 if (!oldwin)
456 return false;
457 if (cmd->flags != '!' && !vis_window_closable(oldwin)) {
458 info_unsaved_changes(vis);
459 return false;
461 if (!argv[1]) {
462 if (oldwin->file->refcount > 1) {
463 vis_info_show(vis, "Can not reload file being opened multiple times");
464 return false;
466 return vis_window_reload(oldwin);
468 if (!openfiles(vis, &argv[1]))
469 return false;
470 if (vis->win != oldwin) {
471 Win *newwin = vis->win;
472 vis_window_swap(oldwin, newwin);
473 vis_window_close(oldwin);
474 vis_window_focus(newwin);
476 return vis->win != oldwin;
479 static bool cmd_read(Vis *vis, Win *win, Command *cmd, const char *argv[], Selection *sel, Filerange *range) {
480 bool ret = false;
481 const size_t first_file = 3;
482 const char *args[MAX_ARGV] = { argv[0], "cat", "--" };
483 const char **name = argv[1] ? &argv[1] : (const char*[]){ ".", NULL };
484 for (size_t i = first_file; *name && i < LENGTH(args)-1; name++, i++) {
485 const char *file = file_open_dialog(vis, *name);
486 if (!file || !(args[i] = strdup(file)))
487 goto err;
489 args[LENGTH(args)-1] = NULL;
490 ret = cmd_pipein(vis, win, cmd, args, sel, range);
491 err:
492 for (size_t i = first_file; i < LENGTH(args); i++)
493 free((char*)args[i]);
494 return ret;
497 static bool has_windows(Vis *vis) {
498 for (Win *win = vis->windows; win; win = win->next) {
499 if (!win->file->internal)
500 return true;
502 return false;
505 static bool cmd_quit(Vis *vis, Win *win, Command *cmd, const char *argv[], Selection *sel, Filerange *range) {
506 if (cmd->flags != '!' && !vis_window_closable(win)) {
507 info_unsaved_changes(vis);
508 return false;
510 vis_window_close(win);
511 if (!has_windows(vis))
512 vis_exit(vis, argv[1] ? atoi(argv[1]) : EXIT_SUCCESS);
513 return true;
516 static bool cmd_qall(Vis *vis, Win *win, Command *cmd, const char *argv[], Selection *sel, Filerange *range) {
517 for (Win *next, *win = vis->windows; win; win = next) {
518 next = win->next;
519 if (!win->file->internal && (!text_modified(win->file->text) || cmd->flags == '!'))
520 vis_window_close(win);
522 if (!has_windows(vis)) {
523 vis_exit(vis, argv[1] ? atoi(argv[1]) : EXIT_SUCCESS);
524 return true;
525 } else {
526 info_unsaved_changes(vis);
527 return false;
531 static bool cmd_split(Vis *vis, Win *win, Command *cmd, const char *argv[], Selection *sel, Filerange *range) {
532 if (!win)
533 return false;
534 enum UiOption options = view_options_get(win->view);
535 windows_arrange(vis, UI_LAYOUT_HORIZONTAL);
536 if (!argv[1])
537 return vis_window_split(win);
538 bool ret = openfiles(vis, &argv[1]);
539 if (ret)
540 view_options_set(vis->win->view, options);
541 return ret;
544 static bool cmd_vsplit(Vis *vis, Win *win, Command *cmd, const char *argv[], Selection *sel, Filerange *range) {
545 if (!win)
546 return false;
547 enum UiOption options = view_options_get(win->view);
548 windows_arrange(vis, UI_LAYOUT_VERTICAL);
549 if (!argv[1])
550 return vis_window_split(win);
551 bool ret = openfiles(vis, &argv[1]);
552 if (ret)
553 view_options_set(vis->win->view, options);
554 return ret;
557 static bool cmd_new(Vis *vis, Win *win, Command *cmd, const char *argv[], Selection *sel, Filerange *range) {
558 windows_arrange(vis, UI_LAYOUT_HORIZONTAL);
559 return vis_window_new(vis, NULL);
562 static bool cmd_vnew(Vis *vis, Win *win, Command *cmd, const char *argv[], Selection *sel, Filerange *range) {
563 windows_arrange(vis, UI_LAYOUT_VERTICAL);
564 return vis_window_new(vis, NULL);
567 static bool cmd_wq(Vis *vis, Win *win, Command *cmd, const char *argv[], Selection *sel, Filerange *range) {
568 if (!win)
569 return false;
570 File *file = win->file;
571 bool unmodified = file->fd == -1 && !file->name && !text_modified(file->text);
572 if (unmodified || cmd_write(vis, win, cmd, argv, sel, range))
573 return cmd_quit(vis, win, cmd, (const char*[]){argv[0], NULL}, sel, range);
574 return false;
577 static bool cmd_earlier_later(Vis *vis, Win *win, Command *cmd, const char *argv[], Selection *sel, Filerange *range) {
578 if (!win)
579 return false;
580 Text *txt = win->file->text;
581 char *unit = "";
582 long count = 1;
583 size_t pos = EPOS;
584 if (argv[1]) {
585 errno = 0;
586 count = strtol(argv[1], &unit, 10);
587 if (errno || unit == argv[1] || count < 0) {
588 vis_info_show(vis, "Invalid number");
589 return false;
592 if (*unit) {
593 while (*unit && isspace((unsigned char)*unit))
594 unit++;
595 switch (*unit) {
596 case 'd': count *= 24; /* fall through */
597 case 'h': count *= 60; /* fall through */
598 case 'm': count *= 60; /* fall through */
599 case 's': break;
600 default:
601 vis_info_show(vis, "Unknown time specifier (use: s,m,h or d)");
602 return false;
605 if (argv[0][0] == 'e')
606 count = -count; /* earlier, move back in time */
608 pos = text_restore(txt, text_state(txt) + count);
612 if (!*unit) {
613 VisCountIterator it = vis_count_iterator_init(vis, count);
614 while (vis_count_iterator_next(&it)) {
615 if (argv[0][0] == 'e')
616 pos = text_earlier(txt);
617 else
618 pos = text_later(txt);
622 struct tm tm;
623 time_t state = text_state(txt);
624 char buf[32];
625 strftime(buf, sizeof buf, "State from %H:%M", localtime_r(&state, &tm));
626 vis_info_show(vis, "%s", buf);
628 return pos != EPOS;
631 static bool print_keylayout(const char *key, void *value, void *data) {
632 return text_appendf(data, " %-18s\t%s\n", key[0] == ' ' ? "␣" : key, (char*)value);
635 static bool print_keybinding(const char *key, void *value, void *data) {
636 KeyBinding *binding = value;
637 const char *desc = binding->alias;
638 if (!desc && binding->action)
639 desc = VIS_HELP_USE(binding->action->help);
640 return text_appendf(data, " %-18s\t%s\n", key[0] == ' ' ? "␣" : key, desc ? desc : "");
643 static void print_mode(Mode *mode, Text *txt) {
644 if (!map_empty(mode->bindings))
645 text_appendf(txt, "\n %s\n\n", mode->name);
646 map_iterate(mode->bindings, print_keybinding, txt);
649 static bool print_action(const char *key, void *value, void *data) {
650 const char *help = VIS_HELP_USE(((KeyAction*)value)->help);
651 return text_appendf(data, " %-30s\t%s\n", key, help ? help : "");
654 static bool print_cmd(const char *key, void *value, void *data) {
655 CommandDef *cmd = value;
656 const char *help = VIS_HELP_USE(cmd->help);
657 char usage[256];
658 snprintf(usage, sizeof usage, "%s%s%s%s%s%s%s",
659 cmd->name,
660 (cmd->flags & CMD_FORCE) ? "[!]" : "",
661 (cmd->flags & CMD_TEXT) ? "/text/" : "",
662 (cmd->flags & CMD_REGEX) ? "/regexp/" : "",
663 (cmd->flags & CMD_CMD) ? " command" : "",
664 (cmd->flags & CMD_SHELL) ? (!strcmp(cmd->name, "s") ? "/regexp/text/" : " shell-command") : "",
665 (cmd->flags & CMD_ARGV) ? " [args...]" : "");
666 return text_appendf(data, " %-30s %s\n", usage, help ? help : "");
669 static bool print_option(const char *key, void *value, void *txt) {
670 char desc[256];
671 const OptionDef *opt = value;
672 const char *help = VIS_HELP_USE(opt->help);
673 if (strcmp(key, opt->names[0]))
674 return true;
675 snprintf(desc, sizeof desc, "%s%s%s%s%s",
676 opt->names[0],
677 opt->names[1] ? "|" : "",
678 opt->names[1] ? opt->names[1] : "",
679 opt->flags & VIS_OPTION_TYPE_BOOL ? " on|off" : "",
680 opt->flags & VIS_OPTION_TYPE_NUMBER ? " nn" : "");
681 return text_appendf(txt, " %-30s %s\n", desc, help ? help : "");
684 static void print_symbolic_keys(Vis *vis, Text *txt) {
685 static const int keys[] = {
686 TERMKEY_SYM_BACKSPACE,
687 TERMKEY_SYM_TAB,
688 TERMKEY_SYM_ENTER,
689 TERMKEY_SYM_ESCAPE,
690 //TERMKEY_SYM_SPACE,
691 TERMKEY_SYM_DEL,
692 TERMKEY_SYM_UP,
693 TERMKEY_SYM_DOWN,
694 TERMKEY_SYM_LEFT,
695 TERMKEY_SYM_RIGHT,
696 TERMKEY_SYM_BEGIN,
697 TERMKEY_SYM_FIND,
698 TERMKEY_SYM_INSERT,
699 TERMKEY_SYM_DELETE,
700 TERMKEY_SYM_SELECT,
701 TERMKEY_SYM_PAGEUP,
702 TERMKEY_SYM_PAGEDOWN,
703 TERMKEY_SYM_HOME,
704 TERMKEY_SYM_END,
705 TERMKEY_SYM_CANCEL,
706 TERMKEY_SYM_CLEAR,
707 TERMKEY_SYM_CLOSE,
708 TERMKEY_SYM_COMMAND,
709 TERMKEY_SYM_COPY,
710 TERMKEY_SYM_EXIT,
711 TERMKEY_SYM_HELP,
712 TERMKEY_SYM_MARK,
713 TERMKEY_SYM_MESSAGE,
714 TERMKEY_SYM_MOVE,
715 TERMKEY_SYM_OPEN,
716 TERMKEY_SYM_OPTIONS,
717 TERMKEY_SYM_PRINT,
718 TERMKEY_SYM_REDO,
719 TERMKEY_SYM_REFERENCE,
720 TERMKEY_SYM_REFRESH,
721 TERMKEY_SYM_REPLACE,
722 TERMKEY_SYM_RESTART,
723 TERMKEY_SYM_RESUME,
724 TERMKEY_SYM_SAVE,
725 TERMKEY_SYM_SUSPEND,
726 TERMKEY_SYM_UNDO,
727 TERMKEY_SYM_KP0,
728 TERMKEY_SYM_KP1,
729 TERMKEY_SYM_KP2,
730 TERMKEY_SYM_KP3,
731 TERMKEY_SYM_KP4,
732 TERMKEY_SYM_KP5,
733 TERMKEY_SYM_KP6,
734 TERMKEY_SYM_KP7,
735 TERMKEY_SYM_KP8,
736 TERMKEY_SYM_KP9,
737 TERMKEY_SYM_KPENTER,
738 TERMKEY_SYM_KPPLUS,
739 TERMKEY_SYM_KPMINUS,
740 TERMKEY_SYM_KPMULT,
741 TERMKEY_SYM_KPDIV,
742 TERMKEY_SYM_KPCOMMA,
743 TERMKEY_SYM_KPPERIOD,
744 TERMKEY_SYM_KPEQUALS,
747 TermKey *termkey = vis->ui->termkey_get(vis->ui);
748 text_appendf(txt, " ␣ (a literal \" \" space symbol must be used to refer to <Space>)\n");
749 for (size_t i = 0; i < LENGTH(keys); i++) {
750 text_appendf(txt, " <%s>\n", termkey_get_keyname(termkey, keys[i]));
754 static bool cmd_help(Vis *vis, Win *win, Command *cmd, const char *argv[], Selection *sel, Filerange *range) {
755 if (!vis_window_new(vis, NULL))
756 return false;
758 Text *txt = vis->win->file->text;
760 text_appendf(txt, "vis %s (PID: %ld)\n\n", VERSION, (long)getpid());
762 text_appendf(txt, " Modes\n\n");
763 for (int i = 0; i < LENGTH(vis_modes); i++) {
764 Mode *mode = &vis_modes[i];
765 if (mode->help)
766 text_appendf(txt, " %-18s\t%s\n", mode->name, mode->help);
769 if (!map_empty(vis->keymap)) {
770 text_appendf(txt, "\n Layout specific mappings (affects all modes except INSERT/REPLACE)\n\n");
771 map_iterate(vis->keymap, print_keylayout, txt);
774 print_mode(&vis_modes[VIS_MODE_NORMAL], txt);
775 print_mode(&vis_modes[VIS_MODE_OPERATOR_PENDING], txt);
776 print_mode(&vis_modes[VIS_MODE_VISUAL], txt);
777 print_mode(&vis_modes[VIS_MODE_INSERT], txt);
779 text_appendf(txt, "\n :-Commands\n\n");
780 map_iterate(vis->cmds, print_cmd, txt);
782 text_appendf(txt, "\n Marks\n\n");
783 text_appendf(txt, " a-z General purpose marks\n");
784 for (size_t i = 0; i < LENGTH(vis_marks); i++) {
785 const char *help = VIS_HELP_USE(vis_marks[i].help);
786 text_appendf(txt, " %c %s\n", vis_marks[i].name, help ? help : "");
789 text_appendf(txt, "\n Registers\n\n");
790 text_appendf(txt, " a-z General purpose registers\n");
791 text_appendf(txt, " A-Z Append to corresponding general purpose register\n");
792 for (size_t i = 0; i < LENGTH(vis_registers); i++) {
793 const char *help = VIS_HELP_USE(vis_registers[i].help);
794 text_appendf(txt, " %c %s\n", vis_registers[i].name, help ? help : "");
797 text_appendf(txt, "\n :set command options\n\n");
798 map_iterate(vis->options, print_option, txt);
800 text_appendf(txt, "\n Key binding actions\n\n");
801 map_iterate(vis->actions, print_action, txt);
803 text_appendf(txt, "\n Symbolic keys usable for key bindings "
804 "(prefix with C-, S-, and M- for Ctrl, Shift and Alt respectively)\n\n");
805 print_symbolic_keys(vis, txt);
807 char *paths[] = { NULL, NULL };
808 char *paths_description[] = {
809 "Lua paths used to load runtime files (? will be replaced by filename):",
810 "Lua paths used to load C libraries (? will be replaced by filename):",
813 if (vis_lua_paths_get(vis, &paths[0], &paths[1])) {
814 for (size_t i = 0; i < LENGTH(paths); i++) {
815 text_appendf(txt, "\n %s\n\n", paths_description[i]);
816 for (char *elem = paths[i], *next; elem; elem = next) {
817 if ((next = strstr(elem, ";")))
818 *next++ = '\0';
819 if (*elem)
820 text_appendf(txt, " %s\n", elem);
822 free(paths[i]);
826 text_appendf(txt, "\n Compile time configuration\n\n");
828 const struct {
829 const char *name;
830 bool enabled;
831 } configs[] = {
832 { "Curses support: ", CONFIG_CURSES },
833 { "Lua support: ", CONFIG_LUA },
834 { "Lua LPeg statically built-in: ", CONFIG_LPEG },
835 { "TRE based regex support: ", CONFIG_TRE },
836 { "POSIX ACL support: ", CONFIG_ACL },
837 { "SELinux support: ", CONFIG_SELINUX },
840 for (size_t i = 0; i < LENGTH(configs); i++)
841 text_appendf(txt, " %-32s\t%s\n", configs[i].name, configs[i].enabled ? "yes" : "no");
843 text_save(txt, NULL);
844 view_cursor_to(vis->win->view, 0);
846 if (argv[1])
847 vis_motion(vis, VIS_MOVE_SEARCH_FORWARD, argv[1]);
848 return true;
851 static bool cmd_langmap(Vis *vis, Win *win, Command *cmd, const char *argv[], Selection *sel, Filerange *range) {
852 const char *nonlatin = argv[1];
853 const char *latin = argv[2];
854 bool mapped = true;
856 if (!latin || !nonlatin) {
857 vis_info_show(vis, "usage: langmap <non-latin keys> <latin keys>");
858 return false;
861 while (*latin && *nonlatin) {
862 size_t i = 0, j = 0;
863 char latin_key[8], nonlatin_key[8];
864 do {
865 if (i < sizeof(latin_key)-1)
866 latin_key[i++] = *latin;
867 latin++;
868 } while (!ISUTF8(*latin));
869 do {
870 if (j < sizeof(nonlatin_key)-1)
871 nonlatin_key[j++] = *nonlatin;
872 nonlatin++;
873 } while (!ISUTF8(*nonlatin));
874 latin_key[i] = '\0';
875 nonlatin_key[j] = '\0';
876 mapped &= vis_keymap_add(vis, nonlatin_key, strdup(latin_key));
879 return mapped;
882 static bool cmd_map(Vis *vis, Win *win, Command *cmd, const char *argv[], Selection *sel, Filerange *range) {
883 bool mapped = false;
884 bool local = strstr(argv[0], "-") != NULL;
885 enum VisMode mode = vis_mode_from(vis, argv[1]);
887 if (local && !win) {
888 vis_info_show(vis, "Invalid window for :%s", argv[0]);
889 return false;
892 if (mode == VIS_MODE_INVALID || !argv[2] || !argv[3]) {
893 vis_info_show(vis, "usage: %s mode lhs rhs", argv[0]);
894 return false;
897 const char *lhs = argv[2];
898 KeyBinding *binding = vis_binding_new(vis);
899 if (!binding || !(binding->alias = strdup(argv[3])))
900 goto err;
902 if (local)
903 mapped = vis_window_mode_map(win, mode, cmd->flags == '!', lhs, binding);
904 else
905 mapped = vis_mode_map(vis, mode, cmd->flags == '!', lhs, binding);
907 err:
908 if (!mapped) {
909 vis_info_show(vis, "Failed to map `%s' in %s mode%s", lhs, argv[1],
910 cmd->flags != '!' ? ", mapping already exists, "
911 "override with `!'" : "");
912 vis_binding_free(vis, binding);
914 return mapped;
917 static bool cmd_unmap(Vis *vis, Win *win, Command *cmd, const char *argv[], Selection *sel, Filerange *range) {
918 bool unmapped = false;
919 bool local = strstr(argv[0], "-") != NULL;
920 enum VisMode mode = vis_mode_from(vis, argv[1]);
921 const char *lhs = argv[2];
923 if (local && !win) {
924 vis_info_show(vis, "Invalid window for :%s", argv[0]);
925 return false;
928 if (mode == VIS_MODE_INVALID || !lhs) {
929 vis_info_show(vis, "usage: %s mode lhs", argv[0]);
930 return false;
933 if (local)
934 unmapped = vis_window_mode_unmap(win, mode, lhs);
935 else
936 unmapped = vis_mode_unmap(vis, mode, lhs);
937 if (!unmapped)
938 vis_info_show(vis, "Failed to unmap `%s' in %s mode", lhs, argv[1]);
939 return unmapped;