1 /* this file is included from sam.c */
7 #define VIS_OPEN "vis-open"
15 bool vis_cmd_register(Vis
*vis
, const char *name
, void *data
, CmdFunc func
) {
18 if (!vis
->usercmds
&& !(vis
->usercmds
= map_new()))
20 CmdUser
*cmd
= calloc(1, sizeof *cmd
);
25 if (!map_put(vis
->cmds
, name
, &cmddef_user
))
27 if (!map_put(vis
->usercmds
, name
, cmd
)) {
28 map_delete(vis
->cmds
, name
);
37 bool vis_cmd_unregister(Vis
*vis
, const char *name
) {
40 CmdUser
*cmd
= map_delete(vis
->usercmds
, name
);
43 if (!map_delete(vis
->cmds
, name
))
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)
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
)) {
75 for (const char **f
= (const char*[]){"0", "false", "no", "off", NULL
}; *f
; f
++) {
76 if (!strcasecmp(s
, *f
)) {
84 static bool cmd_set(Vis
*vis
, Win
*win
, Command
*cmd
, const char *argv
[], Cursor
*cur
, Filerange
*range
) {
95 OPTION_FLAG_OPTIONAL
= 1 << 0,
96 OPTION_FLAG_WINDOW
= 1 << 1,
109 OPTION_NUMBER_RELATIVE
,
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
},
131 if (!(vis
->options
= map_new()))
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
]))
143 vis_info_show(vis
, "Expecting: set option [value]");
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
)
160 opt
= map_closest(vis
->options
, argv
[1]);
162 vis_info_show(vis
, "Unknown option: `%s'", argv
[1]);
166 if (!win
&& (opt
->flags
& OPTION_FLAG_WINDOW
)) {
167 vis_info_show(vis
, "Need active window for :set command");
172 case OPTION_TYPE_STRING
:
173 if (!(opt
->flags
& OPTION_FLAG_OPTIONAL
) && !argv
[2]) {
174 vis_info_show(vis
, "Expecting string option value");
179 case OPTION_TYPE_BOOL
:
182 } else if (!parse_bool(argv
[2], &arg
.b
)) {
183 vis_info_show(vis
, "Expecting boolean option value not: `%s'", argv
[2]);
189 case OPTION_TYPE_NUMBER
:
190 case OPTION_TYPE_UNSIGNED
:
192 vis_info_show(vis
, "Expecting number");
195 /* TODO: error checking? long type */
196 arg
.u
= strtoul(argv
[2], NULL
, 10);
200 switch (opt
->index
) {
201 case OPTION_EXPANDTAB
:
202 vis
->expandtab
= arg
.b
;
204 case OPTION_AUTOINDENT
:
205 vis
->autoindent
= arg
.b
;
207 case OPTION_TABWIDTH
:
208 tabwidth_set(vis
, arg
.i
);
212 const char *syntax
= view_syntax_get(win
->view
);
214 vis_info_show(vis
, "Syntax definition in use: `%s'", syntax
);
216 vis_info_show(vis
, "No syntax definition in use");
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]);
229 vis_info_show(vis
, "Expecting: spaces, tabs, newlines");
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) {
243 } else if (strstr(*args
, keys
[i
]) == *args
) {
245 const char *v
= *args
+ strlen(keys
[i
]);
246 if (*v
== '=' && parse_bool(v
+1, &show
)) {
255 view_options_set(win
->view
, flags
);
257 case OPTION_NUMBER
: {
258 enum UiOption opt
= view_options_get(win
->view
);
260 opt
&= ~UI_OPTION_LINE_NUMBERS_RELATIVE
;
261 opt
|= UI_OPTION_LINE_NUMBERS_ABSOLUTE
;
263 opt
&= ~UI_OPTION_LINE_NUMBERS_ABSOLUTE
;
265 view_options_set(win
->view
, opt
);
268 case OPTION_NUMBER_RELATIVE
: {
269 enum UiOption opt
= view_options_get(win
->view
);
271 opt
&= ~UI_OPTION_LINE_NUMBERS_ABSOLUTE
;
272 opt
|= UI_OPTION_LINE_NUMBERS_RELATIVE
;
274 opt
&= ~UI_OPTION_LINE_NUMBERS_RELATIVE
;
276 view_options_set(win
->view
, opt
);
279 case OPTION_CURSOR_LINE
: {
280 enum UiOption opt
= view_options_get(win
->view
);
282 opt
|= UI_OPTION_CURSOR_LINE
;
284 opt
&= ~UI_OPTION_CURSOR_LINE
;
285 view_options_set(win
->view
, opt
);
289 if (!vis_theme_load(vis
, arg
.s
)) {
290 vis_info_show(vis
, "Failed to load theme: `%s'", arg
.s
);
294 case OPTION_COLOR_COLUMN
:
295 view_colorcolumn_set(win
->view
, arg
.i
);
298 view_horizon_set(win
->view
, arg
.u
);
305 static bool is_file_pattern(const char *pattern
) {
309 if (stat(pattern
, &meta
) == 0 && S_ISDIR(meta
.st_mode
))
311 for (char special
[] = "*?[{$~", *s
= special
; *s
; s
++) {
312 if (strchr(pattern
, *s
))
318 static const char *file_open_dialog(Vis
*vis
, const char *pattern
) {
319 static char name
[PATH_MAX
];
322 if (!is_file_pattern(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
: ""))
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
);
338 strncpy(name
, buffer_content0(&bufout
), sizeof(name
)-1);
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
--)
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
);
358 if (!vis_window_new(vis
, file
)) {
359 vis_info_show(vis
, "Could not open `%s' %s", file
,
360 errno
? strerror(errno
) : "");
367 static bool cmd_open(Vis
*vis
, Win
*win
, Command
*cmd
, const char *argv
[], Cursor
*cur
, Filerange
*range
) {
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
) {
381 if (cmd
->flags
!= '!' && !vis_window_closable(oldwin
)) {
382 info_unsaved_changes(vis
);
386 return vis_window_reload(oldwin
);
387 if (!openfiles(vis
, &argv
[1]))
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
)
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
);
411 vis_window_close(win
);
412 if (!has_windows(vis
))
413 vis_exit(vis
, EXIT_SUCCESS
);
417 static bool cmd_bdelete(Vis
*vis
, Win
*win
, Command
*cmd
, const char *argv
[], Cursor
*cur
, Filerange
*range
) {
420 Text
*txt
= win
->file
->text
;
421 if (text_modified(txt
) && cmd
->flags
!= '!') {
422 info_unsaved_changes(vis
);
425 for (Win
*next
, *win
= vis
->windows
; win
; win
= next
) {
427 if (win
->file
->text
== txt
)
428 vis_window_close(win
);
430 if (!has_windows(vis
))
431 vis_exit(vis
, EXIT_SUCCESS
);
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
) {
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
);
445 info_unsaved_changes(vis
);
450 static bool cmd_split(Vis
*vis
, Win
*win
, Command
*cmd
, const char *argv
[], Cursor
*cur
, Filerange
*range
) {
453 enum UiOption options
= view_options_get(win
->view
);
454 windows_arrange(vis
, UI_LAYOUT_HORIZONTAL
);
456 return vis_window_split(win
);
457 bool ret
= openfiles(vis
, &argv
[1]);
459 view_options_set(vis
->win
->view
, options
);
463 static bool cmd_vsplit(Vis
*vis
, Win
*win
, Command
*cmd
, const char *argv
[], Cursor
*cur
, Filerange
*range
) {
466 enum UiOption options
= view_options_get(win
->view
);
467 windows_arrange(vis
, UI_LAYOUT_VERTICAL
);
469 return vis_window_split(win
);
470 bool ret
= openfiles(vis
, &argv
[1]);
472 view_options_set(vis
->win
->view
, options
);
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
) {
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
);
496 static bool cmd_earlier_later(Vis
*vis
, Win
*win
, Command
*cmd
, const char *argv
[], Cursor
*cur
, Filerange
*range
) {
499 Text
*txt
= win
->file
->text
;
505 count
= strtol(argv
[1], &unit
, 10);
506 if (errno
|| unit
== argv
[1] || count
< 0) {
507 vis_info_show(vis
, "Invalid number");
512 while (*unit
&& isspace((unsigned char)*unit
))
515 case 'd': count
*= 24; /* fall through */
516 case 'h': count
*= 60; /* fall through */
517 case 'm': count
*= 60; /* fall through */
520 vis_info_show(vis
, "Unknown time specifier (use: s,m,h or d)");
524 if (argv
[0][0] == 'e')
525 count
= -count
; /* earlier, move back in time */
527 pos
= text_restore(txt
, text_state(txt
) + count
);
532 if (argv
[0][0] == 'e')
533 pos
= text_earlier(txt
, count
);
535 pos
= text_later(txt
, count
);
538 time_t state
= text_state(txt
);
540 strftime(buf
, sizeof buf
, "State from %H:%M", localtime(&state
));
541 vis_info_show(vis
, "%s", buf
);
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
,
591 TERMKEY_SYM_PAGEDOWN
,
608 TERMKEY_SYM_REFERENCE
,
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
))
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
];
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
);
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
, ";")))
687 text_appendf(txt
, " %s\n", elem
);
692 text_save(txt
, NULL
);
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)
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];
718 if (!latin
|| !nonlatin
) {
719 vis_info_show(vis
, "usage: langmap <non-latin keys> <latin keys>");
723 while (*latin
&& *nonlatin
) {
725 char latin_key
[8], nonlatin_key
[8];
727 if (i
< sizeof(latin_key
)-1)
728 latin_key
[i
++] = *latin
;
730 } while (!ISUTF8(*latin
));
732 if (j
< sizeof(nonlatin_key
)-1)
733 nonlatin_key
[j
++] = *nonlatin
;
735 } while (!ISUTF8(*nonlatin
));
737 nonlatin_key
[j
] = '\0';
738 mapped
&= vis_keymap_add(vis
, nonlatin_key
, strdup(latin_key
));
744 static bool cmd_map(Vis
*vis
, Win
*win
, Command
*cmd
, const char *argv
[], Cursor
*cur
, Filerange
*range
) {
745 KeyBinding
*binding
= NULL
;
747 bool local
= strstr(argv
[0], "-") != NULL
;
748 enum VisMode mode
= str2vismode(argv
[1]);
753 if (mode
== VIS_MODE_INVALID
|| !argv
[2] || !argv
[3]) {
754 vis_info_show(vis
, "usage: map mode lhs rhs\n");
758 char *lhs
= strdup(argv
[2]);
759 char *rhs
= strdup(argv
[3]);
760 if (!lhs
|| !rhs
|| !(binding
= calloc(1, sizeof *binding
)))
764 while (cmd
->flags
== '!' && next
) {
766 next
= (char*)vis_keys_next(vis
, next
);
772 vis_window_mode_unmap(win
, mode
, lhs
);
774 vis_mode_unmap(vis
, mode
, lhs
);
779 binding
->alias
= rhs
;
782 mapped
= vis_window_mode_map(win
, mode
, lhs
, binding
);
784 mapped
= vis_mode_map(vis
, mode
, lhs
, binding
);
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];
803 if (mode
== VIS_MODE_INVALID
|| !lhs
) {
804 vis_info_show(vis
, "usage: unmap mode lhs\n");
809 return vis_window_mode_unmap(win
, mode
, lhs
);
811 return vis_mode_unmap(vis
, mode
, lhs
);