From df5873b1e2edf8e2631db236546618cb3431e3c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Andr=C3=A9=20Tanner?= Date: Fri, 11 Mar 2016 19:04:17 +0100 Subject: Support sam's structural regular expression based command language For those not familiar with sam(1) more information can be found at http://sam.cat-v.org/ For now sam commands can be entered from the vis prompt via :sam A command behaves differently depending on the mode in which it is issued: - in visual mode it behaves as if an implicit extract x command matching the current selection(s) would be preceding it. That is the command is executed once for each selection. - in normal mode: * if an address for the command was provided it is evaluated starting from the current cursor position(s) i.e. dot is set to the current cursor position. * if no address was supplied to the command then: + if multiple cursors exist, the command is executed once for every cursor with dot set to the current line of the cursor + otherwise if there is only 1 cursor then the command is executed with dot set to the whole file The command syntax was slightly tweaked to accpet more terse commands. - When specifiying text or regular expressions the trailing delimiter can be elided if the meaning is unambigious. - If only an address is provided the print command will be executed. - The print command creates a selection matching its range. - In text entry \t inserts a literal tab character (sam only recognizes \n). Hence the sam command ,x/pattern/ can be abbreviated to x/pattern If a command is successful vis switches to normal mode (and hence removes any selections), otherwise the editor is kept in visual mode. The print command "fails" by definition. --- LICENSE | 3 + sam.c | 800 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sam.h | 21 ++ text-regex.h | 1 + vis-cmds.c | 10 + vis-prompt.c | 2 +- 6 files changed, 836 insertions(+), 1 deletion(-) create mode 100644 sam.c create mode 100644 sam.h diff --git a/LICENSE b/LICENSE index 940bd69..7c79698 100644 --- a/LICENSE +++ b/LICENSE @@ -26,6 +26,9 @@ under terms compatible with the above ISC license: - libutf.[ch] originate from libutf a port of Plan 9's Unicode library to Unix and are MIT licensed + - sam.[ch] is based on Rob Pike's sam text editor for Plan 9. + Licensed under the Lucent Public License Version 1.02. + - lexers/* the LPeg based lexers used for syntax highlighting are imported from the Scintillua project licensed under the MIT license diff --git a/sam.c b/sam.c new file mode 100644 index 0000000..8f3fc0d --- /dev/null +++ b/sam.c @@ -0,0 +1,800 @@ +/* + * Heavily inspired (and partially based upon) Rob Pike's sam text editor + * for Plan 9. Licensed under the Lucent Public License Version 1.02. + * + * Copyright © 2000-2009 Lucent Technologies + * Copyright © 2016 Marc André Tanner + */ +#include +#include +#include "sam.h" +#include "vis-core.h" +#include "buffer.h" +#include "text.h" +#include "text-motions.h" +#include "text-objects.h" +#include "text-regex.h" +#include "util.h" + +typedef struct Address Address; +typedef struct Command Command; +typedef struct CommandDef CommandDef; + +struct Address { + char type; /* # (char) l (line) / ? . $ + - , ; * */ + Regex *regex; /* NULL denotes default for x, y, X, and Y commands */ + size_t number; /* line or character number */ + Address *left; /* left hand side of a compound address , ; */ + Address *right; /* either right hand side of a compound address or next address */ +}; + +struct Command { + char name; /* "index" into command table */ + Address *address; /* range of text for command */ + Regex *regex; /* regex to match, used by x, y, g, v, X, Y */ + char *text; /* text to insert, used by i, a, c */ + const CommandDef *cmddef; /* which command is this? */ + int count; /* command count if any */ + char flag; /* command specific flags */ + Command *cmd; /* target of x, y, g, v, X, Y, { */ + Command *next; /* next command in {} group */ +}; + +struct CommandDef { + char name; + enum { + CMD_CMD = 1 << 0, /* does the command take a sub/target command? */ + CMD_REGEX = 1 << 1, /* regex after command? */ + CMD_REGEX_DEFAULT = 1 << 2, /* is the regex optional i.e. can we use a default? */ + CMD_COUNT = 1 << 3, /* does the command support a count as in s2/../? */ + CMD_TEXT = 1 << 4, /* does the command need a text to insert? */ + CMD_ADDRESS_NONE = 1 << 5, /* is it an error to specify an address for the command */ + CMD_ADDRESS_ALL = 1 << 6, /* if no address is given, use the whole file */ + CMD_SHELL = 1 << 7, /* command needs a shell command as argument */ + } flags; + char defcmd; /* name of a default target command */ + bool (*func)(Vis*, Win*, Command*, Filerange*); /* command imiplementation */ +}; + +static bool cmd_insert(Vis*, Win*, Command*, Filerange*); +static bool cmd_append(Vis*, Win*, Command*, Filerange*); +static bool cmd_change(Vis*, Win*, Command*, Filerange*); +static bool cmd_delete(Vis*, Win*, Command*, Filerange*); +static bool cmd_guard(Vis*, Win*, Command*, Filerange*); +static bool cmd_extract(Vis*, Win*, Command*, Filerange*); +static bool cmd_select(Vis*, Win*, Command*, Filerange*); +static bool cmd_print(Vis*, Win*, Command*, Filerange*); +static bool cmd_files(Vis*, Win*, Command*, Filerange*); +static bool cmd_shell(Vis*, Win*, Command*, Filerange*); +static bool cmd_substitute(Vis*, Win*, Command*, Filerange*); + +static const CommandDef cmds[] = { + /* name, flags, default command, command */ + { 'a', CMD_TEXT, 0, cmd_append }, + { 'c', CMD_TEXT, 0, cmd_change }, + { 'd', 0, 0, cmd_delete }, + { 'g', CMD_CMD|CMD_REGEX, 'p', cmd_guard }, + { 'i', CMD_TEXT, 0, cmd_insert }, + { 'p', 0, 0, cmd_print }, + { 's', CMD_TEXT, 0, cmd_substitute }, + { 'v', CMD_CMD|CMD_REGEX, 'p', cmd_guard }, + { 'x', CMD_CMD|CMD_REGEX|CMD_REGEX_DEFAULT, 'p', cmd_extract }, + { 'y', CMD_CMD|CMD_REGEX|CMD_REGEX_DEFAULT, 'p', cmd_extract }, + { 'X', CMD_CMD|CMD_REGEX|CMD_REGEX_DEFAULT, 0, cmd_files }, + { 'Y', CMD_CMD|CMD_REGEX|CMD_REGEX_DEFAULT, 0, cmd_files }, + { '!', CMD_SHELL|CMD_ADDRESS_NONE, 0, cmd_shell }, + { '>', CMD_SHELL, 0, cmd_shell }, + { '<', CMD_SHELL, 0, cmd_shell }, + { '|', CMD_SHELL, 0, cmd_shell }, + { 0 /* array terminator */ }, +}; + +static const CommandDef cmds_internal[] = { + { 's', 0, 0, cmd_select }, + { 0 /* array terminator */ }, +}; + +const char *sam_error(enum SamError err) { + static const char *error_msg[] = { + [SAM_ERR_OK] = "Success", + [SAM_ERR_MEMORY] = "Out of memory", + [SAM_ERR_ADDRESS] = "Bad address", + [SAM_ERR_NO_ADDRESS] = "Command takes no address", + [SAM_ERR_UNMATCHED_BRACE] = "Unmatched `}'", + [SAM_ERR_REGEX] = "Bad regular expression", + [SAM_ERR_TEXT] = "Bad text", + [SAM_ERR_COMMAND] = "Unknown command", + [SAM_ERR_EXECUTE] = "Error executing command", + }; + + return err < LENGTH(error_msg) ? error_msg[err] : NULL; +} + +static Address *address_new(void) { + return calloc(1, sizeof(Address)); +} + +static void address_free(Vis *vis, Address *addr) { + if (!addr) + return; + if (addr->regex != vis->search_pattern) + text_regex_free(addr->regex); + address_free(vis, addr->left); + address_free(vis, addr->right); + free(addr); +} + +static void skip_spaces(const char **s) { + while (**s == ' ' || **s == '\t') + (*s)++; +} + +static char *parse_delimited_text(const char **s) { + Buffer buf; + bool escaped = false; + char delim = **s; + + buffer_init(&buf); + + for ((*s)++; **s && (**s != delim || escaped); (*s)++) { + if (!escaped && **s == '\\') { + escaped = true; + continue; + } + + char c = **s; + + if (escaped) { + escaped = false; + switch (**s) { + case '\n': + continue; + case 'n': + c = '\n'; + break; + case 't': + c = '\t'; + break; + } + } + + if (!buffer_append(&buf, &c, 1)) { + buffer_release(&buf); + return NULL; + } + } + + if (**s == delim) + (*s)++; + + if (!buffer_append(&buf, "\0", 1)) { + buffer_release(&buf); + return NULL; + } + + return buf.data; +} + +static char *parse_text(const char **s) { + skip_spaces(s); + if (**s != '\n') + return parse_delimited_text(s); + + Buffer buf; + buffer_init(&buf); + const char *start = *s + 1; + bool dot = false; + + for ((*s)++; **s && (!dot || **s != '\n'); (*s)++) + dot = (**s == '.'); + + if (!dot || !buffer_put(&buf, start, *s - start - 1) || + !buffer_append(&buf, "\0", 1)) { + buffer_release(&buf); + return NULL; + } + + return buf.data; +} + +static Regex *parse_regex(Vis *vis, const char **s) { + Buffer buf; + bool escaped = false; + char delim = **s; + buffer_init(&buf); + + for ((*s)++; **s && (**s != delim || escaped); (*s)++) { + if (!escaped && **s == '\\') { + escaped = true; + continue; + } + if (escaped) { + escaped = false; + if (**s != delim) + buffer_append(&buf, "\\", 1); + } + if (!buffer_append(&buf, *s, 1)) { + buffer_release(&buf); + return NULL; + } + } + + buffer_append(&buf, "\0", 1); + + Regex *regex = NULL; + + if (**s == delim || **s == '\0') { + if (**s == delim) + (*s)++; + if (buffer_length0(&buf) == 0) { + regex = vis->search_pattern; + } else if ((regex = text_regex_new())) { + if (text_regex_compile(regex, buf.data, REG_EXTENDED|REG_NEWLINE) != 0) { + text_regex_free(regex); + regex = NULL; + } else { + text_regex_free(vis->search_pattern); + vis->search_pattern = regex; + } + } + } + + buffer_release(&buf); + return regex; +} + +static int parse_number(const char **s) { + char *end = NULL; + int number = strtoull(*s, &end, 10); + if (end == *s) + return 1; + *s = end; + return number; +} + +static Address *address_parse_simple(Vis *vis, const char **s, enum SamError *err) { + + skip_spaces(s); + + Address addr = { + .type = **s, + .regex = NULL, + .number = 0, + .left = NULL, + .right = NULL, + }; + + switch (addr.type) { + case '#': /* character #n */ + (*s)++; + addr.number = parse_number(s); + break; + case '0': case '1': case '2': case '3': case '4': /* line n */ + case '5': case '6': case '7': case '8': case '9': + addr.type = 'l'; + addr.number = parse_number(s); + break; + case '/': /* regexp forwards */ + case '?': /* regexp backwards */ + addr.regex = parse_regex(vis, s); + if (!addr.regex) { + *err = SAM_ERR_REGEX; + return NULL; + } + break; + case '$': /* end of file */ + case '.': + case '+': + case '-': + (*s)++; + break; + default: + return NULL; + } + + if ((addr.right = address_parse_simple(vis, s, err))) { + switch (addr.right->type) { + case '.': + case '$': + return NULL; + case '#': + case 'l': + case '/': + case '?': + if (addr.type != '+' && addr.type != '-') { + Address *plus = address_new(); + if (!plus) { + address_free(vis, addr.right); + return NULL; + } + plus->type = '+'; + plus->right = addr.right; + addr.right = plus; + } + break; + } + } + + Address *ret = address_new(); + if (!ret) { + address_free(vis, addr.right); + return NULL; + } + *ret = addr; + return ret; +} + +static Address *address_parse_compound(Vis *vis, const char **s, enum SamError *err) { + Address addr = { 0 }, *left = address_parse_simple(vis, s, err), *right = NULL; + if (!left) + return NULL; + skip_spaces(s); + addr.type = **s; + switch (addr.type) { + case ',': /* a1,a2 */ + case ';': /* a1;a2 */ + (*s)++; + right = address_parse_compound(vis, s, err); + if (!right || ((right->type == ',' || right->type == ';') && !right->left)) { + if (right) + *err = SAM_ERR_ADDRESS; + goto fail; + } + break; + default: + return left; + } + + addr.left = left; + addr.right = right; + + Address *ret = address_new(); + if (ret) { + *ret = addr; + return ret; + } + +fail: + address_free(vis, left); + address_free(vis, right); + return NULL; +} + +static Command *command_new(void) { + return calloc(1, sizeof(Command)); +} + +static void command_free(Vis *vis, Command *cmd) { + if (!cmd) + return; + + for (Command *c = cmd->cmd, *next; c; c = next) { + next = c->next; + command_free(vis, c); + } + + address_free(vis, cmd->address); + if (cmd->regex != vis->search_pattern) + text_regex_free(cmd->regex); + free(cmd->text); + free(cmd); +} + +static const CommandDef *command_lookup(const CommandDef *cmds, char name) { + if (!name) + name = 'p'; + for (const CommandDef *cmd = cmds; cmd->name; cmd++) { + if (cmd->name == name) + return cmd; + } + return NULL; +} + +static Command *command_parse(Vis *vis, const char **s, int level, enum SamError *err) { + Command *cmd = command_new(); + if (!cmd) + return NULL; + + cmd->address = address_parse_compound(vis, s, err); + skip_spaces(s); + + cmd->name = **s; + + const CommandDef *cmddef = command_lookup(cmds, cmd->name); + + if (!cmddef) { + /* let it point to an all zero dummy entry */ + cmddef = &cmds_internal[LENGTH(cmds_internal)-1]; + switch (cmd->name) { + case '{': + { + (*s)++; + Command *prev = NULL, *next; + do { + skip_spaces(s); + if (**s == '\n') + (*s)++; + next = command_parse(vis, s, level+1, err); + if (prev) + prev->next = next; + else + cmd->cmd = next; + } while ((prev = next)); + break; + } + case '}': + if (level == 0) { + *err = SAM_ERR_UNMATCHED_BRACE; + goto fail; + } + (*s)++; + command_free(vis, cmd); + return NULL; + default: + *err = SAM_ERR_COMMAND; + goto fail; + } + } + + cmd->cmddef = cmddef; + + (*s)++; /* skip command name */ + + if (cmddef->flags & CMD_ADDRESS_NONE && cmd->address) { + *err = SAM_ERR_NO_ADDRESS; + goto fail; + } + + if (cmddef->flags & CMD_COUNT) + cmd->count = parse_number(s); + + if (cmddef->flags & CMD_REGEX) { + if (cmddef->flags & CMD_REGEX_DEFAULT && **s == ' ') { + skip_spaces(s); + } else if (!(cmd->regex = parse_regex(vis, s))) { + *err = SAM_ERR_REGEX; + goto fail; + } + } + + if (cmddef->flags & CMD_TEXT && !(cmd->text = parse_text(s))) { + *err = SAM_ERR_TEXT; + goto fail; + } + + if (cmddef->flags & CMD_CMD) { + skip_spaces(s); + if (cmddef->defcmd && (**s == '\n' || **s == '\0')) { + if (**s == '\n') + (*s)++; + if (!(cmd->cmd = command_new())) + goto fail; + cmd->cmd->name = cmddef->defcmd; + cmd->cmd->cmddef = command_lookup(cmds, cmddef->defcmd); + } else { + if (!(cmd->cmd = command_parse(vis, s, level, err))) + goto fail; + if (cmd->name == 'X' || cmd->name == 'Y') { + Command *sel = command_new(); + if (!sel) + goto fail; + sel->cmd = cmd->cmd; + sel->cmddef = command_lookup(cmds_internal, 's'); + cmd->cmd = sel; + } + } + } + + if (!cmd->address) { + if (cmddef->flags & CMD_ADDRESS_ALL) { + if (!(cmd->address = address_new())) + goto fail; + cmd->address->type = '*'; + } + } + + return cmd; +fail: + command_free(vis, cmd); + return NULL; +} + +static Command *sam_parse(Vis *vis, const char *cmd, enum SamError *err) { + const char **s = &cmd; + Command *c = command_parse(vis, s, 0, err); + if (!c) + return NULL; + Command *sel = command_new(); + if (!sel) { + command_free(vis, c); + return NULL; + } + sel->cmd = c; + sel->cmddef = command_lookup(cmds_internal, 's'); + return sel; +} + +static Filerange address_line_evaluate(Address *addr, File *file, Filerange *range, int sign) { + size_t offset = addr->number != 0 ? addr->number : 1; + size_t line; + if (sign > 0) { + line = text_lineno_by_pos(file->text, range->end); + line = text_pos_by_lineno(file->text, line + offset); + } else if (sign < 0) { + line = text_lineno_by_pos(file->text, range->start); + line = offset < line ? text_pos_by_lineno(file->text, line - offset) : 0; + } else { + line = text_pos_by_lineno(file->text, addr->number); + } + return text_range_new(line, text_line_next(file->text, line)); +} + +static Filerange address_evaluate(Address *addr, File *file, Filerange *range, int sign) { + Filerange ret = text_range_empty(); + + do { + switch (addr->type) { + case '#': + if (sign > 0) + ret.start = ret.end = range->end + addr->number; + else if (sign < 0) + ret.start = ret.end = range->start - addr->number; + else + ret = text_range_new(addr->number, addr->number); + break; + case 'l': + ret = address_line_evaluate(addr, file, range, sign); + break; + case '?': + sign = sign == 0 ? -1 : -sign; + /* fall through */ + case '/': + if (sign >= 0) + ret = text_object_search_forward(file->text, range->end, addr->regex); + else + ret = text_object_search_backward(file->text, range->start, addr->regex); + break; + case '$': + { + size_t size = text_size(file->text); + ret = text_range_new(size, size); + break; + } + case '.': + ret = *range; + break; + case '+': + case '-': + sign = addr->type == '+' ? +1 : -1; + if (!addr->right || addr->right->type == '+' || addr->right->type == '-') + ret = address_line_evaluate(addr, file, range, sign); + break; + case ',': + case ';': + { + Filerange left, right; + if (addr->left) + left = address_evaluate(addr->left, file, range, 0); + else + left = text_range_new(0, 0); + + if (addr->type == ';') + range = &left; + + if (addr->right) { + right = address_evaluate(addr->right, file, range, 0); + } else { + size_t size = text_size(file->text); + right = text_range_new(size, size); + } + /* TODO: enforce strict ordering? */ + return text_range_union(&left, &right); + } + case '*': + return text_range_new(0, text_size(file->text)); + } + if (text_range_valid(&ret)) + range = &ret; + } while ((addr = addr->right)); + + return ret; +} + +static bool sam_execute(Vis *vis, Win *win, Command *cmd, Filerange *range) { + bool ret = true; + Filerange r = cmd->address ? address_evaluate(cmd->address, win->file, range, 0) : *range; + + switch (cmd->name) { + case '{': + { + Text *txt = win->file->text; + Mark start, end; + Filerange group = r; + + for (Command *c = cmd->cmd; c; c = c->next) { + if (!text_range_valid(&group)) + return false; + + start = text_mark_set(txt, group.start); + end = text_mark_set(txt, group.end); + + ret &= sam_execute(vis, win, c, &group); + + size_t s = text_mark_get(txt, start); + /* hack to make delete work, only update if still valid */ + if (s != EPOS) + group.start = s; + group.end = text_mark_get(txt, end); + } + break; + } + default: + ret = cmd->cmddef->func(vis, win, cmd, &r); + break; + } + return ret; +} + +enum SamError sam_cmd(Vis *vis, const char *s) { + enum SamError err = SAM_ERR_OK; + Command *cmd = sam_parse(vis, s, &err); + if (!cmd) { + if (err == SAM_ERR_OK) + err = SAM_ERR_MEMORY; + return err; + } + Filerange range = text_range_empty(); + bool status = sam_execute(vis, vis->win, cmd, &range); + vis_mode_switch(vis, status ? VIS_MODE_NORMAL : VIS_MODE_VISUAL); + command_free(vis, cmd); + return err; +} + +static bool cmd_insert(Vis *vis, Win *win, Command *cmd, Filerange *range) { + size_t len = strlen(cmd->text); + bool ret = text_insert(win->file->text, range->start, cmd->text, len); + if (ret) + *range = text_range_new(range->start, range->start + len); + return ret; +} + +static bool cmd_append(Vis *vis, Win *win, Command *cmd, Filerange *range) { + size_t len = strlen(cmd->text); + bool ret = text_insert(win->file->text, range->end, cmd->text, len); + if (ret) + *range = text_range_new(range->end, range->end + len); + return ret; +} + +static bool cmd_change(Vis *vis, Win *win, Command *cmd, Filerange *range) { + Text *txt = win->file->text; + size_t len = strlen(cmd->text); + bool ret = text_delete(txt, range->start, text_range_size(range)) && + text_insert(txt, range->start, cmd->text, len); + if (ret) + *range = text_range_new(range->start, range->start + len); + return ret; +} + +static bool cmd_delete(Vis *vis, Win *win, Command *cmd, Filerange *range) { + bool ret = text_delete(win->file->text, range->start, text_range_size(range)); + if (ret) + *range = text_range_new(range->start, range->start); + return ret; +} + +static bool cmd_guard(Vis *vis, Win *win, Command *cmd, Filerange *range) { + bool match = !text_search_range_forward(win->file->text, range->start, + text_range_size(range), cmd->regex, 0, NULL, 0); + if (match ^ (cmd->name == 'v')) + return sam_execute(vis, win, cmd->cmd, range); + return true; +} + +static bool cmd_extract(Vis *vis, Win *win, Command *cmd, Filerange *range) { + bool ret = true; + Text *txt = win->file->text; + if (cmd->regex) { + size_t start = range->start, end = range->end; + RegexMatch match[1]; + while (start < end) { + bool found = text_search_range_forward(txt, start, + end - start, cmd->regex, 1, match, 0) == 0 && + match[0].start != match[0].end; + Filerange r = text_range_empty(); + if (found) { + if (cmd->name == 'x') + r = text_range_new(match[0].start, match[0].end); + else + r = text_range_new(start, match[0].start); + start = match[0].end; + } else { + if (cmd->name == 'y') + r = text_range_new(start, end); + start = end; + } + + if (text_range_valid(&r) && r.start != r.end) { + Mark mark_start = text_mark_set(txt, start); + Mark mark_end = text_mark_set(txt, end); + ret &= sam_execute(vis, win, cmd->cmd, &r); + start = text_mark_get(txt, mark_start); + end = text_mark_get(txt, mark_end); + if (start == EPOS || end == EPOS) + return false; + } + } + } else { + size_t start = range->start; + for (;;) { + size_t end = text_line_next(txt, start); + Filerange line = text_range_new(start, end); + if (start == end || !text_range_valid(&line)) + break; + Mark mark_end = text_mark_set(txt, end); + ret &= sam_execute(vis, win, cmd->cmd, &line); + start = text_mark_get(txt, mark_end); + if (start == EPOS) + return false; + } + } + return ret; +} + +static bool cmd_select(Vis *vis, Win *win, Command *cmd, Filerange *range) { + bool ret = true; + View *view = win->view; + Text *txt = win->file->text; + bool multiple_cursors = view_cursors_count(view) > 1; + for (Cursor *c = view_cursors(view), *next; c; c = next) { + next = view_cursors_next(c); + Filerange sel; + if (vis->mode->visual) { + sel = view_cursors_selection_get(c); + } else if (cmd->cmd->address) { + size_t start = view_cursors_pos(c); + size_t end = text_char_next(txt, start); + sel = text_range_new(start, end); + } else if (multiple_cursors) { + sel = text_object_line(txt, view_cursors_pos(c)); + } else { + sel = text_range_new(0, text_size(txt)); + } + ret &= sam_execute(vis, win, cmd->cmd, &sel); + view_cursors_dispose(c); + } + return ret; +} + +static bool cmd_print(Vis *vis, Win *win, Command *cmd, Filerange *range) { + View *view = win->view; + Text *txt = win->file->text; + Cursor *cursor = view_cursors_new(view); + if (cursor) { + view_cursors_selection_set(cursor, range); + view_cursors_to(cursor, text_char_prev(txt, range->end)); + } + /* indicate "failure"/incomplete command to keep visual mode */ + return false; +} + +static bool cmd_files(Vis *vis, Win *win, Command *cmd, Filerange *range) { + bool ret = true; + for (Win *win = vis->windows; win; win = win->next) { + if (win->file->internal) + continue; + bool match = !cmd->regex || (win->file->name && + text_regex_match(cmd->regex, win->file->name, 0)); + if (match ^ (cmd->name == 'Y')) + ret &= sam_execute(vis, win, cmd->cmd, range); + } + return ret; +} + +static bool cmd_shell(Vis *vis, Win *win, Command *cmd, Filerange *range) { + return false; +} + +static bool cmd_substitute(Vis *vis, Win *win, Command *cmd, Filerange *range) { + return false; +} diff --git a/sam.h b/sam.h new file mode 100644 index 0000000..fb4f314 --- /dev/null +++ b/sam.h @@ -0,0 +1,21 @@ +#ifndef SAM_H +#define SAM_H + +#include "vis.h" + +enum SamError { + SAM_ERR_OK, + SAM_ERR_MEMORY, + SAM_ERR_ADDRESS, + SAM_ERR_NO_ADDRESS, + SAM_ERR_UNMATCHED_BRACE, + SAM_ERR_REGEX, + SAM_ERR_TEXT, + SAM_ERR_COMMAND, + SAM_ERR_EXECUTE, +}; + +enum SamError sam_cmd(Vis *vis, const char *cmd); +const char *sam_error(enum SamError); + +#endif diff --git a/text-regex.h b/text-regex.h index 15b2340..c18caa3 100644 --- a/text-regex.h +++ b/text-regex.h @@ -1,6 +1,7 @@ #ifndef TEXT_REGEX_H #define TEXT_REGEX_H +#include #include "text.h" typedef struct Regex Regex; diff --git a/vis-cmds.c b/vis-cmds.c index bb90e35..9365d4e 100644 --- a/vis-cmds.c +++ b/vis-cmds.c @@ -16,6 +16,7 @@ #include "text-motions.h" #include "text-objects.h" #include "util.h" +#include "sam.h" enum CmdOpt { /* option flags for command definitions */ CMD_OPT_NONE, /* no option (default value) */ @@ -91,6 +92,7 @@ static bool cmd_map(Vis*, Filerange*, enum CmdOpt, const char *argv[]); static bool cmd_unmap(Vis*, Filerange*, enum CmdOpt, const char *argv[]); /* set language specific key bindings */ static bool cmd_langmap(Vis*, Filerange*, enum CmdOpt, const char *argv[]); +static bool cmd_sam(Vis*, Filerange*, enum CmdOpt, const char *argv[]); /* command recognized at the ':'-prompt. commands are found using a unique * prefix match. that is if a command should be available under an abbreviation @@ -124,6 +126,7 @@ static const Command cmds[] = { { { "later" }, cmd_earlier_later, CMD_OPT_NONE }, { { "!", }, cmd_filter, CMD_OPT_NONE }, { { "|", }, cmd_pipe, CMD_OPT_NONE }, + { { "sam" }, cmd_sam, CMD_OPT_NONE }, { { NULL, }, NULL, CMD_OPT_NONE }, }; @@ -1152,6 +1155,13 @@ static bool cmd_unmap(Vis *vis, Filerange *range, enum CmdOpt opt, const char *a return vis_mode_unmap(vis, mode, lhs); } +static bool cmd_sam(Vis *vis, Filerange *range, enum CmdOpt opt, const char *argv[]) { + enum SamError err = sam_cmd(vis, argv[1]); + if (err != SAM_ERR_OK) + vis_info_show(vis, "%s", sam_error(err)); + return err == SAM_ERR_OK; +} + static Filepos parse_pos(Win *win, char **cmd) { size_t pos = EPOS; View *view = win->view; diff --git a/vis-prompt.c b/vis-prompt.c index d051762..52a0fbd 100644 --- a/vis-prompt.c +++ b/vis-prompt.c @@ -17,7 +17,7 @@ bool vis_prompt_cmd(Vis *vis, const char *cmd) { register_put0(vis, &vis->registers[VIS_REG_COMMAND], cmd+1); bool ret = vis_cmd(vis, cmd+1); if (ret && vis->mode->visual) - vis_mode_switch(vis, VIS_MODE_NORMAL); + ;//vis_mode_switch(vis, VIS_MODE_NORMAL); return ret; } default: -- cgit v1.2.3