tAdd basic infrastructure for sort of automated tests - ledit - Text editor (WI… | |
git clone git://lumidify.org/ledit.git (fast, but not encrypted) | |
git clone https://lumidify.org/git/ledit.git (encrypted, but very slow) | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
commit 225b064c19ce96a38c5472b3d73367922f671dc7 | |
parent 155a081f559a9ecfddd8b086c7d91187c2af4bc2 | |
Author: lumidify <[email protected]> | |
Date: Sat, 4 Nov 2023 19:11:19 +0100 | |
Add basic infrastructure for sort of automated tests | |
Now that I have the infrastructure, I can forget about | |
writing the actual tests. That's how it works, right? | |
Diffstat: | |
M Makefile | 3 ++- | |
M keys_basic.c | 9 +-------- | |
M keys_basic.h | 2 +- | |
M keys_command.c | 38 ++++++++++++++++++++++-------… | |
M keys_command.h | 2 +- | |
M ledit.c | 365 +++++++++++++++++++++++++++++… | |
A tests/README | 59 +++++++++++++++++++++++++++++… | |
M undo.c | 27 +++++++++++++++------------ | |
M undo.h | 4 ++++ | |
M view.h | 2 +- | |
10 files changed, 466 insertions(+), 45 deletions(-) | |
--- | |
diff --git a/Makefile b/Makefile | |
t@@ -12,6 +12,7 @@ MAN5 = leditrc.5 | |
MISCFILES = Makefile README LICENSE IDEAS NOTES TODO | |
DEBUG=0 | |
+TEST=0 | |
SANITIZE=0 | |
ENABLE_UTF8PROC=1 | |
t@@ -78,7 +79,7 @@ EXTRA_LDFLAGS_UTF8PROC1 = `pkg-config --libs libutf8proc` | |
# Xcursor isn't actually needed right now since I'm not using the drag 'n drop… | |
# of ctrlsel yet, but since it's moderately likely that I will use that in the… | |
# decided to just leave it in. | |
-CFLAGS_LEDIT = ${EXTRA_FLAGS_SANITIZE${SANITIZE}} ${EXTRA_CFLAGS_DEBUG${DEBUG}… | |
+CFLAGS_LEDIT = -DTEST=${TEST} ${EXTRA_FLAGS_SANITIZE${SANITIZE}} ${EXTRA_CFLAG… | |
LDFLAGS_LEDIT = ${EXTRA_FLAGS_SANITIZE${SANITIZE}} ${EXTRA_LDFLAGS_DEBUG${DEBU… | |
all: ${BIN} | |
diff --git a/keys_basic.c b/keys_basic.c | |
t@@ -2709,14 +2709,7 @@ repeat_command(ledit_view *view, char *text, size_t len… | |
} | |
struct action | |
-basic_key_handler(ledit_view *view, XEvent *event, int lang_index) { | |
- char *buf = NULL; | |
- KeySym sym = NoSymbol; | |
- int n; | |
- | |
- unsigned int key_state = event->xkey.state; | |
- preprocess_key(view->window, &event->xkey, &sym, &buf, &n); | |
- | |
+basic_key_handler(ledit_view *view, unsigned int key_state, KeySym sym, char *… | |
struct repetition_stack_elem *re = push_repetition_stack(); | |
re->key_text = ledit_strndup(buf, (size_t)n); | |
re->len = (size_t)n; | |
diff --git a/keys_basic.h b/keys_basic.h | |
t@@ -11,6 +11,6 @@ int basic_key_cb_modemask_is_valid(basic_key_cb *cb, ledit_m… | |
/* perform cleanup of global data */ | |
void basic_key_cleanup(void); | |
-struct action basic_key_handler(ledit_view *view, XEvent *event, int lang_inde… | |
+struct action basic_key_handler(ledit_view *view, unsigned int key_state, KeyS… | |
#endif | |
diff --git a/keys_command.c b/keys_command.c | |
t@@ -215,11 +215,16 @@ static int parse_range( | |
static int handle_cmd(ledit_view *view, char *cmd, size_t len, size_t lang_ind… | |
/* FIXME: USE LEN EVERYWHERE INSTEAD OF RELYING ON cmd BEING NUL-TERMINATED */ | |
-/* FIXME: return error so write_quit knows when to quit */ | |
+/* returns 1 on error, 0 otherwise */ | |
static int | |
-handle_write(ledit_view *view, char *cmd, size_t l1, size_t l2) { | |
- (void)l1; | |
- (void)l2; | |
+handle_write_base(ledit_view *view, char *cmd) { | |
+ #if TEST | |
+ /* disallow normal file writing in test mode so no | |
+ file can accidentally be destroyed by fuzz testing */ | |
+ (void)view; | |
+ (void)cmd; | |
+ return 0; | |
+ #else | |
/* FIXME: allow writing only part of file */ | |
char *filename = view->buffer->filename; | |
int stored = 1; | |
t@@ -248,6 +253,7 @@ handle_write(ledit_view *view, char *cmd, size_t l1, size_… | |
"%s: file modification time changed; use ! to over… | |
filename | |
); | |
+ return 1; | |
/* FIXME: I guess the file can still exist if stat returns an … | |
but the writing itself will probably fail then as well. */ | |
} else if (!ret && !force && !stored) { | |
t@@ -256,8 +262,10 @@ handle_write(ledit_view *view, char *cmd, size_t l1, size… | |
"%s: file exists; use ! to override", | |
filename | |
); | |
+ return 1; | |
} else if (buffer_write_to_filename(view->buffer, filename, &e… | |
window_show_message_fmt(view->window, "Error writing %… | |
+ return 1; | |
} else { | |
/* FIXME: better message */ | |
window_show_message_fmt(view->window, "Wrote file %s",… | |
t@@ -270,8 +278,18 @@ handle_write(ledit_view *view, char *cmd, size_t l1, size… | |
} | |
} else { | |
window_show_message(view->window, "No file name", -1); | |
+ return 1; | |
} | |
return 0; | |
+ #endif | |
+} | |
+ | |
+static int | |
+handle_write(ledit_view *view, char *cmd, size_t l1, size_t l2) { | |
+ (void)l1; | |
+ (void)l2; | |
+ handle_write_base(view, cmd); | |
+ return 0; | |
} | |
static int | |
t@@ -320,7 +338,10 @@ close_view(ledit_view *view, char *cmd, size_t l1, size_t… | |
static int | |
handle_write_quit(ledit_view *view, char *cmd, size_t l1, size_t l2) { | |
- handle_write(view, cmd, l1, l2); | |
+ (void)l1; | |
+ (void)l2; | |
+ if (handle_write_base(view, cmd)) | |
+ return 0; | |
ledit_cleanup(); | |
exit(0); | |
return 0; | |
t@@ -984,14 +1005,9 @@ edit_discard(ledit_view *view, char *key_text, size_t le… | |
} | |
struct action | |
-command_key_handler(ledit_view *view, XEvent *event, int lang_index) { | |
- char *buf = NULL; | |
- KeySym sym = NoSymbol; | |
- int n; | |
+command_key_handler(ledit_view *view, unsigned int key_state, KeySym sym, char… | |
command_key_array *cur_keys = config_get_command_keys(lang_index); | |
size_t num_keys = cur_keys->num_keys; | |
- unsigned int key_state = event->xkey.state; | |
- preprocess_key(view->window, &event->xkey, &sym, &buf, &n); | |
int grabkey = 1, found = 0; | |
command_key_cb_flags flags = KEY_FLAG_NONE; | |
for (size_t i = 0; i < num_keys; i++) { | |
diff --git a/keys_command.h b/keys_command.h | |
t@@ -17,6 +17,6 @@ void search_next(ledit_view *view); | |
void search_prev(ledit_view *view); | |
void command_key_cleanup(void); | |
-struct action command_key_handler(ledit_view *view, XEvent *event, int lang_in… | |
+struct action command_key_handler(ledit_view *view, unsigned int key_state, Ke… | |
#endif | |
diff --git a/ledit.c b/ledit.c | |
t@@ -12,6 +12,9 @@ | |
#include <pwd.h> | |
#include <time.h> | |
+#if TEST | |
+#include <fcntl.h> | |
+#endif | |
#include <errno.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
t@@ -45,15 +48,322 @@ static void setup(int argc, char *argv[]); | |
static void redraw(void); | |
static void change_keyboard(char *lang); | |
-static void key_press(ledit_view *view, XEvent *event); | |
+static void key_press_event(ledit_view *view, XEvent *event); | |
+static void key_press(ledit_view *view, unsigned int key_state, KeySym sym, ch… | |
ledit_common common; | |
ledit_clipboard *clipboard = NULL; | |
ledit_buffer *buffer = NULL; | |
size_t cur_lang = 0; | |
+#if TEST | |
+static struct { | |
+ char *read; /* text read from stdin */ | |
+ size_t read_len; /* length of text in read buffer */ | |
+ size_t read_alloc; /* size of read buffer */ | |
+ size_t line_start; /* start of current line */ | |
+ size_t read_cur; /* length of text already read */ | |
+} test_status = {NULL, 0, 0, 0, 0}; | |
+ | |
+#define READ_BLK_SIZE 128 | |
+ | |
+/* Read up to READ_BLK_SIZE bytes from stdin. | |
+ Returns 1 if an error occurred, -1 if not new data available, 0 otherwise. … | |
+static int | |
+read_input(void) { | |
+ if (test_status.read_cur > 0) { | |
+ memmove(test_status.read, test_status.read + test_status.read_… | |
+ test_status.read_len -= test_status.read_cur; | |
+ test_status.read_cur = 0; | |
+ } | |
+ int nread; | |
+ test_status.read_alloc = ideal_array_size(test_status.read_alloc, test… | |
+ test_status.read = ledit_realloc(test_status.read, test_status.read_al… | |
+ nread = read(fileno(stdin), test_status.read + test_status.read_len, R… | |
+ if (nread == -1 && errno == EAGAIN) | |
+ return -1; | |
+ else if (nread == -1 || nread == 0) | |
+ return 1; | |
+ test_status.read_len += nread; | |
+ | |
+ return 0; | |
+} | |
+ | |
+/* based partially on OpenBSD's strtonum */ | |
+int | |
+read_rangeint(long long *ret, int end, long long min, long long max) { | |
+ if (test_status.read_cur >= test_status.read_len || test_status.read[t… | |
+ return 1; | |
+ char end_char = end ? '\n' : ' '; | |
+ size_t len = 0; | |
+ test_status.read_cur++; | |
+ char *str = test_status.read + test_status.read_cur; | |
+ int found = 0; | |
+ for (; test_status.read_cur < test_status.read_len; test_status.read_c… | |
+ if (test_status.read[test_status.read_cur] == end_char) { | |
+ found = 1; | |
+ break; | |
+ } | |
+ len++; | |
+ } | |
+ if (!found || len == 0) | |
+ return 1; | |
+ /* the string needs to be nul-terminated | |
+ if it contains more than 11 characters (10 digits + sign), | |
+ it's illegal anyways (at least for these testing purposes...) */ | |
+ if (len > 11) | |
+ return 1; | |
+ char nstr[12]; | |
+ strncpy(nstr, str, len); | |
+ nstr[len] = '\0'; | |
+ char *num_end; | |
+ long long ll = strtoll(nstr, &num_end, 10); | |
+ if (nstr == num_end || *num_end != '\0' || | |
+ ll < min || ll > max || ((ll == LLONG_MIN || | |
+ ll == LLONG_MAX) && errno == ERANGE)) { | |
+ return 1; | |
+ } | |
+ *ret = ll; | |
+ if (end) | |
+ test_status.read_cur++; | |
+ return 0; | |
+} | |
+ | |
+int | |
+read_uint(unsigned int *ret, int end) { | |
+ long long l; | |
+ int err = read_rangeint(&l, end, 0, UINT_MAX); | |
+ *ret = (unsigned int)l; | |
+ return err; | |
+} | |
+ | |
+int | |
+read_int(int *ret, int end) { | |
+ long long l; | |
+ int err = read_rangeint(&l, end, INT_MIN, INT_MAX); | |
+ *ret = (int)l; | |
+ return err; | |
+} | |
+ | |
+int | |
+read_text(char **text, size_t *text_len) { | |
+ if (test_status.read_cur >= test_status.read_len || test_status.read[t… | |
+ return 1; | |
+ int bs = 0; | |
+ int offset = 0; | |
+ test_status.read_cur++; | |
+ size_t start = test_status.read_cur; | |
+ *text = test_status.read + test_status.read_cur; | |
+ int found = 0; | |
+ for (; test_status.read_cur < test_status.read_len; test_status.read_c… | |
+ if (test_status.read[test_status.read_cur] == '\\') { | |
+ bs++; | |
+ if (bs / 2) | |
+ offset++; | |
+ bs %= 2; | |
+ test_status.read[test_status.read_cur - offset] = '\\'; | |
+ } else if (test_status.read[test_status.read_cur] == '\n') { | |
+ if (!bs) { | |
+ found = 1; | |
+ break; | |
+ } else { | |
+ bs = 0; | |
+ offset++; | |
+ test_status.read[test_status.read_cur - offset… | |
+ } | |
+ } else { | |
+ test_status.read[test_status.read_cur - offset] = test… | |
+ bs = 0; | |
+ } | |
+ } | |
+ if (!found) | |
+ return 1; | |
+ *text_len = test_status.read_cur - start - offset; | |
+ test_status.read_cur++; | |
+ return 0; | |
+} | |
+ | |
+int | |
+read_filename(char **text, size_t *text_len) { | |
+ if (read_text(text, text_len)) | |
+ return 1; | |
+ for (size_t i = 0; i < *text_len; i++) { | |
+ if ((*text)[i] == '/' || (*text)[i] == '\0') | |
+ return 1; | |
+ } | |
+ return 0; | |
+} | |
+ | |
+static unsigned int view_num = 0; | |
+/* Process commands in test_status. | |
+ Returns 0 if no complete commands are contained in read buffer, 1 otherwise… | |
+static int | |
+process_commands(void) { | |
+ int bs = 0; | |
+ int found = 0; | |
+ size_t nl_index = 0; | |
+ for (size_t i = test_status.read_cur; i < test_status.read_len; i++) { | |
+ if (test_status.read[i] == '\\') { | |
+ bs++; | |
+ bs %= 2; | |
+ } else if (test_status.read[i] == '\n' && bs == 0) { | |
+ found = 1; | |
+ nl_index = i; | |
+ break; | |
+ } else { | |
+ bs = 0; | |
+ } | |
+ } | |
+ if (!found) | |
+ return 0; | |
+ unsigned int key_state, button_num, keysym, new_view; | |
+ char *text, *term, *errstr; | |
+ size_t text_len; | |
+ int x, y; | |
+ XEvent e; | |
+ FILE *file; | |
+ test_status.read_cur += 1; | |
+ ledit_view *view = buffer->views[view_num]; | |
+ switch (test_status.read[test_status.read_cur-1]) { | |
+ case 'k': | |
+ /* key press */ | |
+ /* k key_state keysym text */ | |
+ if (read_uint(&key_state, 0)) | |
+ goto error; | |
+ if (read_uint(&keysym, 0)) | |
+ goto error; | |
+ if (read_text(&text, &text_len)) | |
+ goto error; | |
+ key_press(view, key_state, keysym, text, (int)text_len… | |
+ break; | |
+ case 'p': | |
+ /* mouse button press */ | |
+ /* p button_num x y */ | |
+ if (read_uint(&button_num, 0)) | |
+ goto error; | |
+ if (read_int(&x, 0)) | |
+ goto error; | |
+ if (read_int(&y, 1)) | |
+ goto error; | |
+ e = (XEvent){.xbutton = {.type = ButtonPress, .button … | |
+ window_register_button_press(view->window, &e); | |
+ break; | |
+ case 'r': | |
+ /* mouse button release */ | |
+ /* r button_num x y */ | |
+ if (read_uint(&button_num, 0)) | |
+ goto error; | |
+ if (read_int(&x, 0)) | |
+ goto error; | |
+ if (read_int(&y, 1)) | |
+ goto error; | |
+ e = (XEvent){.xbutton = {.type = ButtonRelease, .butto… | |
+ window_button_release(view->window, &e); | |
+ break; | |
+ case 'm': | |
+ /* mouse motion */ | |
+ /* m x y */ | |
+ if (read_int(&x, 0)) | |
+ goto error; | |
+ if (read_int(&y, 1)) | |
+ goto error; | |
+ e = (XEvent){.xmotion = {.type = MotionNotify, .x = x,… | |
+ window_register_motion(view->window, &e); | |
+ break; | |
+ case 'l': | |
+ /* language switch */ | |
+ /* l lang_name */ | |
+ if (read_text(&text, &text_len)) | |
+ goto error; | |
+ term = ledit_strndup(text, text_len); | |
+ change_keyboard(term); | |
+ free(term); | |
+ break; | |
+ case 's': | |
+ /* switch view */ | |
+ /* s view_num */ | |
+ if (read_uint(&new_view, 1)) | |
+ goto error; | |
+ if (new_view >= buffer->views_num) | |
+ fprintf(stderr, "Invalid view number %u\n", ne… | |
+ else | |
+ view_num = new_view; | |
+ break; | |
+ case 'w': | |
+ /* write contents of buffer */ | |
+ /* w file_name */ | |
+ if (read_filename(&text, &text_len)) | |
+ goto error; | |
+ term = ledit_strndup(text, text_len); | |
+ if (buffer_write_to_filename(buffer, term, &errstr)) | |
+ fprintf(stderr, "Error writing %s: %s\n", term… | |
+ free(term); | |
+ break; | |
+ case 'd': | |
+ /* dump other info to file */ | |
+ /* d file_name */ | |
+ if (read_filename(&text, &text_len)) | |
+ goto error; | |
+ term = ledit_strndup(text, text_len); | |
+ file = fopen(term, "w"); | |
+ if (!file) { | |
+ fprintf(stderr, "Unable to open file %s\n", te… | |
+ } else { | |
+ fprintf( | |
+ file, | |
+ "cursor_line: %zu, cursor_byte: %zu, sel_v… | |
+ "sel_line1: %zu, sel_byte1: %zu, " | |
+ "sel_line2: %zu, sel_byte2: %zu\n", | |
+ view->cur_line, view->cur_index, view->sel… | |
+ view->sel.line1, view->sel.byte1, | |
+ view->sel.line2, view->sel.byte2 | |
+ ); | |
+ fclose(file); | |
+ } | |
+ free(term); | |
+ break; | |
+ case 'u': | |
+ /* dump undo stack to file */ | |
+ if (read_filename(&text, &text_len)) | |
+ goto error; | |
+ /* u file_name */ | |
+ term = ledit_strndup(text, text_len); | |
+ file = fopen(term, "w"); | |
+ if (!file) { | |
+ fprintf(stderr, "Unable to open file %s\n", te… | |
+ } else { | |
+ dump_undo_stack(file, buffer->undo); | |
+ fclose(file); | |
+ } | |
+ free(term); | |
+ break; | |
+ default: | |
+ goto error; | |
+ } | |
+ return 1; | |
+error: | |
+ fprintf(stderr, "Error parsing command.\n"); | |
+ test_status.read_cur = nl_index + 1; | |
+ return 1; | |
+} | |
+#endif | |
+ | |
+/* can only be set to 1 when compiled with TEST */ | |
+static int test_extra = 0; | |
+ | |
static void | |
mainloop(void) { | |
+ #if TEST | |
+ int flags = fcntl(fileno(stdin), F_GETFL, 0); | |
+ if (flags == -1) { | |
+ fprintf(stderr, "Unable to set non-blocking mode on stdin.\n"); | |
+ return; | |
+ } | |
+ if (fcntl(fileno(stdin), F_SETFL, flags | O_NONBLOCK)) { | |
+ fprintf(stderr, "Unable to set non-blocking mode on stdin.\n"); | |
+ return; | |
+ } | |
+ #endif | |
XEvent event; | |
int xkb_event_type; | |
int major, minor; | |
t@@ -138,16 +448,20 @@ mainloop(void) { | |
window_register_resize(view->window, &event); | |
break; | |
case ButtonPress: | |
- window_register_button_press(view->window, &ev… | |
+ if (!test_extra) | |
+ window_register_button_press(view->win… | |
break; | |
case ButtonRelease: | |
- window_button_release(view->window, &event); | |
+ if (!test_extra) | |
+ window_button_release(view->window, &e… | |
break; | |
case MotionNotify: | |
- window_register_motion(window, &event); | |
+ if (!test_extra) | |
+ window_register_motion(window, &event); | |
break; | |
case KeyPress: | |
- key_press(view, &event); | |
+ if (!test_extra) | |
+ key_press_event(view, &event); | |
break; | |
case ClientMessage: | |
if ((Atom)event.xclient.data.l[0] == view->win… | |
t@@ -161,11 +475,22 @@ mainloop(void) { | |
} | |
}; | |
+ #if TEST | |
+ int ret; | |
+ if ((ret = read_input()) == 1) { | |
+ fprintf(stderr, "Unable to read text from stdin.\n"); | |
+ } else if (ret == 0) { | |
+ while (process_commands()) { | |
+ /* NOP */ | |
+ } | |
+ } | |
+ #endif | |
+ | |
for (size_t i = 0; i < buffer->views_num; i++) { | |
window_handle_filtered_events(buffer->views[i]->window… | |
} | |
- if (change_kbd) { | |
+ if (!test_extra && change_kbd) { | |
change_kbd = 0; | |
XkbStateRec s; | |
XkbGetState(common.dpy, XkbUseCoreKbd, &s); | |
t@@ -201,11 +526,21 @@ setup(int argc, char *argv[]) { | |
char c; | |
char *opt_filename = NULL; | |
- while ((c = getopt(argc, argv, "c:")) != -1) { | |
+ #if TEST | |
+ char *opts = "tc:"; | |
+ #else | |
+ char *opts = "c:"; | |
+ #endif | |
+ while ((c = getopt(argc, argv, opts)) != -1) { | |
switch (c) { | |
case 'c': | |
opt_filename = optarg; | |
break; | |
+ #if TEST | |
+ case 't': | |
+ test_extra = 1; | |
+ break; | |
+ #endif | |
default: | |
fprintf(stderr, "USAGE: ledit [-c config] [file]\n"); | |
exit(1); | |
t@@ -517,16 +852,26 @@ change_keyboard(char *lang) { | |
} | |
static void | |
-key_press(ledit_view *view, XEvent *event) { | |
+key_press(ledit_view *view, unsigned int key_state, KeySym sym, char *buf, int… | |
/* FIXME: just let view handle this since the action is part | |
of it anyways now */ | |
if (view->cur_action.type == ACTION_GRABKEY && view->cur_action.callba… | |
- view->cur_action = view->cur_action.callback(view, event, cur_… | |
+ view->cur_action = view->cur_action.callback(view, key_state, … | |
} else { | |
- view->cur_action = basic_key_handler(view, event, cur_lang); | |
+ view->cur_action = basic_key_handler(view, key_state, sym, buf… | |
} | |
} | |
+static void | |
+key_press_event(ledit_view *view, XEvent *event) { | |
+ char *buf = NULL; | |
+ KeySym sym = NoSymbol; | |
+ int n; | |
+ unsigned int key_state = event->xkey.state; | |
+ preprocess_key(view->window, &event->xkey, &sym, &buf, &n); | |
+ key_press(view, key_state, sym, buf, n); | |
+} | |
+ | |
int | |
main(int argc, char *argv[]) { | |
setup(argc, argv); | |
diff --git a/tests/README b/tests/README | |
t@@ -0,0 +1,59 @@ | |
+There aren't any proper tests currently, but some infrastructure is in place t… | |
+ | |
+When compiled with TEST=1, ledit accepts commands on standard input to generat… | |
+Each command ends in newline. If the last argument is text, it may also contai… | |
+they are escaped with backslash. Single backslashes that are not in front of a… | |
+just taken verbatim, but two backslashes are collapsed into one. Filenames are… | |
+case because they are not allowed to contain '/' or '\0'. | |
+ | |
+The commands to generate events take raw integers instead of symbolic names fo… | |
+and other parameters. These need to be given using the definitions from Xlib. | |
+ | |
+The commands currently supported are the following: | |
+ | |
+k <key_state> <keysym> <text> | |
+ | |
+Generate a keypress event. <key_state> and <keysym> are the modifier state and… | |
+ | |
+p <button_num> <x> <y> | |
+ | |
+Generate a mouse button press event. | |
+ | |
+r <button_num> <x> <y> | |
+ | |
+Generate a mouse button release event. | |
+ | |
+m <x> <y> | |
+ | |
+Generate a mouse motion event. | |
+ | |
+l <lang> | |
+ | |
+Switch to keyboard layout <lang>. | |
+ | |
+s <view_num> | |
+ | |
+Switch to view <view_num>, if it exists. | |
+ | |
+w <filename> | |
+ | |
+Write the contents of the buffer to <filename>. | |
+ | |
+d <filename> | |
+ | |
+Dump various information to <filename>. Currently, the cursor position and | |
+information about the selection is given. See ledit.c for the exact format. | |
+ | |
+u <filename> | |
+ | |
+Dump the undo stack to <filename>. See undo.c for the exact format. | |
+ | |
+TODO: Add more commands, e.g. for dumping the repetition stack. | |
+ | |
+ | |
+When compiled with TEST=1, ledit supports an additional command-line argument … | |
+This disables handling of the regular key press, mouse, and language switch ev… | |
+in order to avoid messing with the results. | |
+ | |
+Note that regular file writing using :w is disabled when compiled with TEST=1 | |
+in order to avoid overwriting anything important if fuzz testing is done. | |
diff --git a/undo.c b/undo.c | |
t@@ -101,22 +101,27 @@ undo_change_mode_group(undo_stack *undo) { | |
undo->change_mode_group = 1; | |
} | |
-/* | |
-static void | |
-dump_undo(undo_stack *undo) { | |
- printf("START UNDO STACK\n"); | |
- printf("cur: %zu\n", undo->cur); | |
+#if TEST | |
+void | |
+dump_undo_stack(FILE *file, undo_stack *undo) { | |
+ fprintf( | |
+ file, | |
+ "cur: %zu, cur_valid: %d, change_mode_group: %d, len: %zu, cap: %z… | |
+ undo->cur, undo->cur_valid, undo->change_mode_group, undo->len, un… | |
+ ); | |
for (size_t i = 0; i < undo->len; i++) { | |
undo_elem *e = &undo->stack[i]; | |
- printf( | |
- "type %d, mode %d, group %d, mode_group %d, text '%.*s', r… | |
+ fprintf( | |
+ file, | |
+ "type %d, mode %d, group %d, mode_group %d, text '%.*s', " | |
+ "op_range (%zu,%zu;%zu,%zu), cursor_range (%zu,%zu;%zu,%zu… | |
e->type, e->mode, e->group, e->mode_group, (int)e->text->l… | |
- e->op_range.byte1, e->op_range.byte2 | |
+ e->op_range.line1, e->op_range.byte1, e->op_range.line2, e… | |
+ e->cursor_range.line1, e->cursor_range.byte1, e->cursor_ra… | |
); | |
} | |
- printf("END UNDO STACK\n"); | |
} | |
-*/ | |
+#endif | |
static void | |
push_undo( | |
t@@ -140,7 +145,6 @@ push_undo( | |
txtbuf_copy(e->text, text); | |
else | |
e->text = txtbuf_dup(text); | |
- /* dump_undo(undo); */ | |
} | |
void | |
t@@ -243,7 +247,6 @@ ledit_undo(undo_stack *undo, ledit_mode mode, void *callba… | |
*min_line_ret = min_line; | |
if (mode == NORMAL || mode == VISUAL) | |
undo_change_mode_group(undo); | |
- /* dump_undo(undo); */ | |
return UNDO_NORMAL; | |
} | |
diff --git a/undo.h b/undo.h | |
t@@ -133,4 +133,8 @@ void undo_change_last_cur_range(undo_stack *undo, ledit_ra… | |
*/ | |
char *undo_state_to_str(undo_status s); | |
+#if TEST | |
+void dump_undo_stack(FILE *file, undo_stack *undo); | |
+#endif | |
+ | |
#endif | |
diff --git a/view.h b/view.h | |
t@@ -26,7 +26,7 @@ enum action_type { | |
main event manager what key handler to call next */ | |
struct action { | |
enum action_type type; | |
- struct action (*callback)(ledit_view *view, XEvent *event, int lang_in… | |
+ struct action (*callback)(ledit_view *view, unsigned int key_state, Ke… | |
}; | |
typedef struct { |