From fa1eb7921e7c35a0d587a5c0da30a4c47d65a848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Andr=C3=A9=20Tanner?= Date: Fri, 6 Nov 2015 21:47:12 +0100 Subject: vis: move :-commands into their own file --- vis.c | 1210 +---------------------------------------------------------------- 1 file changed, 4 insertions(+), 1206 deletions(-) (limited to 'vis.c') diff --git a/vis.c b/vis.c index d503405..03734f7 100644 --- a/vis.c +++ b/vis.c @@ -38,178 +38,8 @@ #include "text-util.h" #include "text-motions.h" #include "text-objects.h" -#include "text-regex.h" #include "util.h" -#include "map.h" -#include "ring-buffer.h" -#include "macro.h" - - -/* a mode contains a set of key bindings which are currently valid. - * - * each mode can specify one parent mode which is consultated if a given key - * is not found in the current mode. hence the modes form a tree which is - * searched from the current mode up towards the root mode until a valid binding - * is found. - * - * if no binding is found, mode->input(...) is called and the user entered - * keys are passed as argument. this is used to change the document content. - */ -typedef struct Mode Mode; -struct Mode { - Mode *parent; /* if no match is found in this mode, search will continue there */ - Map *bindings; - const char *name; /* descriptive, user facing name of the mode */ - const char *status; /* name displayed in the window status bar */ - const char *help; /* short description used by :help */ - bool isuser; /* whether this is a user or internal mode */ - void (*enter)(Vis*, Mode *old); /* called right before the mode becomes active */ - void (*leave)(Vis*, Mode *new); /* called right before the mode becomes inactive */ - void (*input)(Vis*, const char*, size_t); /* called whenever a key is not found in this mode and all its parent modes */ - void (*idle)(Vis*); /* called whenever a certain idle time i.e. without any user input elapsed */ - time_t idle_timeout; /* idle time in seconds after which the registered function will be called */ - bool visual; /* whether text selection is possible in this mode */ -}; - - - -typedef struct { - int count; /* how many times should the command be executed? */ - Register *reg; /* always non-NULL, set to a default register */ - Filerange range; /* which part of the file should be affected by the operator */ - size_t pos; /* at which byte from the start of the file should the operation start? */ - size_t newpos; /* new position after motion or EPOS if none given */ - bool linewise; /* should the changes always affect whole lines? */ - const Arg *arg; /* arbitrary arguments */ -} OperatorContext; - -typedef struct { - size_t (*func)(Vis*, Text*, OperatorContext*); /* operator logic, returns new cursor position */ -} Operator; - -typedef struct { - /* TODO: merge types / use union to save space */ - size_t (*cur)(Cursor*); /* a movement based on current window content from view.h */ - size_t (*txt)(Text*, size_t pos); /* a movement form text-motions.h */ - size_t (*file)(Vis*, File*, size_t pos); - size_t (*vis)(Vis*, Text*, size_t pos); - size_t (*view)(Vis*, View*); - size_t (*win)(Vis*, Win*, size_t pos); - enum { - LINEWISE = 1 << 0, - CHARWISE = 1 << 1, - INCLUSIVE = 1 << 2, - EXCLUSIVE = 1 << 3, - IDEMPOTENT = 1 << 4, - JUMP = 1 << 5, - } type; - int count; -} Movement; - -typedef struct { - Filerange (*range)(Text*, size_t pos); /* a text object from text-objects.h */ - enum { - INNER, - OUTER, - } type; -} TextObject; - -typedef struct { /** collects all information until an operator is executed */ - int count; - enum VisMotionType type; - const Operator *op; - const Movement *movement; - const TextObject *textobj; - const Macro *macro; - Register *reg; - enum VisMark mark; - Arg arg; -} Action; - -struct File { - Text *text; - const char *name; - volatile sig_atomic_t truncated; - bool is_stdin; - struct stat stat; - int refcount; - Mark marks[VIS_MARK_INVALID]; - File *next, *prev; -}; - -typedef struct { - time_t state; /* state of the text, used to invalidate change list */ - size_t index; /* #number of changes */ - size_t pos; /* where the current change occured */ -} ChangeList; - -struct Win { - Vis *editor; /* editor instance to which this window belongs */ - UiWin *ui; - File *file; /* file being displayed in this window */ - View *view; /* currently displayed part of underlying text */ - ViewEvent events; - RingBuffer *jumplist; /* LRU jump management */ - ChangeList changelist; /* state for iterating through least recently changes */ - Win *prev, *next; /* neighbouring windows */ -}; - -struct Vis { - Ui *ui; - File *files; - Win *windows; /* list of windows */ - Win *win; /* currently active window */ - Syntax *syntaxes; /* NULL terminated array of syntax definitions */ - Register registers[VIS_REGISTER_INVALID]; /* register used for copy and paste */ - Macro macros[VIS_MACRO_INVALID]; /* recorded macros */ - Macro *recording, *last_recording;/* currently and least recently recorded macro */ - Macro *macro_operator; - Win *prompt; /* 1-line height window to get user input */ - Win *prompt_window; /* window which was focused before prompt was shown */ - char prompt_type; /* command ':' or search '/','?' prompt */ - Regex *search_pattern; /* last used search pattern */ - char search_char[8]; /* last used character to search for via 'f', 'F', 't', 'T' */ - int last_totill; /* last to/till movement used for ';' and ',' */ - int tabwidth; /* how many spaces should be used to display a tab */ - bool expandtab; /* whether typed tabs should be converted to spaces */ - bool autoindent; /* whether indentation should be copied from previous line on newline */ - Map *cmds; /* ":"-commands, used for unique prefix queries */ - Map *options; /* ":set"-options */ - Buffer input_queue; /* holds pending input keys */ - - Action action; /* current action which is in progress */ - Action action_prev; /* last operator action used by the repeat '.' key */ - Mode *mode; /* currently active mode, used to search for keybindings */ - Mode *mode_prev; /* previsouly active user mode */ - Mode *mode_before_prompt; /* user mode which was active before entering prompt */ - volatile bool running; /* exit main loop once this becomes false */ - int exit_status; - volatile sig_atomic_t cancel_filter; /* abort external command */ - volatile sig_atomic_t sigbus; - sigjmp_buf sigbus_jmpbuf; - Map *actions; /* built in special editor keys / commands */ - Buffer *keys; /* if non-NULL we are currently handling keys from this buffer, - * points to either the input_queue or a macro */ -}; - -/* TODO make part of struct Vis? */ -static Mode vis_modes[VIS_MODE_LAST]; - -enum CmdOpt { /* option flags for command definitions */ - CMD_OPT_NONE, /* no option (default value) */ - CMD_OPT_FORCE, /* whether the command can be forced by appending '!' */ - CMD_OPT_ARGS, /* whether the command line should be parsed in to space - * separated arguments to placed into argv, otherwise argv[1] - * will contain the remaining command line unmodified */ -}; - -typedef struct { /* command definitions for the ':'-prompt */ - const char *name[3]; /* name and optional alias for the command */ - /* command logic called with a NULL terminated array of arguments. - * argv[0] will be the command name */ - bool (*cmd)(Vis*, Filerange*, enum CmdOpt opt, const char *argv[]); - enum CmdOpt opt; /* command option flags */ -} Command; +#include "vis-core.h" static void mode_set(Vis *vis, Mode *new_mode); static Mode *mode_get(Vis *vis, enum VisMode mode); @@ -281,7 +111,7 @@ static File *file_new(Vis *vis, const char *filename) { return file; } -static void window_name(Win *win, const char *filename) { +void vis_window_name(Win *win, const char *filename) { File *file = win->file; if (filename != file->name) { free((char*)file->name); @@ -321,10 +151,6 @@ static void window_selection_changed(void *win, Filerange *sel) { } } -static void windows_arrange(Vis *vis, enum UiLayout layout) { - vis->ui->arrange(vis->ui, layout); -} - static void window_free(Win *win) { if (!win) return; @@ -420,14 +246,6 @@ static int tabwidth_get(Vis *vis) { return vis->tabwidth; } -static void tabwidth_set(Vis *vis, int tabwidth) { - if (tabwidth < 1 || tabwidth > 8) - return; - for (Win *win = vis->windows; win; win = win->next) - view_tabwidth_set(win->view, tabwidth); - vis->tabwidth = tabwidth; -} - bool vis_syntax_load(Vis *vis, Syntax *syntaxes) { bool success = true; vis->syntaxes = syntaxes; @@ -486,7 +304,7 @@ bool vis_window_new(Vis *vis, const char *filename) { return false; } - window_name(win, filename); + vis_window_name(win, filename); vis_draw(vis); return true; @@ -800,50 +618,6 @@ static TextObject textobjs[] = { }; -/** commands to enter at the ':'-prompt */ -/* set various runtime options */ -static bool cmd_set(Vis*, Filerange*, enum CmdOpt, const char *argv[]); -/* for each argument create a new window and open the corresponding file */ -static bool cmd_open(Vis*, Filerange*, enum CmdOpt, const char *argv[]); -/* close current window (discard modifications if forced ) and open argv[1], - * if no argv[1] is given re-read to current file from disk */ -static bool cmd_edit(Vis*, Filerange*, enum CmdOpt, const char *argv[]); -/* close the current window, discard modifications if forced */ -static bool cmd_quit(Vis*, Filerange*, enum CmdOpt, const char *argv[]); -/* close all windows which show current file, discard modifications if forced */ -static bool cmd_bdelete(Vis*, Filerange*, enum CmdOpt, const char *argv[]); -/* close all windows, exit editor, discard modifications if forced */ -static bool cmd_qall(Vis*, Filerange*, enum CmdOpt, const char *argv[]); -/* for each argument try to insert the file content at current cursor postion */ -static bool cmd_read(Vis*, Filerange*, enum CmdOpt, const char *argv[]); -static bool cmd_substitute(Vis*, Filerange*, enum CmdOpt, const char *argv[]); -/* if no argument are given, split the current window horizontally, - * otherwise open the file */ -static bool cmd_split(Vis*, Filerange*, enum CmdOpt, const char *argv[]); -/* if no argument are given, split the current window vertically, - * otherwise open the file */ -static bool cmd_vsplit(Vis*, Filerange*, enum CmdOpt, const char *argv[]); -/* create a new empty window and arrange all windows either horizontally or vertically */ -static bool cmd_new(Vis*, Filerange*, enum CmdOpt, const char *argv[]); -static bool cmd_vnew(Vis*, Filerange*, enum CmdOpt, const char *argv[]); -/* save the file displayed in the current window and close it */ -static bool cmd_wq(Vis*, Filerange*, enum CmdOpt, const char *argv[]); -/* save the file displayed in the current window if it was changvis, then close the window */ -static bool cmd_xit(Vis*, Filerange*, enum CmdOpt, const char *argv[]); -/* save the file displayed in the current window to the name given. - * do not change internal filname association. further :w commands - * without arguments will still write to the old filename */ -static bool cmd_write(Vis*, Filerange*, enum CmdOpt, const char *argv[]); -/* save the file displayed in the current window to the name given, - * associate the new name with the buffer. further :w commands - * without arguments will write to the new filename */ -static bool cmd_saveas(Vis*, Filerange*, enum CmdOpt, const char *argv[]); -/* filter range through external program argv[1] */ -static bool cmd_filter(Vis*, Filerange*, enum CmdOpt, const char *argv[]); -/* switch to the previous/next saved state of the text, chronologically */ -static bool cmd_earlier_later(Vis*, Filerange*, enum CmdOpt, const char *argv[]); -/* dump current key bindings */ -static bool cmd_help(Vis*, Filerange*, enum CmdOpt, const char *argv[]); static void action_reset(Vis*, Action *a); @@ -986,7 +760,7 @@ static void vis_mode_replace_input(Vis *vis, const char *str, size_t len) { * VISUAL-LINE */ -static Mode vis_modes[] = { +Mode vis_modes[] = { [VIS_MODE_BASIC] = { .name = "BASIC", .parent = NULL, @@ -1662,982 +1436,6 @@ static void mode_set(Vis *vis, Mode *new_mode) { vis->win->ui->draw_status(vis->win->ui); } -/** ':'-command implementations */ - - -/* command recognized at the ':'-prompt. commands are found using a unique - * prefix match. that is if a command should be available under an abbreviation - * which is a prefix for another command it has to be added as an alias. the - * long human readable name should always come first */ -static Command cmds[] = { - /* command name / optional alias, function, options */ - { { "bdelete" }, cmd_bdelete, CMD_OPT_FORCE }, - { { "edit" }, cmd_edit, CMD_OPT_FORCE }, - { { "help" }, cmd_help, CMD_OPT_NONE }, - { { "new" }, cmd_new, CMD_OPT_NONE }, - { { "open" }, cmd_open, CMD_OPT_NONE }, - { { "qall" }, cmd_qall, CMD_OPT_FORCE }, - { { "quit", "q" }, cmd_quit, CMD_OPT_FORCE }, - { { "read", }, cmd_read, CMD_OPT_FORCE }, - { { "saveas" }, cmd_saveas, CMD_OPT_FORCE }, - { { "set", }, cmd_set, CMD_OPT_ARGS }, - { { "split" }, cmd_split, CMD_OPT_NONE }, - { { "substitute", "s" }, cmd_substitute, CMD_OPT_NONE }, - { { "vnew" }, cmd_vnew, CMD_OPT_NONE }, - { { "vsplit", }, cmd_vsplit, CMD_OPT_NONE }, - { { "wq", }, cmd_wq, CMD_OPT_FORCE }, - { { "write", "w" }, cmd_write, CMD_OPT_FORCE }, - { { "xit", }, cmd_xit, CMD_OPT_FORCE }, - { { "earlier" }, cmd_earlier_later, CMD_OPT_NONE }, - { { "later" }, cmd_earlier_later, CMD_OPT_NONE }, - { { "!", }, cmd_filter, CMD_OPT_NONE }, - { /* array terminator */ }, -}; - -/* parse human-readable boolean value in s. If successful, store the result in - * outval and return true. Else return false and leave outval alone. */ -static bool parse_bool(const char *s, bool *outval) { - for (const char **t = (const char*[]){"1", "true", "yes", "on", NULL}; *t; t++) { - if (!strcasecmp(s, *t)) { - *outval = true; - return true; - } - } - for (const char **f = (const char*[]){"0", "false", "no", "off", NULL}; *f; f++) { - if (!strcasecmp(s, *f)) { - *outval = false; - return true; - } - } - return false; -} - -static bool cmd_set(Vis *vis, Filerange *range, enum CmdOpt cmdopt, const char *argv[]) { - - typedef struct { - const char *names[3]; - enum { - OPTION_TYPE_STRING, - OPTION_TYPE_BOOL, - OPTION_TYPE_NUMBER, - } type; - bool optional; - int index; - } OptionDef; - - enum { - OPTION_AUTOINDENT, - OPTION_EXPANDTAB, - OPTION_TABWIDTH, - OPTION_SYNTAX, - OPTION_SHOW, - OPTION_NUMBER, - OPTION_NUMBER_RELATIVE, - }; - - /* definitions have to be in the same order as the enum above */ - static OptionDef options[] = { - [OPTION_AUTOINDENT] = { { "autoindent", "ai" }, OPTION_TYPE_BOOL }, - [OPTION_EXPANDTAB] = { { "expandtab", "et" }, OPTION_TYPE_BOOL }, - [OPTION_TABWIDTH] = { { "tabwidth", "tw" }, OPTION_TYPE_NUMBER }, - [OPTION_SYNTAX] = { { "syntax" }, OPTION_TYPE_STRING, true }, - [OPTION_SHOW] = { { "show" }, OPTION_TYPE_STRING }, - [OPTION_NUMBER] = { { "numbers", "nu" }, OPTION_TYPE_BOOL }, - [OPTION_NUMBER_RELATIVE] = { { "relativenumbers", "rnu" }, OPTION_TYPE_BOOL }, - }; - - if (!vis->options) { - if (!(vis->options = map_new())) - return false; - for (int i = 0; i < LENGTH(options); i++) { - options[i].index = i; - for (const char **name = options[i].names; *name; name++) { - if (!map_put(vis->options, *name, &options[i])) - return false; - } - } - } - - if (!argv[1]) { - vis_info_show(vis, "Expecting: set option [value]"); - return false; - } - - Arg arg; - bool invert = false; - OptionDef *opt = NULL; - - if (!strncasecmp(argv[1], "no", 2)) { - opt = map_closest(vis->options, argv[1]+2); - if (opt && opt->type == OPTION_TYPE_BOOL) - invert = true; - else - opt = NULL; - } - - if (!opt) - opt = map_closest(vis->options, argv[1]); - if (!opt) { - vis_info_show(vis, "Unknown option: `%s'", argv[1]); - return false; - } - - switch (opt->type) { - case OPTION_TYPE_STRING: - if (!opt->optional && !argv[2]) { - vis_info_show(vis, "Expecting string option value"); - return false; - } - break; - case OPTION_TYPE_BOOL: - if (!argv[2]) { - arg.b = true; - } else if (!parse_bool(argv[2], &arg.b)) { - vis_info_show(vis, "Expecting boolean option value not: `%s'", argv[2]); - return false; - } - if (invert) - arg.b = !arg.b; - break; - case OPTION_TYPE_NUMBER: - if (!argv[2]) { - vis_info_show(vis, "Expecting number"); - return false; - } - /* TODO: error checking? long type */ - arg.i = strtoul(argv[2], NULL, 10); - break; - } - - switch (opt->index) { - case OPTION_EXPANDTAB: - vis->expandtab = arg.b; - break; - case OPTION_AUTOINDENT: - vis->autoindent = arg.b; - break; - case OPTION_TABWIDTH: - tabwidth_set(vis, arg.i); - break; - case OPTION_SYNTAX: - if (!argv[2]) { - Syntax *syntax = view_syntax_get(vis->win->view); - if (syntax) - vis_info_show(vis, "Syntax definition in use: `%s'", syntax->name); - else - vis_info_show(vis, "No syntax definition in use"); - return true; - } - - for (Syntax *syntax = vis->syntaxes; syntax && syntax->name; syntax++) { - if (!strcasecmp(syntax->name, argv[2])) { - view_syntax_set(vis->win->view, syntax); - return true; - } - } - - if (parse_bool(argv[2], &arg.b) && !arg.b) - view_syntax_set(vis->win->view, NULL); - else - vis_info_show(vis, "Unknown syntax definition: `%s'", argv[2]); - break; - case OPTION_SHOW: - if (!argv[2]) { - vis_info_show(vis, "Expecting: spaces, tabs, newlines"); - return false; - } - char *keys[] = { "spaces", "tabs", "newlines" }; - int values[] = { - UI_OPTION_SYMBOL_SPACE, - UI_OPTION_SYMBOL_TAB|UI_OPTION_SYMBOL_TAB_FILL, - UI_OPTION_SYMBOL_EOL, - }; - int flags = view_options_get(vis->win->view); - for (const char **args = &argv[2]; *args; args++) { - for (int i = 0; i < LENGTH(keys); i++) { - if (strcmp(*args, keys[i]) == 0) { - flags |= values[i]; - } else if (strstr(*args, keys[i]) == *args) { - bool show; - const char *v = *args + strlen(keys[i]); - if (*v == '=' && parse_bool(v+1, &show)) { - if (show) - flags |= values[i]; - else - flags &= ~values[i]; - } - } - } - } - view_options_set(vis->win->view, flags); - break; - case OPTION_NUMBER: { - enum UiOption opt = view_options_get(vis->win->view); - if (arg.b) { - opt &= ~UI_OPTION_LINE_NUMBERS_RELATIVE; - opt |= UI_OPTION_LINE_NUMBERS_ABSOLUTE; - } else { - opt &= ~UI_OPTION_LINE_NUMBERS_ABSOLUTE; - } - view_options_set(vis->win->view, opt); - break; - } - case OPTION_NUMBER_RELATIVE: { - enum UiOption opt = view_options_get(vis->win->view); - if (arg.b) { - opt &= ~UI_OPTION_LINE_NUMBERS_ABSOLUTE; - opt |= UI_OPTION_LINE_NUMBERS_RELATIVE; - } else { - opt &= ~UI_OPTION_LINE_NUMBERS_RELATIVE; - } - view_options_set(vis->win->view, opt); - break; - } - } - - return true; -} - -static bool is_file_pattern(const char *pattern) { - return pattern && (strcmp(pattern, ".") == 0 || strchr(pattern, '*') || - strchr(pattern, '[') || strchr(pattern, '{')); -} - -static const char *file_open_dialog(Vis *vis, const char *pattern) { - if (!is_file_pattern(pattern)) - return pattern; - /* this is a bit of a hack, we temporarily replace the text/view of the active - * window such that we can use cmd_filter as is */ - char vis_open[512]; - static char filename[PATH_MAX]; - Filerange range = text_range_empty(); - Win *win = vis->win; - File *file = win->file; - Text *txt_orig = file->text; - View *view_orig = win->view; - Text *txt = text_load(NULL); - View *view = view_new(txt, NULL); - filename[0] = '\0'; - snprintf(vis_open, sizeof(vis_open)-1, "vis-open %s", pattern ? pattern : ""); - - if (!txt || !view) - goto out; - win->view = view; - file->text = txt; - - if (cmd_filter(vis, &range, CMD_OPT_NONE, (const char *[]){ "open", vis_open, NULL })) { - size_t len = text_size(txt); - if (len >= sizeof(filename)) - len = 0; - if (len > 0) - text_bytes_get(txt, 0, --len, filename); - filename[len] = '\0'; - } - -out: - view_free(view); - text_free(txt); - win->view = view_orig; - file->text = txt_orig; - return filename[0] ? filename : NULL; -} - -static bool openfiles(Vis *vis, const char **files) { - for (; *files; files++) { - const char *file = file_open_dialog(vis, *files); - if (!file) - return false; - errno = 0; - if (!vis_window_new(vis, file)) { - vis_info_show(vis, "Could not open `%s' %s", file, - errno ? strerror(errno) : ""); - return false; - } - } - return true; -} - -static bool cmd_open(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { - if (!argv[1]) - return vis_window_new(vis, NULL); - return openfiles(vis, &argv[1]); -} - -static bool is_view_closeable(Win *win) { - if (!text_modified(win->file->text)) - return true; - return win->file->refcount > 1; -} - -static void info_unsaved_changes(Vis *vis) { - vis_info_show(vis, "No write since last change (add ! to override)"); -} - -static bool cmd_edit(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { - Win *oldwin = vis->win; - if (!(opt & CMD_OPT_FORCE) && !is_view_closeable(oldwin)) { - info_unsaved_changes(vis); - return false; - } - if (!argv[1]) - return vis_window_reload(oldwin); - if (!openfiles(vis, &argv[1])) - return false; - if (vis->win != oldwin) - vis_window_close(oldwin); - return vis->win != oldwin; -} - -static bool cmd_quit(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { - if (!(opt & CMD_OPT_FORCE) && !is_view_closeable(vis->win)) { - info_unsaved_changes(vis); - return false; - } - vis_window_close(vis->win); - if (!vis->windows) - vis_exit(vis, EXIT_SUCCESS); - return true; -} - -static bool cmd_xit(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { - if (text_modified(vis->win->file->text) && !cmd_write(vis, range, opt, argv)) { - if (!(opt & CMD_OPT_FORCE)) - return false; - } - return cmd_quit(vis, range, opt, argv); -} - -static bool cmd_bdelete(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { - Text *txt = vis->win->file->text; - if (text_modified(txt) && !(opt & CMD_OPT_FORCE)) { - info_unsaved_changes(vis); - return false; - } - for (Win *next, *win = vis->windows; win; win = next) { - next = win->next; - if (win->file->text == txt) - vis_window_close(win); - } - if (!vis->windows) - vis_exit(vis, EXIT_SUCCESS); - return true; -} - -static bool cmd_qall(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { - for (Win *next, *win = vis->windows; win; win = next) { - next = win->next; - if (!text_modified(vis->win->file->text) || (opt & CMD_OPT_FORCE)) - vis_window_close(win); - } - if (!vis->windows) - vis_exit(vis, EXIT_SUCCESS); - else - info_unsaved_changes(vis); - return vis->windows == NULL; -} - -static bool cmd_read(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { - char cmd[255]; - - if (!argv[1]) { - vis_info_show(vis, "Filename or command expected"); - return false; - } - - bool iscmd = (opt & CMD_OPT_FORCE) || argv[1][0] == '!'; - const char *arg = argv[1]+(argv[1][0] == '!'); - snprintf(cmd, sizeof cmd, "%s%s", iscmd ? "" : "cat ", arg); - - size_t pos = view_cursor_get(vis->win->view); - if (!text_range_valid(range)) - *range = (Filerange){ .start = pos, .end = pos }; - Filerange delete = *range; - range->start = range->end; - - bool ret = cmd_filter(vis, range, opt, (const char*[]){ argv[0], "sh", "-c", cmd, NULL}); - if (ret) - text_delete_range(vis->win->file->text, &delete); - return ret; -} - -static bool cmd_substitute(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { - char pattern[255]; - if (!text_range_valid(range)) - *range = (Filerange){ .start = 0, .end = text_size(vis->win->file->text) }; - snprintf(pattern, sizeof pattern, "s%s", argv[1]); - return cmd_filter(vis, range, opt, (const char*[]){ argv[0], "sed", pattern, NULL}); -} - -static bool cmd_split(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { - enum UiOption options = view_options_get(vis->win->view); - windows_arrange(vis, UI_LAYOUT_HORIZONTAL); - if (!argv[1]) - return vis_window_split(vis->win); - bool ret = openfiles(vis, &argv[1]); - view_options_set(vis->win->view, options); - return ret; -} - -static bool cmd_vsplit(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { - enum UiOption options = view_options_get(vis->win->view); - windows_arrange(vis, UI_LAYOUT_VERTICAL); - if (!argv[1]) - return vis_window_split(vis->win); - bool ret = openfiles(vis, &argv[1]); - view_options_set(vis->win->view, options); - return ret; -} - -static bool cmd_new(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { - windows_arrange(vis, UI_LAYOUT_HORIZONTAL); - return vis_window_new(vis, NULL); -} - -static bool cmd_vnew(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { - windows_arrange(vis, UI_LAYOUT_VERTICAL); - return vis_window_new(vis, NULL); -} - -static bool cmd_wq(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { - if (cmd_write(vis, range, opt, argv)) - return cmd_quit(vis, range, opt, argv); - return false; -} - -static bool cmd_write(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { - File *file = vis->win->file; - Text *text = file->text; - if (!text_range_valid(range)) - *range = (Filerange){ .start = 0, .end = text_size(text) }; - if (!argv[1]) - argv[1] = file->name; - if (!argv[1]) { - if (file->is_stdin) { - if (strchr(argv[0], 'q')) { - ssize_t written = text_write_range(text, range, STDOUT_FILENO); - if (written == -1 || (size_t)written != text_range_size(range)) { - vis_info_show(vis, "Can not write to stdout"); - return false; - } - /* make sure the file is marked as saved i.e. not modified */ - text_save_range(text, range, NULL); - return true; - } - vis_info_show(vis, "No filename given, use 'wq' to write to stdout"); - return false; - } - vis_info_show(vis, "Filename expected"); - return false; - } - for (const char **name = &argv[1]; *name; name++) { - struct stat meta; - if (!(opt & CMD_OPT_FORCE) && file->stat.st_mtime && stat(*name, &meta) == 0 && - file->stat.st_mtime < meta.st_mtime) { - vis_info_show(vis, "WARNING: file has been changed since reading it"); - return false; - } - if (!text_save_range(text, range, *name)) { - vis_info_show(vis, "Can't write `%s'", *name); - return false; - } - if (!file->name) { - window_name(vis->win, *name); - file->name = vis->win->file->name; - } - if (strcmp(file->name, *name) == 0) - file->stat = text_stat(text); - } - return true; -} - -static bool cmd_saveas(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { - if (cmd_write(vis, range, opt, argv)) { - window_name(vis->win, argv[1]); - vis->win->file->stat = text_stat(vis->win->file->text); - return true; - } - return false; -} - -static bool cmd_filter(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { - /* if an invalid range was given, stdin (i.e. key board input) is passed - * through the external command. */ - Text *text = vis->win->file->text; - View *view = vis->win->view; - int pin[2], pout[2], perr[2], status = -1; - bool interactive = !text_range_valid(range); - size_t pos = view_cursor_get(view); - - if (pipe(pin) == -1) - return false; - if (pipe(pout) == -1) { - close(pin[0]); - close(pin[1]); - return false; - } - - if (pipe(perr) == -1) { - close(pin[0]); - close(pin[1]); - close(pout[0]); - close(pout[1]); - return false; - } - - vis->ui->terminal_save(vis->ui); - pid_t pid = fork(); - - if (pid == -1) { - close(pin[0]); - close(pin[1]); - close(pout[0]); - close(pout[1]); - close(perr[0]); - close(perr[1]); - vis_info_show(vis, "fork failure: %s", strerror(errno)); - return false; - } else if (pid == 0) { /* child i.e filter */ - if (!interactive) - dup2(pin[0], STDIN_FILENO); - close(pin[0]); - close(pin[1]); - dup2(pout[1], STDOUT_FILENO); - close(pout[1]); - close(pout[0]); - if (!interactive) - dup2(perr[1], STDERR_FILENO); - close(perr[0]); - close(perr[1]); - if (!argv[2]) - execl("/bin/sh", "sh", "-c", argv[1], NULL); - else - execvp(argv[1], (char**)argv+1); - vis_info_show(vis, "exec failure: %s", strerror(errno)); - exit(EXIT_FAILURE); - } - - vis->cancel_filter = false; - - close(pin[0]); - close(pout[1]); - close(perr[1]); - - fcntl(pout[0], F_SETFL, O_NONBLOCK); - fcntl(perr[0], F_SETFL, O_NONBLOCK); - - if (interactive) - *range = (Filerange){ .start = pos, .end = pos }; - - /* ranges which are written to the filter and read back in */ - Filerange rout = *range; - Filerange rin = (Filerange){ .start = range->end, .end = range->end }; - - /* The general idea is the following: - * - * 1) take a snapshot - * 2) write [range.start, range.end] to exteneral command - * 3) read the output of the external command and insert it after the range - * 4) depending on the exit status of the external command - * - on success: delete original range - * - on failure: revert to previous snapshot - * - * 2) and 3) happend in small junks - */ - - text_snapshot(text); - - fd_set rfds, wfds; - Buffer errmsg; - buffer_init(&errmsg); - - do { - if (vis->cancel_filter) { - kill(-pid, SIGTERM); - vis_info_show(vis, "Command cancelled"); - break; - } - - FD_ZERO(&rfds); - FD_ZERO(&wfds); - if (pin[1] != -1) - FD_SET(pin[1], &wfds); - if (pout[0] != -1) - FD_SET(pout[0], &rfds); - if (perr[0] != -1) - FD_SET(perr[0], &rfds); - - if (select(FD_SETSIZE, &rfds, &wfds, NULL, NULL) == -1) { - if (errno == EINTR) - continue; - vis_info_show(vis, "Select failure"); - break; - } - - if (pin[1] != -1 && FD_ISSET(pin[1], &wfds)) { - Filerange junk = *range; - if (junk.end > junk.start + PIPE_BUF) - junk.end = junk.start + PIPE_BUF; - ssize_t len = text_write_range(text, &junk, pin[1]); - if (len > 0) { - range->start += len; - if (text_range_size(range) == 0) { - close(pout[1]); - pout[1] = -1; - } - } else { - close(pin[1]); - pin[1] = -1; - if (len == -1) - vis_info_show(vis, "Error writing to external command"); - } - } - - if (pout[0] != -1 && FD_ISSET(pout[0], &rfds)) { - char buf[BUFSIZ]; - ssize_t len = read(pout[0], buf, sizeof buf); - if (len > 0) { - text_insert(text, rin.end, buf, len); - rin.end += len; - } else if (len == 0) { - close(pout[0]); - pout[0] = -1; - } else if (errno != EINTR && errno != EWOULDBLOCK) { - vis_info_show(vis, "Error reading from filter stdout"); - close(pout[0]); - pout[0] = -1; - } - } - - if (perr[0] != -1 && FD_ISSET(perr[0], &rfds)) { - char buf[BUFSIZ]; - ssize_t len = read(perr[0], buf, sizeof buf); - if (len > 0) { - buffer_append(&errmsg, buf, len); - } else if (len == 0) { - close(perr[0]); - perr[0] = -1; - } else if (errno != EINTR && errno != EWOULDBLOCK) { - vis_info_show(vis, "Error reading from filter stderr"); - close(pout[0]); - pout[0] = -1; - } - } - - } while (pin[1] != -1 || pout[0] != -1 || perr[0] != -1); - - if (pin[1] != -1) - close(pin[1]); - if (pout[0] != -1) - close(pout[0]); - if (perr[0] != -1) - close(perr[0]); - - if (waitpid(pid, &status, 0) == pid && status == 0) { - text_delete_range(text, &rout); - text_snapshot(text); - } else { - /* make sure we have somehting to undo */ - text_insert(text, pos, " ", 1); - text_undo(text); - } - - view_cursor_to(view, rout.start); - - if (!vis->cancel_filter) { - if (status == 0) - vis_info_show(vis, "Command succeded"); - else if (errmsg.len > 0) - vis_info_show(vis, "Command failed: %s", errmsg.data); - else - vis_info_show(vis, "Command failed"); - } - - vis->ui->terminal_restore(vis->ui); - return status == 0; -} - -static bool cmd_earlier_later(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { - Text *txt = vis->win->file->text; - char *unit = ""; - long count = 1; - size_t pos = EPOS; - if (argv[1]) { - errno = 0; - count = strtol(argv[1], &unit, 10); - if (errno || unit == argv[1] || count < 0) { - vis_info_show(vis, "Invalid number"); - return false; - } - - if (*unit) { - while (*unit && isspace((unsigned char)*unit)) - unit++; - switch (*unit) { - case 'd': count *= 24; /* fall through */ - case 'h': count *= 60; /* fall through */ - case 'm': count *= 60; /* fall through */ - case 's': break; - default: - vis_info_show(vis, "Unknown time specifier (use: s,m,h or d)"); - return false; - } - - if (argv[0][0] == 'e') - count = -count; /* earlier, move back in time */ - - pos = text_restore(txt, text_state(txt) + count); - } - } - - if (!*unit) { - if (argv[0][0] == 'e') - pos = text_earlier(txt, count); - else - pos = text_later(txt, count); - } - - time_t state = text_state(txt); - char buf[32]; - strftime(buf, sizeof buf, "State from %H:%M", localtime(&state)); - vis_info_show(vis, "%s", buf); - - return pos != EPOS; -} - -bool print_keybinding(const char *key, void *value, void *data) { - Text *txt = (Text*)data; - KeyBinding *binding = (KeyBinding*)value; - const char *desc = binding->alias; - if (!desc && binding->action) - desc = binding->action->help; - return text_appendf(txt, " %-15s\t%s\n", key, desc ? desc : ""); -} - -static void print_mode(Mode *mode, Text *txt, bool recursive) { - if (recursive && mode->parent) - print_mode(mode->parent, txt, recursive); - map_iterate(mode->bindings, print_keybinding, txt); -} - -static bool cmd_help(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { - if (!vis_window_new(vis, NULL)) - return false; - - Text *txt = vis->win->file->text; - - text_appendf(txt, "vis %s, compiled " __DATE__ " " __TIME__ "\n\n", VERSION); - - text_appendf(txt, " Modes\n\n"); - for (int i = 0; i < LENGTH(vis_modes); i++) { - Mode *mode = &vis_modes[i]; - if (mode->help) - text_appendf(txt, " %-15s\t%s\n", mode->name, mode->help); - } - - for (int i = 0; i < LENGTH(vis_modes); i++) { - Mode *mode = &vis_modes[i]; - if (mode->isuser && !map_empty(mode->bindings)) { - text_appendf(txt, "\n %s\n\n", mode->name); - print_mode(mode, txt, i == VIS_MODE_NORMAL || - i == VIS_MODE_INSERT); - } - } - - text_appendf(txt, "\n Text objects\n\n"); - print_mode(&vis_modes[VIS_MODE_TEXTOBJ], txt, false); - - text_appendf(txt, "\n Motions\n\n"); - print_mode(&vis_modes[VIS_MODE_MOVE], txt, false); - - text_appendf(txt, "\n :-Commands\n\n"); - for (Command *cmd = cmds; cmd && cmd->name[0]; cmd++) - text_appendf(txt, " %s\n", cmd->name[0]); - - text_save(txt, NULL); - return true; -} - -static Filepos parse_pos(Win *win, char **cmd) { - size_t pos = EPOS; - View *view = win->view; - Text *txt = win->file->text; - Mark *marks = win->file->marks; - switch (**cmd) { - case '.': - pos = text_line_begin(txt, view_cursor_get(view)); - (*cmd)++; - break; - case '$': - pos = text_size(txt); - (*cmd)++; - break; - case '\'': - (*cmd)++; - if ('a' <= **cmd && **cmd <= 'z') - pos = text_mark_get(txt, marks[**cmd - 'a']); - else if (**cmd == '<') - pos = text_mark_get(txt, marks[MARK_SELECTION_START]); - else if (**cmd == '>') - pos = text_mark_get(txt, marks[MARK_SELECTION_END]); - (*cmd)++; - break; - case '/': - (*cmd)++; - char *pattern_end = strchr(*cmd, '/'); - if (!pattern_end) - return EPOS; - *pattern_end++ = '\0'; - Regex *regex = text_regex_new(); - if (!regex) - return EPOS; - if (!text_regex_compile(regex, *cmd, 0)) { - *cmd = pattern_end; - pos = text_search_forward(txt, view_cursor_get(view), regex); - } - text_regex_free(regex); - break; - case '+': - case '-': - { - CursorPos curspos = view_cursor_getpos(view); - long long line = curspos.line + strtoll(*cmd, cmd, 10); - if (line < 0) - line = 0; - pos = text_pos_by_lineno(txt, line); - break; - } - default: - if ('0' <= **cmd && **cmd <= '9') - pos = text_pos_by_lineno(txt, strtoul(*cmd, cmd, 10)); - break; - } - - return pos; -} - -static Filerange parse_range(Win *win, char **cmd) { - Text *txt = win->file->text; - Filerange r = text_range_empty(); - Mark *marks = win->file->marks; - char start = **cmd; - switch (**cmd) { - case '%': - r.start = 0; - r.end = text_size(txt); - (*cmd)++; - break; - case '*': - r.start = text_mark_get(txt, marks[MARK_SELECTION_START]); - r.end = text_mark_get(txt, marks[MARK_SELECTION_END]); - (*cmd)++; - break; - default: - r.start = parse_pos(win, cmd); - if (**cmd != ',') { - if (start == '.') - r.end = text_line_next(txt, r.start); - return r; - } - (*cmd)++; - r.end = parse_pos(win, cmd); - break; - } - return r; -} - -static Command *lookup_cmd(Vis *vis, const char *name) { - if (!vis->cmds) { - if (!(vis->cmds = map_new())) - return NULL; - - for (Command *cmd = cmds; cmd && cmd->name[0]; cmd++) { - for (const char **name = cmd->name; *name; name++) - map_put(vis->cmds, *name, cmd); - } - } - return map_closest(vis->cmds, name); -} - -bool vis_cmd(Vis *vis, const char *cmdline) { - enum CmdOpt opt = CMD_OPT_NONE; - size_t len = strlen(cmdline); - char *line = malloc(len+2); - if (!line) - return false; - line = strncpy(line, cmdline, len+1); - char *name = line; - - Filerange range = parse_range(vis->win, &name); - if (!text_range_valid(&range)) { - /* if only one position was given, jump to it */ - if (range.start != EPOS && !*name) { - view_cursor_to(vis->win->view, range.start); - free(line); - return true; - } - - if (name != line) { - vis_info_show(vis, "Invalid range\n"); - free(line); - return false; - } - } - /* skip leading white space */ - while (*name == ' ') - name++; - char *param = name; - while (*param && isalpha(*param)) - param++; - - if (*param == '!') { - if (param != name) { - opt |= CMD_OPT_FORCE; - *param = ' '; - } else { - param++; - } - } - - memmove(param+1, param, strlen(param)+1); - *param++ = '\0'; /* separate command name from parameters */ - - Command *cmd = lookup_cmd(vis, name); - if (!cmd) { - vis_info_show(vis, "Not an editor command"); - free(line); - return false; - } - - char *s = param; - const char *argv[32] = { name }; - for (int i = 1; i < LENGTH(argv); i++) { - while (s && *s && *s == ' ') - s++; - if (s && !*s) - s = NULL; - argv[i] = s; - if (!(cmd->opt & CMD_OPT_ARGS)) { - /* remove trailing spaces */ - if (s) { - while (*s) s++; - while (*(--s) == ' ') *s = '\0'; - } - s = NULL; - } - if (s && (s = strchr(s, ' '))) - *s++ = '\0'; - /* strip out a single '!' argument to make ":q !" work */ - if (argv[i] && !strcmp(argv[i], "!")) { - opt |= CMD_OPT_FORCE; - i--; - } - } - - cmd->cmd(vis, &range, opt, argv); - free(line); - return true; -} - static bool prompt_cmd(Vis *vis, char type, const char *cmd) { if (!cmd || !cmd[0]) return true; -- cgit v1.2.3