From e45499816eab8abadbdd5bb6dd79b526a4ed6648 Mon Sep 17 00:00:00 2001 From: Albert Cervin Date: Sat, 11 Feb 2023 23:03:39 +0100 Subject: Implement undo This also fixes a bunch of valgrind errors --- test/buffer.c | 2 ++ test/command.c | 6 ++++ test/keyboard.c | 1 + test/main.c | 3 ++ test/minibuffer.c | 21 ++++++++++- test/test.h | 1 + test/text.c | 36 ++++++++++--------- test/undo.c | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 157 insertions(+), 17 deletions(-) create mode 100644 test/undo.c (limited to 'test') diff --git a/test/buffer.c b/test/buffer.c index 9781a6d..38ce468 100644 --- a/test/buffer.c +++ b/test/buffer.c @@ -41,6 +41,8 @@ void test_move() { ASSERT( b.dot.col == 0 && b.dot.line == 0, "Expected to not be able to move backwards when at beginning of buffer"); + + buffer_destroy(&b); } void run_buffer_tests() { run_test(test_move); } diff --git a/test/command.c b/test/command.c index be5fffc..09de7f4 100644 --- a/test/command.c +++ b/test/command.c @@ -53,6 +53,8 @@ void test_register_command() { "Expected number of commands to be 3 after inserting two more"); ASSERT(cmds.capacity > 1, "Expected capacity to have increased to accommodate new commands"); + + command_registry_destroy(&cmds); } void test_lookup_command() { @@ -70,6 +72,8 @@ void test_lookup_command() { "Expected to be able to look up inserted command by hash"); ASSERT_STR_EQ(cmd->name, "fake", "Expected the found function to have the correct name"); + + command_registry_destroy(&cmds); } int32_t failing_command(struct command_ctx ctx, int argc, const char *argv[]) { @@ -91,6 +95,8 @@ void test_execute_command() { struct command *fail_cmd = lookup_command(&cmds, "fejl"); int32_t res2 = execute_command(fail_cmd, &cmds, NULL, NULL, 0, NULL); ASSERT(res2 != 0, "Expected failing command to fail"); + + command_registry_destroy(&cmds); } void run_command_tests() { diff --git a/test/keyboard.c b/test/keyboard.c index b85c4ad..a76d2a7 100644 --- a/test/keyboard.c +++ b/test/keyboard.c @@ -68,6 +68,7 @@ void fake_keyboard_close_write(struct fake_keyboard *kbd) { void fake_keyboard_destroy(struct fake_keyboard *kbd) { fake_keyboard_close_write(kbd); + reactor_destroy(kbd->reactor); } void simple_key() { diff --git a/test/main.c b/test/main.c index 7663f8f..a999b43 100644 --- a/test/main.c +++ b/test/main.c @@ -21,6 +21,9 @@ int main() { printf("\n📜 \x1b[1;36mRunning text tests...\x1b[0m\n"); run_text_tests(); + printf("\n⏪ \x1b[1;36mRunning undo tests...\x1b[0m\n"); + run_undo_tests(); + printf("\n🕴️ \x1b[1;36mRunning buffer tests...\x1b[0m\n"); run_buffer_tests(); diff --git a/test/minibuffer.c b/test/minibuffer.c index dc29648..0e41c7b 100644 --- a/test/minibuffer.c +++ b/test/minibuffer.c @@ -2,12 +2,17 @@ #include "stdlib.h" #include "test.h" +#include "allocator.h" #include "buffer.h" #include "display.h" #include "minibuffer.h" static struct buffer b = {0}; +static struct frame_allocator *g_alloc = NULL; + +void *alloc_fn(size_t sz) { return frame_allocator_alloc(g_alloc, sz); } + void init() { if (b.name == NULL) { b = buffer_create("minibuffer", false); @@ -16,12 +21,21 @@ void init() { minibuffer_init(&b); } +void destroy() { + if (b.name != NULL) { + buffer_destroy(&b); + } +} + void test_minibuffer_echo() { uint32_t relline, relcol; // TODO: how to clear this? + struct frame_allocator alloc = frame_allocator_create(1024); + g_alloc = &alloc; + struct command_list *list = - command_list_create(10, malloc, 0, 0, "minibuffer"); + command_list_create(10, alloc_fn, 0, 0, "minibuffer"); init(); ASSERT(!minibuffer_displaying(), @@ -38,6 +52,10 @@ void test_minibuffer_echo() { buffer_update(&b, 100, 1, list, 0, &relline, &relcol); ASSERT(!minibuffer_displaying(), "A zero timeout echo should be cleared after first update"); + + frame_allocator_destroy(&alloc); + g_alloc = NULL; + destroy(); } int32_t fake(struct command_ctx ctx, int argc, const char *argv[]) { return 0; } @@ -64,6 +82,7 @@ void test_minibuffer_prompt() { minibuffer_abort_prompt(); ASSERT(!minibuffer_focused(), "Minibuffer must not be focused after prompt has been aborted"); + destroy(); } void run_minibuffer_tests() { diff --git a/test/test.h b/test/test.h index f777916..4dab62e 100644 --- a/test/test.h +++ b/test/test.h @@ -12,6 +12,7 @@ void run_buffer_tests(); void run_utf8_tests(); void run_text_tests(); +void run_undo_tests(); void run_command_tests(); void run_keyboard_tests(); void run_allocator_tests(); diff --git a/test/text.c b/test/text.c index 9c8d825..cb18df5 100644 --- a/test/text.c +++ b/test/text.c @@ -8,6 +8,10 @@ #include #include +void assert_line_eq(struct text_chunk line, const char *txt, const char *msg) { + ASSERT(strncmp((const char *)line.text, txt, line.nbytes) == 0, msg); +} + void assert_line_equal(struct text_chunk *line) {} void test_add_text() { @@ -24,16 +28,16 @@ void test_add_text() { ASSERT(text_line_size(t, 0) == 14 && text_line_length(t, 0) == 14, "Expected line 1 to have 14 chars and 14 bytes"); - ASSERT_STR_EQ((const char *)text_get_line(t, 0).text, "This is line 1", - "Expected line 1 to be line 1"); + assert_line_eq(text_get_line(t, 0), "This is line 1", + "Expected line 1 to be line 1"); const char *txt2 = "This is line 2\n"; text_insert_at(t, 1, 0, (uint8_t *)txt2, strlen(txt2), &lines_added, &cols_added); ASSERT(text_num_lines(t) == 2, "Expected text to have two lines after second insertion"); - ASSERT_STR_EQ((const char *)text_get_line(t, 1).text, "This is line 2", - "Expected line 2 to be line 2"); + assert_line_eq(text_get_line(t, 1), "This is line 2", + "Expected line 2 to be line 2"); // simulate indentation const char *txt3 = " "; @@ -41,19 +45,18 @@ void test_add_text() { &cols_added); ASSERT(text_num_lines(t) == 2, "Expected text to have two lines after second insertion"); - ASSERT_STR_EQ((const char *)text_get_line(t, 0).text, " This is line 1", - "Expected line 1 to be indented"); - ASSERT_STR_EQ((const char *)text_get_line(t, 1).text, "This is line 2", - "Expected line 2 to be line 2 still"); + assert_line_eq(text_get_line(t, 0), " This is line 1", + "Expected line 1 to be indented"); + assert_line_eq(text_get_line(t, 1), "This is line 2", + "Expected line 2 to be line 2 still"); // insert newline in middle of line text_insert_at(t, 1, 4, (uint8_t *)"\n", 1, &lines_added, &cols_added); ASSERT(text_num_lines(t) == 3, "Expected text to have three lines after inserting a new line"); - ASSERT_STR_EQ((const char *)text_get_line(t, 1).text, "This", - "Expected line 2 to be split"); - ASSERT_STR_EQ((const char *)text_get_line(t, 2).text, " is line 2", - "Expected line 2 to be split"); + assert_line_eq(text_get_line(t, 1), "This", "Expected line 2 to be split"); + assert_line_eq(text_get_line(t, 2), " is line 2", + "Expected line 2 to be split"); // insert newline before line 1 text_insert_at(t, 1, 0, (uint8_t *)"\n", 1, &lines_added, &cols_added); @@ -61,10 +64,10 @@ void test_add_text() { text_num_lines(t) == 4, "Expected to have four lines after adding an empty line in the middle"); ASSERT(text_line_length(t, 1) == 0, "Expected line 2 to be empty"); - ASSERT_STR_EQ((const char *)text_get_line(t, 2).text, "This", - "Expected line 3 to be previous line 2"); - ASSERT_STR_EQ((const char *)text_get_line(t, 3).text, " is line 2", - "Expected line 4 to be previous line 3"); + assert_line_eq(text_get_line(t, 2), "This", + "Expected line 3 to be previous line 2"); + assert_line_eq(text_get_line(t, 3), " is line 2", + "Expected line 4 to be previous line 3"); text_destroy(t); } @@ -151,6 +154,7 @@ void test_delete_text() { text_destroy(t); text_destroy(t2); text_destroy(t3); + text_destroy(t4); } void run_text_tests() { diff --git a/test/undo.c b/test/undo.c new file mode 100644 index 0000000..d0e2fdb --- /dev/null +++ b/test/undo.c @@ -0,0 +1,104 @@ +#include "assert.h" +#include "test.h" + +#include "undo.h" +#include + +void test_undo_insert() { + struct undo_stack undo; + + /* small capacity on purpose to force re-sizing */ + undo_init(&undo, 1); + + undo_push_boundary(&undo, (struct undo_boundary){.save_point = true}); + ASSERT(undo_size(&undo) == 1, + "Expected undo stack to have one item after inserting a save point"); + + undo_push_boundary(&undo, (struct undo_boundary){}); + ASSERT(undo_size(&undo) == 2, + "Expected undo stack to have two items after inserting a boundary"); + + undo_push_add(&undo, (struct undo_add){.begin = {.col = 0, .row = 0}, + .end = {.col = 4, .row = 0}}); + ASSERT(undo_size(&undo) == 3, + "Expected undo stack to have three items after inserting an add"); + + undo_push_delete(&undo, (struct undo_delete){.pos = {.row = 0, .col = 3}, + .data = NULL, + .nbytes = 0}); + + ASSERT(undo_size(&undo) == 4, + "Expected undo stack to have four items after inserting a delete"); + + ASSERT(undo_current_position(&undo) == undo_size(&undo) - 1, + "Undo stack position should be at the top after inserting"); + + undo_destroy(&undo); +} + +void test_undo() { + struct undo_stack undo; + undo_init(&undo, 10); + + undo_push_boundary(&undo, (struct undo_boundary){.save_point = true}); + undo_push_add(&undo, (struct undo_add){.begin = {.row = 0, .col = 10}, + .end = {.row = 2, .col = 3}}); + + struct undo_record *records = NULL; + uint32_t nrecords = 0; + + undo_next(&undo, &records, &nrecords); + ASSERT(nrecords == 2, "Expected to get back two records"); + ASSERT(records[0].type == Undo_Add, + "Expected first returned record to be an add"); + free(records); + + ASSERT(undo_current_position(&undo) == INVALID_TOP, + "Expected undo stack position to have changed after undo"); + + // check that undo begin causes the top of the stack to not be reset + undo_begin(&undo); + undo_push_add(&undo, (struct undo_add){.begin = {.row = 0, .col = 10}, + .end = {.row = 0, .col = 12}}); + undo_end(&undo); + + ASSERT(undo_current_position(&undo) == INVALID_TOP, + "Expected undo stack position to not have changed when undo " + "information was added as part of an undo"); + + // but now it should + undo_push_add(&undo, (struct undo_add){.begin = {.row = 0, .col = 10}, + .end = {.row = 0, .col = 12}}); + + ASSERT(undo_current_position(&undo) == 3, + "Expected undo stack position to have changed when undo information " + "was added"); + + // test that it gets reset to top + undo_begin(&undo); + records = NULL; + undo_next(&undo, &records, &nrecords); + free(records); + + undo_push_add(&undo, (struct undo_add){.begin = {.row = 0, .col = 10}, + .end = {.row = 0, .col = 12}}); + undo_push_boundary(&undo, (struct undo_boundary){.save_point = false}); + undo_push_add(&undo, (struct undo_add){.begin = {.row = 0, .col = 10}, + .end = {.row = 0, .col = 12}}); + + records = NULL; + undo_next(&undo, &records, &nrecords); + free(records); + + undo_end(&undo); + ASSERT( + undo_current_position(&undo) == 4, + "Expected undo stack position to have been reset when it reached zero"); + + undo_destroy(&undo); +} + +void run_undo_tests() { + run_test(test_undo_insert); + run_test(test_undo); +} -- cgit v1.2.3