commit 225b064c19ce96a38c5472b3d73367922f671dc7
parent 155a081f559a9ecfddd8b086c7d91187c2af4bc2
Author: lumidify <nobody@lumidify.org>
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:
10 files changed, 466 insertions(+), 45 deletions(-)
diff --git a/Makefile b/Makefile
@@ -12,6 +12,7 @@ MAN5 = leditrc.5
MISCFILES = Makefile README LICENSE IDEAS NOTES TODO
DEBUG=0
+TEST=0
SANITIZE=0
ENABLE_UTF8PROC=1
@@ -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 functionality
# of ctrlsel yet, but since it's moderately likely that I will use that in the future, I
# decided to just leave it in.
-CFLAGS_LEDIT = ${EXTRA_FLAGS_SANITIZE${SANITIZE}} ${EXTRA_CFLAGS_DEBUG${DEBUG}} ${EXTRA_CFLAGS_UTF8PROC${ENABLE_UTF8PROC}} -Wall -Wextra -pedantic -D_POSIX_C_SOURCE=200809L -std=c99 `pkg-config --cflags x11 xkbfile pangoxft xext xcursor`
+CFLAGS_LEDIT = -DTEST=${TEST} ${EXTRA_FLAGS_SANITIZE${SANITIZE}} ${EXTRA_CFLAGS_DEBUG${DEBUG}} ${EXTRA_CFLAGS_UTF8PROC${ENABLE_UTF8PROC}} -Wall -Wextra -pedantic -D_POSIX_C_SOURCE=200809L -std=c99 `pkg-config --cflags x11 xkbfile pangoxft xext xcursor`
LDFLAGS_LEDIT = ${EXTRA_FLAGS_SANITIZE${SANITIZE}} ${EXTRA_LDFLAGS_DEBUG${DEBUG}} ${EXTRA_LDFLAGS_UTF8PROC${ENABLE_UTF8PROC}} `pkg-config --libs x11 xkbfile pangoxft xext xcursor` -lm
all: ${BIN}
diff --git a/keys_basic.c b/keys_basic.c
@@ -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 *buf, int n, int lang_index) {
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
@@ -11,6 +11,6 @@ int basic_key_cb_modemask_is_valid(basic_key_cb *cb, ledit_mode modes);
/* perform cleanup of global data */
void basic_key_cleanup(void);
-struct action basic_key_handler(ledit_view *view, XEvent *event, int lang_index);
+struct action basic_key_handler(ledit_view *view, unsigned int key_state, KeySym sym, char *buf, int n, int lang_index);
#endif
diff --git a/keys_command.c b/keys_command.c
@@ -215,11 +215,16 @@ static int parse_range(
static int handle_cmd(ledit_view *view, char *cmd, size_t len, size_t lang_index);
/* 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;
@@ -248,6 +253,7 @@ handle_write(ledit_view *view, char *cmd, size_t l1, size_t l2) {
"%s: file modification time changed; use ! to override",
filename
);
+ return 1;
/* FIXME: I guess the file can still exist if stat returns an error,
but the writing itself will probably fail then as well. */
} else if (!ret && !force && !stored) {
@@ -256,8 +262,10 @@ handle_write(ledit_view *view, char *cmd, size_t l1, size_t l2) {
"%s: file exists; use ! to override",
filename
);
+ return 1;
} else if (buffer_write_to_filename(view->buffer, filename, &errstr)) {
window_show_message_fmt(view->window, "Error writing %s: %s", filename, errstr);
+ return 1;
} else {
/* FIXME: better message */
window_show_message_fmt(view->window, "Wrote file %s", filename);
@@ -270,8 +278,18 @@ handle_write(ledit_view *view, char *cmd, size_t l1, size_t l2) {
}
} 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
@@ -320,7 +338,10 @@ close_view(ledit_view *view, char *cmd, size_t l1, size_t l2) {
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;
@@ -984,14 +1005,9 @@ edit_discard(ledit_view *view, char *key_text, size_t len, size_t lang_index) {
}
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 *buf, int n, int lang_index) {
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
@@ -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_index);
+struct action command_key_handler(ledit_view *view, unsigned int key_state, KeySym sym, char *buf, int n, int lang_index);
#endif
diff --git a/ledit.c b/ledit.c
@@ -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>
@@ -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, char *buf, int n);
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_cur, test_status.read_len - test_status.read_cur);
+ 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_status.read_len + READ_BLK_SIZE);
+ test_status.read = ledit_realloc(test_status.read, test_status.read_alloc);
+ nread = read(fileno(stdin), test_status.read + test_status.read_len, READ_BLK_SIZE);
+ 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[test_status.read_cur] != ' ')
+ 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_cur++) {
+ 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[test_status.read_cur] != ' ')
+ 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_cur++) {
+ 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] = '\n';
+ }
+ } else {
+ test_status.read[test_status.read_cur - offset] = test_status.read[test_status.read_cur];
+ 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 = button_num, .x = x, .y = y}};
+ 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, .button = button_num, .x = x, .y = y}};
+ 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, .y = y}};
+ 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", new_view);
+ 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, errstr);
+ 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", term);
+ } else {
+ fprintf(
+ file,
+ "cursor_line: %zu, cursor_byte: %zu, sel_valid: %d, "
+ "sel_line1: %zu, sel_byte1: %zu, "
+ "sel_line2: %zu, sel_byte2: %zu\n",
+ view->cur_line, view->cur_index, view->sel_valid,
+ 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", term);
+ } 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;
@@ -138,16 +448,20 @@ mainloop(void) {
window_register_resize(view->window, &event);
break;
case ButtonPress:
- window_register_button_press(view->window, &event);
+ if (!test_extra)
+ window_register_button_press(view->window, &event);
break;
case ButtonRelease:
- window_button_release(view->window, &event);
+ if (!test_extra)
+ window_button_release(view->window, &event);
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->window->wm_delete_msg) {
@@ -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);
@@ -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);
@@ -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 n) {
/* 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.callback) {
- view->cur_action = view->cur_action.callback(view, event, cur_lang);
+ view->cur_action = view->cur_action.callback(view, key_state, sym, buf, n, cur_lang);
} else {
- view->cur_action = basic_key_handler(view, event, cur_lang);
+ view->cur_action = basic_key_handler(view, key_state, sym, buf, n, cur_lang);
}
}
+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
@@ -0,0 +1,59 @@
+There aren't any proper tests currently, but some infrastructure is in place to support them.
+
+When compiled with TEST=1, ledit accepts commands on standard input to generate fake events.
+Each command ends in newline. If the last argument is text, it may also contain newlines if
+they are escaped with backslash. Single backslashes that are not in front of a newline are
+just taken verbatim, but two backslashes are collapsed into one. Filenames are a special
+case because they are not allowed to contain '/' or '\0'.
+
+The commands to generate events take raw integers instead of symbolic names for keysyms
+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 keysym.
+
+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 '-t'.
+This disables handling of the regular key press, mouse, and language switch events
+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
@@ -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: %zu\n",
+ undo->cur, undo->cur_valid, undo->change_mode_group, undo->len, undo->cap
+ );
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', range (%zu, %zu)\n",
+ 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)\n",
e->type, e->mode, e->group, e->mode_group, (int)e->text->len, e->text->text,
- e->op_range.byte1, e->op_range.byte2
+ e->op_range.line1, e->op_range.byte1, e->op_range.line2, e->op_range.byte2,
+ e->cursor_range.line1, e->cursor_range.byte1, e->cursor_range.line2, e->cursor_range.byte2
);
}
- printf("END UNDO STACK\n");
}
-*/
+#endif
static void
push_undo(
@@ -140,7 +145,6 @@ push_undo(
txtbuf_copy(e->text, text);
else
e->text = txtbuf_dup(text);
- /* dump_undo(undo); */
}
void
@@ -243,7 +247,6 @@ ledit_undo(undo_stack *undo, ledit_mode mode, void *callback_data,
*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
@@ -133,4 +133,8 @@ void undo_change_last_cur_range(undo_stack *undo, ledit_range cur_range);
*/
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
@@ -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_index);
+ struct action (*callback)(ledit_view *view, unsigned int key_state, KeySym sym, char *buf, int n, int lang_index);
};
typedef struct {