10 #include <sys/select.h>
11 #include <sys/types.h>
15 #include "text-util.h"
16 #include "text-motions.h"
19 enum CmdOpt
{ /* option flags for command definitions */
20 CMD_OPT_NONE
, /* no option (default value) */
21 CMD_OPT_FORCE
, /* whether the command can be forced by appending '!' */
22 CMD_OPT_ARGS
, /* whether the command line should be parsed in to space
23 * separated arguments to placed into argv, otherwise argv[1]
24 * will contain the remaining command line unmodified */
27 typedef struct { /* command definitions for the ':'-prompt */
28 const char *name
[3]; /* name and optional alias for the command */
29 /* command logic called with a NULL terminated array of arguments.
30 * argv[0] will be the command name */
31 bool (*cmd
)(Vis
*, Filerange
*, enum CmdOpt opt
, const char *argv
[]);
32 enum CmdOpt opt
; /* command option flags */
35 /** ':'-command implementations */
36 /* set various runtime options */
37 static bool cmd_set(Vis
*, Filerange
*, enum CmdOpt
, const char *argv
[]);
38 /* for each argument create a new window and open the corresponding file */
39 static bool cmd_open(Vis
*, Filerange
*, enum CmdOpt
, const char *argv
[]);
40 /* close current window (discard modifications if forced ) and open argv[1],
41 * if no argv[1] is given re-read to current file from disk */
42 static bool cmd_edit(Vis
*, Filerange
*, enum CmdOpt
, const char *argv
[]);
43 /* close the current window, discard modifications if forced */
44 static bool cmd_quit(Vis
*, Filerange
*, enum CmdOpt
, const char *argv
[]);
45 /* close all windows which show current file, discard modifications if forced */
46 static bool cmd_bdelete(Vis
*, Filerange
*, enum CmdOpt
, const char *argv
[]);
47 /* close all windows, exit editor, discard modifications if forced */
48 static bool cmd_qall(Vis
*, Filerange
*, enum CmdOpt
, const char *argv
[]);
49 /* for each argument try to insert the file content at current cursor postion */
50 static bool cmd_read(Vis
*, Filerange
*, enum CmdOpt
, const char *argv
[]);
51 static bool cmd_substitute(Vis
*, Filerange
*, enum CmdOpt
, const char *argv
[]);
52 /* if no argument are given, split the current window horizontally,
53 * otherwise open the file */
54 static bool cmd_split(Vis
*, Filerange
*, enum CmdOpt
, const char *argv
[]);
55 /* if no argument are given, split the current window vertically,
56 * otherwise open the file */
57 static bool cmd_vsplit(Vis
*, Filerange
*, enum CmdOpt
, const char *argv
[]);
58 /* create a new empty window and arrange all windows either horizontally or vertically */
59 static bool cmd_new(Vis
*, Filerange
*, enum CmdOpt
, const char *argv
[]);
60 static bool cmd_vnew(Vis
*, Filerange
*, enum CmdOpt
, const char *argv
[]);
61 /* save the file displayed in the current window and close it */
62 static bool cmd_wq(Vis
*, Filerange
*, enum CmdOpt
, const char *argv
[]);
63 /* save the file displayed in the current window if it was changvis, then close the window */
64 static bool cmd_xit(Vis
*, Filerange
*, enum CmdOpt
, const char *argv
[]);
65 /* save the file displayed in the current window to the name given.
66 * do not change internal filname association. further :w commands
67 * without arguments will still write to the old filename */
68 static bool cmd_write(Vis
*, Filerange
*, enum CmdOpt
, const char *argv
[]);
69 /* save the file displayed in the current window to the name given,
70 * associate the new name with the buffer. further :w commands
71 * without arguments will write to the new filename */
72 static bool cmd_saveas(Vis
*, Filerange
*, enum CmdOpt
, const char *argv
[]);
73 /* filter range through external program argv[1] */
74 static bool cmd_filter(Vis
*, Filerange
*, enum CmdOpt
, const char *argv
[]);
75 /* switch to the previous/next saved state of the text, chronologically */
76 static bool cmd_earlier_later(Vis
*, Filerange
*, enum CmdOpt
, const char *argv
[]);
77 /* dump current key bindings */
78 static bool cmd_help(Vis
*, Filerange
*, enum CmdOpt
, const char *argv
[]);
80 /* command recognized at the ':'-prompt. commands are found using a unique
81 * prefix match. that is if a command should be available under an abbreviation
82 * which is a prefix for another command it has to be added as an alias. the
83 * long human readable name should always come first */
84 static Command cmds
[] = {
85 /* command name / optional alias, function, options */
86 { { "bdelete" }, cmd_bdelete
, CMD_OPT_FORCE
},
87 { { "edit" }, cmd_edit
, CMD_OPT_FORCE
},
88 { { "help" }, cmd_help
, CMD_OPT_NONE
},
89 { { "new" }, cmd_new
, CMD_OPT_NONE
},
90 { { "open" }, cmd_open
, CMD_OPT_NONE
},
91 { { "qall" }, cmd_qall
, CMD_OPT_FORCE
},
92 { { "quit", "q" }, cmd_quit
, CMD_OPT_FORCE
},
93 { { "read", }, cmd_read
, CMD_OPT_FORCE
},
94 { { "saveas" }, cmd_saveas
, CMD_OPT_FORCE
},
95 { { "set", }, cmd_set
, CMD_OPT_ARGS
},
96 { { "split" }, cmd_split
, CMD_OPT_NONE
},
97 { { "substitute", "s" }, cmd_substitute
, CMD_OPT_NONE
},
98 { { "vnew" }, cmd_vnew
, CMD_OPT_NONE
},
99 { { "vsplit", }, cmd_vsplit
, CMD_OPT_NONE
},
100 { { "wq", }, cmd_wq
, CMD_OPT_FORCE
},
101 { { "write", "w" }, cmd_write
, CMD_OPT_FORCE
},
102 { { "xit", }, cmd_xit
, CMD_OPT_FORCE
},
103 { { "earlier" }, cmd_earlier_later
, CMD_OPT_NONE
},
104 { { "later" }, cmd_earlier_later
, CMD_OPT_NONE
},
105 { { "!", }, cmd_filter
, CMD_OPT_NONE
},
106 { /* array terminator */ },
110 static void windows_arrange(Vis
*vis
, enum UiLayout layout
) {
111 vis
->ui
->arrange(vis
->ui
, layout
);
114 static void tabwidth_set(Vis
*vis
, int tabwidth
) {
115 if (tabwidth
< 1 || tabwidth
> 8)
117 for (Win
*win
= vis
->windows
; win
; win
= win
->next
)
118 view_tabwidth_set(win
->view
, tabwidth
);
119 vis
->tabwidth
= tabwidth
;
122 /* parse human-readable boolean value in s. If successful, store the result in
123 * outval and return true. Else return false and leave outval alone. */
124 static bool parse_bool(const char *s
, bool *outval
) {
125 for (const char **t
= (const char*[]){"1", "true", "yes", "on", NULL
}; *t
; t
++) {
126 if (!strcasecmp(s
, *t
)) {
131 for (const char **f
= (const char*[]){"0", "false", "no", "off", NULL
}; *f
; f
++) {
132 if (!strcasecmp(s
, *f
)) {
140 static bool cmd_set(Vis
*vis
, Filerange
*range
, enum CmdOpt cmdopt
, const char *argv
[]) {
143 const char *names
[3];
160 OPTION_NUMBER_RELATIVE
,
166 /* definitions have to be in the same order as the enum above */
167 static OptionDef options
[] = {
168 [OPTION_AUTOINDENT
] = { { "autoindent", "ai" }, OPTION_TYPE_BOOL
},
169 [OPTION_EXPANDTAB
] = { { "expandtab", "et" }, OPTION_TYPE_BOOL
},
170 [OPTION_TABWIDTH
] = { { "tabwidth", "tw" }, OPTION_TYPE_NUMBER
},
171 [OPTION_SYNTAX
] = { { "syntax" }, OPTION_TYPE_STRING
, true },
172 [OPTION_SHOW
] = { { "show" }, OPTION_TYPE_STRING
},
173 [OPTION_NUMBER
] = { { "numbers", "nu" }, OPTION_TYPE_BOOL
},
174 [OPTION_NUMBER_RELATIVE
] = { { "relativenumbers", "rnu" }, OPTION_TYPE_BOOL
},
175 [OPTION_CURSOR_LINE
] = { { "cursorline", "cul" }, OPTION_TYPE_BOOL
},
176 [OPTION_THEME
] = { { "theme" }, OPTION_TYPE_STRING
},
177 [OPTION_COLOR_COLUMN
] = { { "colorcolumn", "cc" }, OPTION_TYPE_NUMBER
},
181 if (!(vis
->options
= map_new()))
183 for (int i
= 0; i
< LENGTH(options
); i
++) {
184 options
[i
].index
= i
;
185 for (const char **name
= options
[i
].names
; *name
; name
++) {
186 if (!map_put(vis
->options
, *name
, &options
[i
]))
193 vis_info_show(vis
, "Expecting: set option [value]");
199 OptionDef
*opt
= NULL
;
201 if (!strncasecmp(argv
[1], "no", 2)) {
202 opt
= map_closest(vis
->options
, argv
[1]+2);
203 if (opt
&& opt
->type
== OPTION_TYPE_BOOL
)
210 opt
= map_closest(vis
->options
, argv
[1]);
212 vis_info_show(vis
, "Unknown option: `%s'", argv
[1]);
217 case OPTION_TYPE_STRING
:
218 if (!opt
->optional
&& !argv
[2]) {
219 vis_info_show(vis
, "Expecting string option value");
224 case OPTION_TYPE_BOOL
:
227 } else if (!parse_bool(argv
[2], &arg
.b
)) {
228 vis_info_show(vis
, "Expecting boolean option value not: `%s'", argv
[2]);
234 case OPTION_TYPE_NUMBER
:
236 vis_info_show(vis
, "Expecting number");
239 /* TODO: error checking? long type */
240 arg
.i
= strtoul(argv
[2], NULL
, 10);
244 switch (opt
->index
) {
245 case OPTION_EXPANDTAB
:
246 vis
->expandtab
= arg
.b
;
248 case OPTION_AUTOINDENT
:
249 vis
->autoindent
= arg
.b
;
251 case OPTION_TABWIDTH
:
252 tabwidth_set(vis
, arg
.i
);
256 const char *syntax
= view_syntax_get(vis
->win
->view
);
258 vis_info_show(vis
, "Syntax definition in use: `%s'", syntax
);
260 vis_info_show(vis
, "No syntax definition in use");
264 if (parse_bool(argv
[2], &arg
.b
) && !arg
.b
)
265 return view_syntax_set(vis
->win
->view
, NULL
);
266 if (!view_syntax_set(vis
->win
->view
, argv
[2])) {
267 vis_info_show(vis
, "Unknown syntax definition: `%s'", argv
[2]);
273 vis_info_show(vis
, "Expecting: spaces, tabs, newlines");
276 char *keys
[] = { "spaces", "tabs", "newlines" };
278 UI_OPTION_SYMBOL_SPACE
,
279 UI_OPTION_SYMBOL_TAB
|UI_OPTION_SYMBOL_TAB_FILL
,
280 UI_OPTION_SYMBOL_EOL
,
282 int flags
= view_options_get(vis
->win
->view
);
283 for (const char **args
= &argv
[2]; *args
; args
++) {
284 for (int i
= 0; i
< LENGTH(keys
); i
++) {
285 if (strcmp(*args
, keys
[i
]) == 0) {
287 } else if (strstr(*args
, keys
[i
]) == *args
) {
289 const char *v
= *args
+ strlen(keys
[i
]);
290 if (*v
== '=' && parse_bool(v
+1, &show
)) {
299 view_options_set(vis
->win
->view
, flags
);
301 case OPTION_NUMBER
: {
302 enum UiOption opt
= view_options_get(vis
->win
->view
);
304 opt
&= ~UI_OPTION_LINE_NUMBERS_RELATIVE
;
305 opt
|= UI_OPTION_LINE_NUMBERS_ABSOLUTE
;
307 opt
&= ~UI_OPTION_LINE_NUMBERS_ABSOLUTE
;
309 view_options_set(vis
->win
->view
, opt
);
312 case OPTION_NUMBER_RELATIVE
: {
313 enum UiOption opt
= view_options_get(vis
->win
->view
);
315 opt
&= ~UI_OPTION_LINE_NUMBERS_ABSOLUTE
;
316 opt
|= UI_OPTION_LINE_NUMBERS_RELATIVE
;
318 opt
&= ~UI_OPTION_LINE_NUMBERS_RELATIVE
;
320 view_options_set(vis
->win
->view
, opt
);
323 case OPTION_CURSOR_LINE
: {
324 enum UiOption opt
= view_options_get(vis
->win
->view
);
326 opt
|= UI_OPTION_CURSOR_LINE
;
328 opt
&= ~UI_OPTION_CURSOR_LINE
;
329 view_options_set(vis
->win
->view
, opt
);
333 if (!vis_theme_load(vis
, arg
.s
)) {
334 vis_info_show(vis
, "Failed to load theme: `%s'", arg
.s
);
338 case OPTION_COLOR_COLUMN
:
339 view_colorcolumn_set(vis
->win
->view
, arg
.i
);
346 static bool is_file_pattern(const char *pattern
) {
350 if (stat(pattern
, &meta
) == 0 && S_ISDIR(meta
.st_mode
))
352 return strchr(pattern
, '*') || strchr(pattern
, '[') || strchr(pattern
, '{');
355 static const char *file_open_dialog(Vis
*vis
, const char *pattern
) {
356 if (!is_file_pattern(pattern
))
358 /* this is a bit of a hack, we temporarily replace the text/view of the active
359 * window such that we can use cmd_filter as is */
361 static char filename
[PATH_MAX
];
362 Filerange range
= text_range_empty();
364 File
*file
= win
->file
;
365 Text
*txt_orig
= file
->text
;
366 View
*view_orig
= win
->view
;
367 Text
*txt
= text_load(NULL
);
368 View
*view
= view_new(txt
, NULL
);
370 snprintf(vis_open
, sizeof(vis_open
)-1, "vis-open %s", pattern
? pattern
: "");
377 if (cmd_filter(vis
, &range
, CMD_OPT_NONE
, (const char *[]){ "open", vis_open
, NULL
})) {
378 size_t len
= text_size(txt
);
379 if (len
>= sizeof(filename
))
382 text_bytes_get(txt
, 0, --len
, filename
);
383 filename
[len
] = '\0';
389 win
->view
= view_orig
;
390 file
->text
= txt_orig
;
391 return filename
[0] ? filename
: NULL
;
394 static bool openfiles(Vis
*vis
, const char **files
) {
395 for (; *files
; files
++) {
396 const char *file
= file_open_dialog(vis
, *files
);
400 if (!vis_window_new(vis
, file
)) {
401 vis_info_show(vis
, "Could not open `%s' %s", file
,
402 errno
? strerror(errno
) : "");
409 static bool cmd_open(Vis
*vis
, Filerange
*range
, enum CmdOpt opt
, const char *argv
[]) {
411 return vis_window_new(vis
, NULL
);
412 return openfiles(vis
, &argv
[1]);
415 static bool is_view_closeable(Win
*win
) {
416 if (!text_modified(win
->file
->text
))
418 return win
->file
->refcount
> 1;
421 static void info_unsaved_changes(Vis
*vis
) {
422 vis_info_show(vis
, "No write since last change (add ! to override)");
425 static bool cmd_edit(Vis
*vis
, Filerange
*range
, enum CmdOpt opt
, const char *argv
[]) {
426 Win
*oldwin
= vis
->win
;
427 if (!(opt
& CMD_OPT_FORCE
) && !is_view_closeable(oldwin
)) {
428 info_unsaved_changes(vis
);
432 return vis_window_reload(oldwin
);
433 if (!openfiles(vis
, &argv
[1]))
435 if (vis
->win
!= oldwin
)
436 vis_window_close(oldwin
);
437 return vis
->win
!= oldwin
;
440 static bool cmd_quit(Vis
*vis
, Filerange
*range
, enum CmdOpt opt
, const char *argv
[]) {
441 if (!(opt
& CMD_OPT_FORCE
) && !is_view_closeable(vis
->win
)) {
442 info_unsaved_changes(vis
);
445 vis_window_close(vis
->win
);
447 vis_exit(vis
, EXIT_SUCCESS
);
451 static bool cmd_xit(Vis
*vis
, Filerange
*range
, enum CmdOpt opt
, const char *argv
[]) {
452 if (text_modified(vis
->win
->file
->text
) && !cmd_write(vis
, range
, opt
, argv
)) {
453 if (!(opt
& CMD_OPT_FORCE
))
456 return cmd_quit(vis
, range
, opt
, argv
);
459 static bool cmd_bdelete(Vis
*vis
, Filerange
*range
, enum CmdOpt opt
, const char *argv
[]) {
460 Text
*txt
= vis
->win
->file
->text
;
461 if (text_modified(txt
) && !(opt
& CMD_OPT_FORCE
)) {
462 info_unsaved_changes(vis
);
465 for (Win
*next
, *win
= vis
->windows
; win
; win
= next
) {
467 if (win
->file
->text
== txt
)
468 vis_window_close(win
);
471 vis_exit(vis
, EXIT_SUCCESS
);
475 static bool cmd_qall(Vis
*vis
, Filerange
*range
, enum CmdOpt opt
, const char *argv
[]) {
476 for (Win
*next
, *win
= vis
->windows
; win
; win
= next
) {
478 if (!text_modified(vis
->win
->file
->text
) || (opt
& CMD_OPT_FORCE
))
479 vis_window_close(win
);
482 vis_exit(vis
, EXIT_SUCCESS
);
484 info_unsaved_changes(vis
);
485 return vis
->windows
== NULL
;
488 static bool cmd_read(Vis
*vis
, Filerange
*range
, enum CmdOpt opt
, const char *argv
[]) {
492 vis_info_show(vis
, "Filename or command expected");
496 bool iscmd
= (opt
& CMD_OPT_FORCE
) || argv
[1][0] == '!';
497 const char *arg
= argv
[1]+(argv
[1][0] == '!');
498 snprintf(cmd
, sizeof cmd
, "%s%s", iscmd
? "" : "cat ", arg
);
500 size_t pos
= view_cursor_get(vis
->win
->view
);
501 if (!text_range_valid(range
))
502 *range
= (Filerange
){ .start
= pos
, .end
= pos
};
503 Filerange
delete = *range
;
504 range
->start
= range
->end
;
506 bool ret
= cmd_filter(vis
, range
, opt
, (const char*[]){ argv
[0], "sh", "-c", cmd
, NULL
});
508 text_delete_range(vis
->win
->file
->text
, &delete);
512 static bool cmd_substitute(Vis
*vis
, Filerange
*range
, enum CmdOpt opt
, const char *argv
[]) {
514 if (!text_range_valid(range
))
515 *range
= (Filerange
){ .start
= 0, .end
= text_size(vis
->win
->file
->text
) };
516 snprintf(pattern
, sizeof pattern
, "s%s", argv
[1]);
517 return cmd_filter(vis
, range
, opt
, (const char*[]){ argv
[0], "sed", pattern
, NULL
});
520 static bool cmd_split(Vis
*vis
, Filerange
*range
, enum CmdOpt opt
, const char *argv
[]) {
521 enum UiOption options
= view_options_get(vis
->win
->view
);
522 windows_arrange(vis
, UI_LAYOUT_HORIZONTAL
);
524 return vis_window_split(vis
->win
);
525 bool ret
= openfiles(vis
, &argv
[1]);
526 view_options_set(vis
->win
->view
, options
);
530 static bool cmd_vsplit(Vis
*vis
, Filerange
*range
, enum CmdOpt opt
, const char *argv
[]) {
531 enum UiOption options
= view_options_get(vis
->win
->view
);
532 windows_arrange(vis
, UI_LAYOUT_VERTICAL
);
534 return vis_window_split(vis
->win
);
535 bool ret
= openfiles(vis
, &argv
[1]);
536 view_options_set(vis
->win
->view
, options
);
540 static bool cmd_new(Vis
*vis
, Filerange
*range
, enum CmdOpt opt
, const char *argv
[]) {
541 windows_arrange(vis
, UI_LAYOUT_HORIZONTAL
);
542 return vis_window_new(vis
, NULL
);
545 static bool cmd_vnew(Vis
*vis
, Filerange
*range
, enum CmdOpt opt
, const char *argv
[]) {
546 windows_arrange(vis
, UI_LAYOUT_VERTICAL
);
547 return vis_window_new(vis
, NULL
);
550 static bool cmd_wq(Vis
*vis
, Filerange
*range
, enum CmdOpt opt
, const char *argv
[]) {
551 if (cmd_write(vis
, range
, opt
, argv
))
552 return cmd_quit(vis
, range
, opt
, argv
);
556 static bool cmd_write(Vis
*vis
, Filerange
*range
, enum CmdOpt opt
, const char *argv
[]) {
557 File
*file
= vis
->win
->file
;
558 Text
*text
= file
->text
;
559 if (!text_range_valid(range
))
560 *range
= (Filerange
){ .start
= 0, .end
= text_size(text
) };
562 argv
[1] = file
->name
;
564 if (file
->is_stdin
) {
565 if (strchr(argv
[0], 'q')) {
566 ssize_t written
= text_write_range(text
, range
, STDOUT_FILENO
);
567 if (written
== -1 || (size_t)written
!= text_range_size(range
)) {
568 vis_info_show(vis
, "Can not write to stdout");
571 /* make sure the file is marked as saved i.e. not modified */
572 text_save_range(text
, range
, NULL
);
575 vis_info_show(vis
, "No filename given, use 'wq' to write to stdout");
578 vis_info_show(vis
, "Filename expected");
581 for (const char **name
= &argv
[1]; *name
; name
++) {
583 if (!(opt
& CMD_OPT_FORCE
) && file
->stat
.st_mtime
&& stat(*name
, &meta
) == 0 &&
584 file
->stat
.st_mtime
< meta
.st_mtime
) {
585 vis_info_show(vis
, "WARNING: file has been changed since reading it");
588 if (!text_save_range(text
, range
, *name
)) {
589 vis_info_show(vis
, "Can't write `%s'", *name
);
593 vis_window_name(vis
->win
, *name
);
594 file
->name
= vis
->win
->file
->name
;
596 if (strcmp(file
->name
, *name
) == 0)
597 file
->stat
= text_stat(text
);
598 if (vis
->event
&& vis
->event
->file_save
)
599 vis
->event
->file_save(vis
, file
);
604 static bool cmd_saveas(Vis
*vis
, Filerange
*range
, enum CmdOpt opt
, const char *argv
[]) {
605 if (cmd_write(vis
, range
, opt
, argv
)) {
606 vis_window_name(vis
->win
, argv
[1]);
607 vis
->win
->file
->stat
= text_stat(vis
->win
->file
->text
);
613 static bool cmd_filter(Vis
*vis
, Filerange
*range
, enum CmdOpt opt
, const char *argv
[]) {
614 /* if an invalid range was given, stdin (i.e. key board input) is passed
615 * through the external command. */
616 Text
*text
= vis
->win
->file
->text
;
617 View
*view
= vis
->win
->view
;
618 int pin
[2], pout
[2], perr
[2], status
= -1;
619 bool interactive
= !text_range_valid(range
);
620 size_t pos
= view_cursor_get(view
);
624 if (pipe(pout
) == -1) {
630 if (pipe(perr
) == -1) {
638 vis
->ui
->terminal_save(vis
->ui
);
648 vis_info_show(vis
, "fork failure: %s", strerror(errno
));
650 } else if (pid
== 0) { /* child i.e filter */
652 dup2(pin
[0], STDIN_FILENO
);
655 dup2(pout
[1], STDOUT_FILENO
);
659 dup2(perr
[1], STDERR_FILENO
);
663 execl("/bin/sh", "sh", "-c", argv
[1], NULL
);
665 execvp(argv
[1], (char**)argv
+1);
666 vis_info_show(vis
, "exec failure: %s", strerror(errno
));
670 vis
->cancel_filter
= false;
676 fcntl(pout
[0], F_SETFL
, O_NONBLOCK
);
677 fcntl(perr
[0], F_SETFL
, O_NONBLOCK
);
680 *range
= (Filerange
){ .start
= pos
, .end
= pos
};
682 /* ranges which are written to the filter and read back in */
683 Filerange rout
= *range
;
684 Filerange rin
= (Filerange
){ .start
= range
->end
, .end
= range
->end
};
686 /* The general idea is the following:
689 * 2) write [range.start, range.end] to exteneral command
690 * 3) read the output of the external command and insert it after the range
691 * 4) depending on the exit status of the external command
692 * - on success: delete original range
693 * - on failure: revert to previous snapshot
695 * 2) and 3) happend in small junks
702 buffer_init(&errmsg
);
705 if (vis
->cancel_filter
) {
707 vis_info_show(vis
, "Command cancelled");
714 FD_SET(pin
[1], &wfds
);
716 FD_SET(pout
[0], &rfds
);
718 FD_SET(perr
[0], &rfds
);
720 if (select(FD_SETSIZE
, &rfds
, &wfds
, NULL
, NULL
) == -1) {
723 vis_info_show(vis
, "Select failure");
727 if (pin
[1] != -1 && FD_ISSET(pin
[1], &wfds
)) {
728 Filerange junk
= *range
;
729 if (junk
.end
> junk
.start
+ PIPE_BUF
)
730 junk
.end
= junk
.start
+ PIPE_BUF
;
731 ssize_t len
= text_write_range(text
, &junk
, pin
[1]);
734 if (text_range_size(range
) == 0) {
742 vis_info_show(vis
, "Error writing to external command");
746 if (pout
[0] != -1 && FD_ISSET(pout
[0], &rfds
)) {
748 ssize_t len
= read(pout
[0], buf
, sizeof buf
);
750 text_insert(text
, rin
.end
, buf
, len
);
752 } else if (len
== 0) {
755 } else if (errno
!= EINTR
&& errno
!= EWOULDBLOCK
) {
756 vis_info_show(vis
, "Error reading from filter stdout");
762 if (perr
[0] != -1 && FD_ISSET(perr
[0], &rfds
)) {
764 ssize_t len
= read(perr
[0], buf
, sizeof buf
);
766 buffer_append(&errmsg
, buf
, len
);
767 } else if (len
== 0) {
770 } else if (errno
!= EINTR
&& errno
!= EWOULDBLOCK
) {
771 vis_info_show(vis
, "Error reading from filter stderr");
777 } while (pin
[1] != -1 || pout
[0] != -1 || perr
[0] != -1);
786 if (waitpid(pid
, &status
, 0) == pid
&& status
== 0) {
787 text_delete_range(text
, &rout
);
790 /* make sure we have somehting to undo */
791 text_insert(text
, pos
, " ", 1);
795 view_cursor_to(view
, rout
.start
);
797 if (!vis
->cancel_filter
) {
799 vis_info_show(vis
, "Command succeded");
800 else if (errmsg
.len
> 0)
801 vis_info_show(vis
, "Command failed: %s", errmsg
.data
);
803 vis_info_show(vis
, "Command failed");
806 vis
->ui
->terminal_restore(vis
->ui
);
810 static bool cmd_earlier_later(Vis
*vis
, Filerange
*range
, enum CmdOpt opt
, const char *argv
[]) {
811 Text
*txt
= vis
->win
->file
->text
;
817 count
= strtol(argv
[1], &unit
, 10);
818 if (errno
|| unit
== argv
[1] || count
< 0) {
819 vis_info_show(vis
, "Invalid number");
824 while (*unit
&& isspace((unsigned char)*unit
))
827 case 'd': count
*= 24; /* fall through */
828 case 'h': count
*= 60; /* fall through */
829 case 'm': count
*= 60; /* fall through */
832 vis_info_show(vis
, "Unknown time specifier (use: s,m,h or d)");
836 if (argv
[0][0] == 'e')
837 count
= -count
; /* earlier, move back in time */
839 pos
= text_restore(txt
, text_state(txt
) + count
);
844 if (argv
[0][0] == 'e')
845 pos
= text_earlier(txt
, count
);
847 pos
= text_later(txt
, count
);
850 time_t state
= text_state(txt
);
852 strftime(buf
, sizeof buf
, "State from %H:%M", localtime(&state
));
853 vis_info_show(vis
, "%s", buf
);
858 bool print_keybinding(const char *key
, void *value
, void *data
) {
859 Text
*txt
= (Text
*)data
;
860 KeyBinding
*binding
= (KeyBinding
*)value
;
861 const char *desc
= binding
->alias
;
862 if (!desc
&& binding
->action
)
863 desc
= binding
->action
->help
;
864 return text_appendf(txt
, " %-15s\t%s\n", key
, desc
? desc
: "");
867 static void print_mode(Mode
*mode
, Text
*txt
, bool recursive
) {
868 if (recursive
&& mode
->parent
)
869 print_mode(mode
->parent
, txt
, recursive
);
870 map_iterate(mode
->bindings
, print_keybinding
, txt
);
873 static bool cmd_help(Vis
*vis
, Filerange
*range
, enum CmdOpt opt
, const char *argv
[]) {
874 if (!vis_window_new(vis
, NULL
))
877 Text
*txt
= vis
->win
->file
->text
;
879 text_appendf(txt
, "vis %s, compiled " __DATE__
" " __TIME__
"\n\n", VERSION
);
881 text_appendf(txt
, " Modes\n\n");
882 for (int i
= 0; i
< LENGTH(vis_modes
); i
++) {
883 Mode
*mode
= &vis_modes
[i
];
885 text_appendf(txt
, " %-15s\t%s\n", mode
->name
, mode
->help
);
888 for (int i
= 0; i
< LENGTH(vis_modes
); i
++) {
889 Mode
*mode
= &vis_modes
[i
];
890 if (mode
->isuser
&& !map_empty(mode
->bindings
)) {
891 text_appendf(txt
, "\n %s\n\n", mode
->name
);
892 print_mode(mode
, txt
, i
== VIS_MODE_NORMAL
||
893 i
== VIS_MODE_INSERT
);
897 text_appendf(txt
, "\n Text objects\n\n");
898 print_mode(&vis_modes
[VIS_MODE_TEXTOBJ
], txt
, false);
900 text_appendf(txt
, "\n Motions\n\n");
901 print_mode(&vis_modes
[VIS_MODE_MOVE
], txt
, false);
903 text_appendf(txt
, "\n :-Commands\n\n");
904 for (Command
*cmd
= cmds
; cmd
&& cmd
->name
[0]; cmd
++)
905 text_appendf(txt
, " %s\n", cmd
->name
[0]);
907 text_save(txt
, NULL
);
911 static Filepos
parse_pos(Win
*win
, char **cmd
) {
913 View
*view
= win
->view
;
914 Text
*txt
= win
->file
->text
;
915 Mark
*marks
= win
->file
->marks
;
918 pos
= text_line_begin(txt
, view_cursor_get(view
));
922 pos
= text_size(txt
);
927 if ('a' <= **cmd
&& **cmd
<= 'z')
928 pos
= text_mark_get(txt
, marks
[**cmd
- 'a']);
929 else if (**cmd
== '<')
930 pos
= text_mark_get(txt
, marks
[VIS_MARK_SELECTION_START
]);
931 else if (**cmd
== '>')
932 pos
= text_mark_get(txt
, marks
[VIS_MARK_SELECTION_END
]);
937 char *pattern_end
= strchr(*cmd
, '/');
940 *pattern_end
++ = '\0';
941 Regex
*regex
= text_regex_new();
944 if (!text_regex_compile(regex
, *cmd
, 0)) {
946 pos
= text_search_forward(txt
, view_cursor_get(view
), regex
);
948 text_regex_free(regex
);
953 CursorPos curspos
= view_cursor_getpos(view
);
954 long long line
= curspos
.line
+ strtoll(*cmd
, cmd
, 10);
957 pos
= text_pos_by_lineno(txt
, line
);
961 if ('0' <= **cmd
&& **cmd
<= '9')
962 pos
= text_pos_by_lineno(txt
, strtoul(*cmd
, cmd
, 10));
969 static Filerange
parse_range(Win
*win
, char **cmd
) {
970 Text
*txt
= win
->file
->text
;
971 Filerange r
= text_range_empty();
972 Mark
*marks
= win
->file
->marks
;
977 r
.end
= text_size(txt
);
981 r
.start
= text_mark_get(txt
, marks
[VIS_MARK_SELECTION_START
]);
982 r
.end
= text_mark_get(txt
, marks
[VIS_MARK_SELECTION_END
]);
986 r
.start
= parse_pos(win
, cmd
);
989 r
.end
= text_line_next(txt
, r
.start
);
993 r
.end
= parse_pos(win
, cmd
);
999 static Command
*lookup_cmd(Vis
*vis
, const char *name
) {
1001 if (!(vis
->cmds
= map_new()))
1004 for (Command
*cmd
= cmds
; cmd
&& cmd
->name
[0]; cmd
++) {
1005 for (const char **name
= cmd
->name
; *name
; name
++)
1006 map_put(vis
->cmds
, *name
, cmd
);
1009 return map_closest(vis
->cmds
, name
);
1012 bool vis_cmd(Vis
*vis
, const char *cmdline
) {
1013 enum CmdOpt opt
= CMD_OPT_NONE
;
1014 size_t len
= strlen(cmdline
);
1015 char *line
= malloc(len
+2);
1018 line
= strncpy(line
, cmdline
, len
+1);
1021 Filerange range
= parse_range(vis
->win
, &name
);
1022 if (!text_range_valid(&range
)) {
1023 /* if only one position was given, jump to it */
1024 if (range
.start
!= EPOS
&& !*name
) {
1025 view_cursor_to(vis
->win
->view
, range
.start
);
1031 vis_info_show(vis
, "Invalid range\n");
1036 /* skip leading white space */
1037 while (*name
== ' ')
1040 while (*param
&& isalpha(*param
))
1043 if (*param
== '!') {
1044 if (param
!= name
) {
1045 opt
|= CMD_OPT_FORCE
;
1052 memmove(param
+1, param
, strlen(param
)+1);
1053 *param
++ = '\0'; /* separate command name from parameters */
1055 Command
*cmd
= lookup_cmd(vis
, name
);
1057 vis_info_show(vis
, "Not an editor command");
1063 const char *argv
[32] = { name
};
1064 for (int i
= 1; i
< LENGTH(argv
); i
++) {
1065 while (s
&& *s
&& *s
== ' ')
1070 if (!(cmd
->opt
& CMD_OPT_ARGS
)) {
1071 /* remove trailing spaces */
1074 while (*(--s
) == ' ') *s
= '\0';
1078 if (s
&& (s
= strchr(s
, ' ')))
1080 /* strip out a single '!' argument to make ":q !" work */
1081 if (argv
[i
] && !strcmp(argv
[i
], "!")) {
1082 opt
|= CMD_OPT_FORCE
;
1087 cmd
->cmd(vis
, &range
, opt
, argv
);