diff --git a/Makefile b/Makefile index 92c7a817f..3599eb2b0 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ include config.mk -SRC = editor.c window.c text.c text-motions.c text-objects.c register.c -HDR := ${SRC:.c=.h} syntax.h util.h config.def.h +SRC = editor.c window.c text.c text-motions.c text-objects.c register.c buffer.c +HDR := ${SRC:.c=.h} macro.h syntax.h util.h config.def.h SRC += vis.c OBJ = ${SRC:.c=.o} ALL = ${SRC} ${HDR} config.mk Makefile LICENSE README vis.1 diff --git a/README b/README index a4476625d..75808bf48 100644 --- a/README +++ b/README @@ -414,6 +414,12 @@ and their current support in vis. again). The same restriction also applies to commands which are not implemented in terms of operators, such as 'o', 'O', 'J' etc. + Macros + ------ + + [a-z] are recoginized macro names, q starts a recording, @ plays it back. + @@ refers to the least recently recorded macro. + Command line prompt ------------------- @@ -496,7 +502,6 @@ and their current support in vis. - right-to-left text - tabs (as in multiple workspaces) - ex mode - - macro recording How to help? ------------ diff --git a/buffer.c b/buffer.c new file mode 100644 index 000000000..9722101aa --- /dev/null +++ b/buffer.c @@ -0,0 +1,54 @@ +#include +#include + +#include "buffer.h" +#include "util.h" + +#define BUF_SIZE 1024 + +bool buffer_alloc(Buffer *buf, size_t size) { + if (size < BUF_SIZE) + size = BUF_SIZE; + if (buf->size < size) { + if (buf->size > 0) + size *= 2; + buf->data = realloc(buf->data, size); + if (!buf->data) { + buf->size = 0; + buf->len = 0; + return false; + } + buf->size = size; + } + return true; +} + +void buffer_truncate(Buffer *buf) { + buf->len = 0; +} + +void buffer_free(Buffer *buf) { + if (!buf) + return; + free(buf->data); + buf->data = NULL; + buf->len = 0; + buf->size = 0; +} + +bool buffer_put(Buffer *buf, void *data, size_t len) { + if (!buffer_alloc(buf, len)) + return false; + memcpy(buf->data, data, len); + buf->len = len; + return true; +} + +bool buffer_append(Buffer *buf, void *data, size_t len) { + size_t rem = buf->size - buf->len; + if (len > rem && !buffer_alloc(buf, buf->size + len - rem)) + return false; + memcpy(buf->data + buf->len, data, len); + buf->len += len; + return true; +} diff --git a/buffer.h b/buffer.h new file mode 100644 index 000000000..190f57989 --- /dev/null +++ b/buffer.h @@ -0,0 +1,20 @@ +#ifndef BUFFER_H +#define BUFFER_H + +#include +#include +#include "text.h" + +typedef struct { + char *data; /* NULL if empty */ + size_t len; /* current length of data */ + size_t size; /* maximal capacity of the buffer */ +} Buffer; + +void buffer_free(Buffer *buf); +bool buffer_alloc(Buffer *buf, size_t size); +void buffer_truncate(Buffer *buf); +bool buffer_put(Buffer *buf, void *data, size_t len); +bool buffer_append(Buffer *buf, void *data, size_t len); + +#endif diff --git a/config.def.h b/config.def.h index f41dd8190..16fd674c2 100644 --- a/config.def.h +++ b/config.def.h @@ -73,10 +73,11 @@ static void statusbar(EditorWin *win) { window_cursor_getxy(win->win, &line, &col); wattrset(win->statuswin, focused ? A_REVERSE|A_BOLD : A_REVERSE); mvwhline(win->statuswin, 0, 0, ' ', win->width); - mvwprintw(win->statuswin, 0, 0, "%s %s %s", + mvwprintw(win->statuswin, 0, 0, "%s %s %s %s", mode->name && mode->name[0] == '-' ? mode->name : "", text_filename_get(win->text), - text_modified(win->text) ? "[+]" : ""); + text_modified(win->text) ? "[+]" : "", + vis->recording ? "recording": ""); char buf[win->width + 1]; int len = snprintf(buf, win->width, "%d, %d", line, col); if (len > 0) { @@ -86,9 +87,11 @@ static void statusbar(EditorWin *win) { } /* called before any other keybindings are checked, if the function returns false - * the key is completely ignored. used to clear a user visible message. */ + * the key is completely ignored. */ static bool vis_keypress(Key *key) { editor_info_hide(vis); + if (vis->recording) + macro_append(vis->recording, key, sizeof(*key)); return true; } @@ -407,6 +410,8 @@ static KeyBinding vis_mode_normal[] = { { { NONE('z'), NONE('t') }, window, { .w = window_redraw_top } }, { { NONE('z'), NONE('z') }, window, { .w = window_redraw_center } }, { { NONE('z'), NONE('b') }, window, { .w = window_redraw_bottom } }, + { { NONE('q') }, macro_record, { NULL } }, + { { NONE('@') }, macro_replay, { NULL } }, { /* empty last element, array terminator */ }, }; diff --git a/editor.h b/editor.h index 718ca968d..206d3ea17 100644 --- a/editor.h +++ b/editor.h @@ -6,6 +6,7 @@ #include #include "window.h" #include "register.h" +#include "macro.h" #include "syntax.h" typedef struct Editor Editor; @@ -96,6 +97,8 @@ struct Editor { EditorWin *win; /* currently active window */ Syntax *syntaxes; /* NULL terminated array of syntax definitions */ Register registers[REG_LAST]; /* register used for copy and paste */ + Macro macros[26]; /* recorded macros */ + Macro *recording, *last_recording;/* currently and least recently recorded macro */ Prompt *prompt; /* used to get user input */ char info[255]; /* a user message currently being displayed */ Regex *search_pattern; /* last used search pattern */ diff --git a/macro.h b/macro.h new file mode 100644 index 000000000..c605cdf86 --- /dev/null +++ b/macro.h @@ -0,0 +1,11 @@ +#ifndef MACRO_H +#define MACRO_H + +#include "buffer.h" + +typedef Buffer Macro; +#define macro_free buffer_free +#define macro_reset buffer_truncate +#define macro_append buffer_append + +#endif diff --git a/register.c b/register.c index db43d71ce..cc25414cb 100644 --- a/register.c +++ b/register.c @@ -2,37 +2,17 @@ #include #include "register.h" +#include "buffer.h" +#include "text.h" #include "util.h" -#define REG_SIZE 1024 - -static bool register_alloc(Register *reg, size_t size) { - if (size < REG_SIZE) - size = REG_SIZE; - if (reg->size < size) { - reg->data = realloc(reg->data, size); - if (!reg->data) { - reg->size = 0; - reg->len = 0; - return false; - } - reg->size = size; - } - return true; -} - void register_free(Register *reg) { - if (!reg) - return; - free(reg->data); - reg->data = NULL; - reg->len = 0; - reg->size = 0; + buffer_free((Buffer*)reg); } bool register_put(Register *reg, Text *txt, Filerange *range) { size_t len = range->end - range->start; - if (!register_alloc(reg, len)) + if (!buffer_alloc((Buffer*)reg, len)) return false; reg->len = text_bytes_get(txt, range->start, len, reg->data); return true; @@ -41,7 +21,7 @@ bool register_put(Register *reg, Text *txt, Filerange *range) { bool register_append(Register *reg, Text *txt, Filerange *range) { size_t rem = reg->size - reg->len; size_t len = range->end - range->start; - if (len > rem && !register_alloc(reg, reg->size + len - rem)) + if (len > rem && !buffer_alloc((Buffer*)reg, reg->size + len - rem)) return false; reg->len += text_bytes_get(txt, range->start, len, reg->data + reg->len); return true; diff --git a/register.h b/register.h index 506b86a7a..02890e807 100644 --- a/register.h +++ b/register.h @@ -3,8 +3,9 @@ #include #include -#include "text.h" +#include "buffer.h" +/* definition has to match Buffer */ typedef struct { char *data; /* NULL if empty */ size_t len; /* current length of data */ diff --git a/vis.c b/vis.c index 1d9f9e51d..6015fcb02 100644 --- a/vis.c +++ b/vis.c @@ -375,6 +375,8 @@ static TextObject *moves_linewise[] = { }; /** functions to be called from keybindings */ +static void macro_record(const Arg *arg); +static void macro_replay(const Arg *arg); /* temporarily suspend the editor and return to the shell, type 'fg' to get back */ static void suspend(const Arg *arg); /* switch to mode indicated by arg->i */ @@ -511,6 +513,7 @@ static void switchmode_to(Mode *new_mode); #include "config.h" static Key getkey(void); +static void keypress(Key *key); static void action_do(Action *a); static bool exec_command(char type, char *cmdline); @@ -778,6 +781,40 @@ static size_t window_lines_bottom(const Arg *arg) { /** key bindings functions of type: void (*func)(const Arg*) */ +static Macro *key2macro(const Arg *arg) { + if (arg->i) + return &vis->macros[arg->i]; + Key key = getkey(); + if (key.str[0] >= 'a' && key.str[0] <= 'z') + return &vis->macros[key.str[0] - 'a']; + if (key.str[0] == '@') + return vis->last_recording; + return NULL; +} + +static void macro_record(const Arg *arg) { + if (vis->recording) { + /* hack to remove last recorded key, otherwise upon replay + * we would start another recording */ + vis->recording->len -= sizeof(Key); + vis->last_recording = vis->recording; + vis->recording = NULL; + } else { + vis->recording = key2macro(arg); + if (vis->recording) + macro_reset(vis->recording); + } + editor_draw(vis); +} + +static void macro_replay(const Arg *arg) { + Macro *macro = key2macro(arg); + if (!macro || macro == vis->recording) + return; + for (size_t i = 0; i < macro->len; i += sizeof(Key)) + keypress((Key*)(macro->data + i)); +} + static void suspend(const Arg *arg) { endwin(); raise(SIGSTOP); @@ -1691,6 +1728,36 @@ static KeyBinding *keybinding(Mode *mode, KeyCombo keys) { return NULL; } +static void keypress(Key *key) { + static KeyCombo keys; + static int keylen; + + if (config->keypress && !config->keypress(key)) + return; + + keys[keylen++] = *key; + KeyBinding *action = keybinding(mode, keys); + + if (action) { + int combolen = 0; + while (combolen < MAX_KEYS && keyvalid(&action->key[combolen])) + combolen++; + if (keylen < combolen) + return; /* combo not yet complete */ + /* need to reset state before calling action->func in case + * it will call us (=keypress) again as e.g. macro_replay */ + keylen = 0; + memset(keys, 0, sizeof(keys)); + if (action->func) + action->func(&action->arg); + } else if (keylen == 1 && key->code == 0 && mode->input) { + mode->input(key->str, strlen(key->str)); + } + + keylen = 0; + memset(keys, 0, sizeof(keys)); +} + static Key getkey(void) { Key key = { .str = "\0\0\0\0\0\0", .code = 0 }; int keycode = getch(), len = 0; @@ -1716,8 +1783,6 @@ static Key getkey(void) { static void mainloop() { struct timespec idle = { .tv_nsec = 0 }, *timeout = NULL; - KeyCombo keys; - int keylen = 0; sigset_t emptyset, blockset; sigemptyset(&emptyset); sigemptyset(&blockset); @@ -1754,35 +1819,8 @@ static void mainloop() { } Key key = getkey(); - if (config->keypress && !config->keypress(&key)) - continue; + keypress(&key); - keys[keylen++] = key; - KeyBinding *action = keybinding(mode, keys); - - if (action) { - int combolen = 0; - while (combolen < MAX_KEYS && keyvalid(&action->key[combolen])) - combolen++; - if (combolen == keylen) { - if (action->func) - action->func(&action->arg); - keylen = 0; - memset(keys, 0, sizeof(keys)); - } - continue; - } else { - int oldkeylen = keylen; - keylen = 0; - memset(keys, 0, sizeof(keys)); - if (oldkeylen > 1) - continue; /* cancel partial action */ - } - - if (key.code) /* ignore curses KEY_* */ - continue; - if (mode->input) - mode->input(key.str, strlen(key.str)); if (mode->idle) timeout = &idle; }