| #include "gtktexthistoryprivate.h" |
| |
| #if 0 |
| # define DEBUG_COMMANDS |
| #endif |
| |
| typedef struct |
| { |
| GtkTextHistory *history; |
| GString *buf; |
| struct { |
| int insert; |
| int bound; |
| } selection; |
| guint can_redo : 1; |
| guint can_undo : 1; |
| guint is_modified : 1; |
| } Text; |
| |
| enum { |
| IGNORE = 0, |
| SET = 1, |
| UNSET = 2, |
| }; |
| |
| enum { |
| IGNORE_SELECT = 0, |
| DO_SELECT = 1, |
| }; |
| |
| enum { |
| INSERT = 1, |
| INSERT_SEQ, |
| BACKSPACE, |
| DELETE_KEY, |
| UNDO, |
| REDO, |
| BEGIN_IRREVERSIBLE, |
| END_IRREVERSIBLE, |
| BEGIN_USER, |
| END_USER, |
| MODIFIED, |
| UNMODIFIED, |
| SELECT, |
| CHECK_SELECT, |
| SET_MAX_UNDO, |
| }; |
| |
| typedef struct |
| { |
| int kind; |
| int location; |
| int end_location; |
| const char *text; |
| const char *expected; |
| int can_undo; |
| int can_redo; |
| int is_modified; |
| int select; |
| } Command; |
| |
| static void |
| do_change_state (gpointer funcs_data, |
| gboolean is_modified, |
| gboolean can_undo, |
| gboolean can_redo) |
| { |
| Text *text = funcs_data; |
| |
| text->is_modified = is_modified; |
| text->can_undo = can_undo; |
| text->can_redo = can_redo; |
| } |
| |
| static void |
| do_insert (gpointer funcs_data, |
| guint begin, |
| guint end, |
| const char *text, |
| guint len) |
| { |
| Text *t = funcs_data; |
| |
| #ifdef DEBUG_COMMANDS |
| g_printerr ("Insert Into '%s' (begin=%u end=%u text=%s)\n", |
| t->buf->str, begin, end, text); |
| #endif |
| |
| g_string_insert_len (t->buf, begin, text, len); |
| } |
| |
| static void |
| do_delete (gpointer funcs_data, |
| guint begin, |
| guint end, |
| const char *expected_text, |
| guint len) |
| { |
| Text *t = funcs_data; |
| |
| #ifdef DEBUG_COMMANDS |
| g_printerr ("Delete(begin=%u end=%u expected_text=%s)\n", begin, end, expected_text); |
| #endif |
| |
| if (end < begin) |
| { |
| guint tmp = end; |
| end = begin; |
| begin = tmp; |
| } |
| |
| g_assert_cmpint (memcmp (t->buf->str + begin, expected_text, len), ==, 0); |
| |
| if (end >= t->buf->len) |
| { |
| t->buf->len = begin; |
| t->buf->str[begin] = 0; |
| return; |
| } |
| |
| memmove (t->buf->str + begin, |
| t->buf->str + end, |
| t->buf->len - end); |
| g_string_truncate (t->buf, t->buf->len - (end - begin)); |
| } |
| |
| static void |
| do_select (gpointer funcs_data, |
| int selection_insert, |
| int selection_bound) |
| { |
| Text *text = funcs_data; |
| |
| text->selection.insert = selection_insert; |
| text->selection.bound = selection_bound; |
| } |
| |
| static GtkTextHistoryFuncs funcs = { |
| do_change_state, |
| do_insert, |
| do_delete, |
| do_select, |
| }; |
| |
| static Text * |
| text_new (void) |
| { |
| Text *text = g_slice_new0 (Text); |
| |
| text->history = gtk_text_history_new (&funcs, text); |
| text->buf = g_string_new (NULL); |
| text->selection.insert = -1; |
| text->selection.bound = -1; |
| |
| return text; |
| } |
| |
| static void |
| text_free (Text *text) |
| { |
| g_object_unref (text->history); |
| g_string_free (text->buf, TRUE); |
| g_slice_free (Text, text); |
| } |
| |
| static void |
| command_insert (const Command *cmd, |
| Text *text) |
| { |
| do_insert (text, |
| cmd->location, |
| cmd->location + g_utf8_strlen (cmd->text, -1), |
| cmd->text, |
| strlen (cmd->text)); |
| gtk_text_history_text_inserted (text->history, cmd->location, cmd->text, -1); |
| } |
| |
| static void |
| command_delete_key (const Command *cmd, |
| Text *text) |
| { |
| do_delete (text, |
| cmd->location, |
| cmd->end_location, |
| cmd->text, |
| strlen (cmd->text)); |
| gtk_text_history_text_deleted (text->history, |
| cmd->location, |
| cmd->end_location, |
| cmd->text, |
| ABS (cmd->end_location - cmd->location)); |
| } |
| |
| static void |
| command_undo (const Command *cmd, |
| Text *text) |
| { |
| gtk_text_history_undo (text->history); |
| } |
| |
| static void |
| command_redo (const Command *cmd, |
| Text *text) |
| { |
| gtk_text_history_redo (text->history); |
| } |
| |
| static void |
| set_selection (Text *text, |
| int begin, |
| int end) |
| { |
| gtk_text_history_selection_changed (text->history, begin, end); |
| } |
| |
| static void |
| run_test (const Command *commands, |
| guint n_commands, |
| guint max_undo) |
| { |
| Text *text = text_new (); |
| |
| if (max_undo) |
| gtk_text_history_set_max_undo_levels (text->history, max_undo); |
| |
| for (guint i = 0; i < n_commands; i++) |
| { |
| const Command *cmd = &commands[i]; |
| |
| #ifdef DEBUG_COMMANDS |
| g_printerr ("%d: %d\n", i, cmd->kind); |
| #endif |
| |
| switch (cmd->kind) |
| { |
| case INSERT: |
| command_insert (cmd, text); |
| break; |
| |
| case INSERT_SEQ: |
| for (guint j = 0; cmd->text[j]; j++) |
| { |
| const char seqtext[2] = { cmd->text[j], 0 }; |
| Command seq = { INSERT, cmd->location + j, 1, seqtext, NULL }; |
| command_insert (&seq, text); |
| } |
| break; |
| |
| case DELETE_KEY: |
| if (cmd->select == DO_SELECT) |
| set_selection (text, cmd->location, cmd->end_location); |
| else if (strlen (cmd->text) == 1) |
| set_selection (text, cmd->location, -1); |
| else |
| set_selection (text, -1, -1); |
| command_delete_key (cmd, text); |
| break; |
| |
| case BACKSPACE: |
| if (cmd->select == DO_SELECT) |
| set_selection (text, cmd->location, cmd->end_location); |
| else if (strlen (cmd->text) == 1) |
| set_selection (text, cmd->end_location, -1); |
| else |
| set_selection (text, -1, -1); |
| command_delete_key (cmd, text); |
| break; |
| |
| case UNDO: |
| command_undo (cmd, text); |
| break; |
| |
| case REDO: |
| command_redo (cmd, text); |
| break; |
| |
| case BEGIN_USER: |
| gtk_text_history_begin_user_action (text->history); |
| break; |
| |
| case END_USER: |
| gtk_text_history_end_user_action (text->history); |
| break; |
| |
| case BEGIN_IRREVERSIBLE: |
| gtk_text_history_begin_irreversible_action (text->history); |
| break; |
| |
| case END_IRREVERSIBLE: |
| gtk_text_history_end_irreversible_action (text->history); |
| break; |
| |
| case MODIFIED: |
| gtk_text_history_modified_changed (text->history, TRUE); |
| break; |
| |
| case UNMODIFIED: |
| gtk_text_history_modified_changed (text->history, FALSE); |
| break; |
| |
| case SELECT: |
| gtk_text_history_selection_changed (text->history, |
| cmd->location, |
| cmd->end_location); |
| break; |
| |
| case CHECK_SELECT: |
| g_assert_cmpint (text->selection.insert, ==, cmd->location); |
| g_assert_cmpint (text->selection.bound, ==, cmd->end_location); |
| break; |
| |
| case SET_MAX_UNDO: |
| /* Not ideal use of location, but fine */ |
| gtk_text_history_set_max_undo_levels (text->history, cmd->location); |
| break; |
| |
| default: |
| break; |
| } |
| |
| if (cmd->expected) |
| g_assert_cmpstr (text->buf->str, ==, cmd->expected); |
| |
| if (cmd->can_redo == SET) |
| g_assert_cmpint (text->can_redo, ==, TRUE); |
| else if (cmd->can_redo == UNSET) |
| g_assert_cmpint (text->can_redo, ==, FALSE); |
| |
| if (cmd->can_undo == SET) |
| g_assert_cmpint (text->can_undo, ==, TRUE); |
| else if (cmd->can_undo == UNSET) |
| g_assert_cmpint (text->can_undo, ==, FALSE); |
| |
| if (cmd->is_modified == SET) |
| g_assert_cmpint (text->is_modified, ==, TRUE); |
| else if (cmd->is_modified == UNSET) |
| g_assert_cmpint (text->is_modified, ==, FALSE); |
| } |
| |
| text_free (text); |
| } |
| |
| static void |
| test1 (void) |
| { |
| static const Command commands[] = { |
| { INSERT, 0, -1, "test", "test", SET, UNSET }, |
| { INSERT, 2, -1, "s", "tesst", SET, UNSET }, |
| { INSERT, 3, -1, "ss", "tesssst", SET, UNSET }, |
| { DELETE_KEY, 2, 5, "sss", "test", SET, UNSET }, |
| { UNDO, -1, -1, NULL, "tesssst", SET, SET }, |
| { REDO, -1, -1, NULL, "test", SET, UNSET }, |
| { UNDO, -1, -1, NULL, "tesssst", SET, SET }, |
| { DELETE_KEY, 0, 7, "tesssst", "", SET, UNSET }, |
| { INSERT, 0, -1, "z", "z", SET, UNSET }, |
| { UNDO, -1, -1, NULL, "", SET, SET }, |
| { UNDO, -1, -1, NULL, "tesssst", SET, SET }, |
| { UNDO, -1, -1, NULL, "test", SET, SET }, |
| }; |
| |
| run_test (commands, G_N_ELEMENTS (commands), 0); |
| } |
| |
| static void |
| test2 (void) |
| { |
| static const Command commands[] = { |
| { BEGIN_IRREVERSIBLE, -1, -1, NULL, "", UNSET, UNSET }, |
| { INSERT, 0, -1, "this is a test", "this is a test", UNSET, UNSET }, |
| { END_IRREVERSIBLE, -1, -1, NULL, "this is a test", UNSET, UNSET }, |
| { UNDO, -1, -1, NULL, "this is a test", UNSET, UNSET }, |
| { REDO, -1, -1, NULL, "this is a test", UNSET, UNSET }, |
| { BEGIN_USER, -1, -1, NULL, NULL, UNSET, UNSET }, |
| { INSERT, 0, -1, "first", "firstthis is a test", UNSET, UNSET }, |
| { INSERT, 5, -1, " ", "first this is a test", UNSET, UNSET }, |
| { END_USER, -1, -1, NULL, "first this is a test", SET, UNSET }, |
| { UNDO, -1, -1, NULL, "this is a test", UNSET, SET }, |
| { UNDO, -1, -1, NULL, "this is a test", UNSET, SET }, |
| { REDO, -1, -1, NULL, "first this is a test", SET, UNSET }, |
| { UNDO, -1, -1, NULL, "this is a test", UNSET, SET }, |
| }; |
| |
| run_test (commands, G_N_ELEMENTS (commands), 0); |
| } |
| |
| static void |
| test3 (void) |
| { |
| static const Command commands[] = { |
| { INSERT_SEQ, 0, -1, "this is a test of insertions.", "this is a test of insertions.", SET, UNSET }, |
| { UNDO, -1, -1, NULL, "this is a test of", SET, SET }, |
| { UNDO, -1, -1, NULL, "this is a test", SET, SET }, |
| { UNDO, -1, -1, NULL, "this is a", SET, SET }, |
| { UNDO, -1, -1, NULL, "this is", SET, SET }, |
| { UNDO, -1, -1, NULL, "this", SET, SET }, |
| { UNDO, -1, -1, NULL, "", UNSET, SET }, |
| { UNDO, -1, -1, NULL, "" , UNSET, SET }, |
| { REDO, -1, -1, NULL, "this", SET, SET }, |
| { REDO, -1, -1, NULL, "this is", SET, SET }, |
| { REDO, -1, -1, NULL, "this is a", SET, SET }, |
| { REDO, -1, -1, NULL, "this is a test", SET, SET }, |
| { REDO, -1, -1, NULL, "this is a test of", SET, SET }, |
| { REDO, -1, -1, NULL, "this is a test of insertions.", SET, UNSET }, |
| }; |
| |
| run_test (commands, G_N_ELEMENTS (commands), 0); |
| } |
| |
| static void |
| test4 (void) |
| { |
| static const Command commands[] = { |
| { INSERT, 0, -1, "initial text", "initial text", SET, UNSET }, |
| /* Barrier */ |
| { BEGIN_IRREVERSIBLE, -1, -1, NULL, NULL, UNSET, UNSET }, |
| { END_IRREVERSIBLE, -1, -1, NULL, NULL, UNSET, UNSET }, |
| { INSERT, 0, -1, "more text ", "more text initial text", SET, UNSET }, |
| { UNDO, -1, -1, NULL, "initial text", UNSET, SET }, |
| { UNDO, -1, -1, NULL, "initial text", UNSET, SET }, |
| { REDO, -1, -1, NULL, "more text initial text", SET, UNSET }, |
| /* Barrier */ |
| { BEGIN_IRREVERSIBLE, UNSET, UNSET }, |
| { END_IRREVERSIBLE, UNSET, UNSET }, |
| { UNDO, -1, -1, NULL, "more text initial text", UNSET, UNSET }, |
| }; |
| |
| run_test (commands, G_N_ELEMENTS (commands), 0); |
| } |
| |
| static void |
| test5 (void) |
| { |
| static const Command commands[] = { |
| { INSERT, 0, -1, "initial text", "initial text", SET, UNSET }, |
| { DELETE_KEY, 0, 12, "initial text", "", SET, UNSET }, |
| /* Add empty nested user action (should get ignored) */ |
| { BEGIN_USER, -1, -1, NULL, NULL, UNSET, UNSET }, |
| { BEGIN_USER, -1, -1, NULL, NULL, UNSET, UNSET }, |
| { BEGIN_USER, -1, -1, NULL, NULL, UNSET, UNSET }, |
| { END_USER, -1, -1, NULL, NULL, UNSET, UNSET }, |
| { END_USER, -1, -1, NULL, NULL, UNSET, UNSET }, |
| { END_USER, -1, -1, NULL, NULL, SET, UNSET }, |
| { UNDO, -1, -1, NULL, "initial text" }, |
| }; |
| |
| run_test (commands, G_N_ELEMENTS (commands), 0); |
| } |
| |
| static void |
| test6 (void) |
| { |
| static const Command commands[] = { |
| { INSERT_SEQ, 0, -1, " \t\t this is some text", " \t\t this is some text", SET, UNSET }, |
| { UNDO, -1, -1, NULL, " \t\t this is some", SET, SET }, |
| { UNDO, -1, -1, NULL, " \t\t this is", SET, SET }, |
| { UNDO, -1, -1, NULL, " \t\t this", SET, SET }, |
| { UNDO, -1, -1, NULL, "", UNSET, SET }, |
| { UNDO, -1, -1, NULL, "", UNSET, SET }, |
| }; |
| |
| run_test (commands, G_N_ELEMENTS (commands), 0); |
| } |
| |
| static void |
| test7 (void) |
| { |
| static const Command commands[] = { |
| { MODIFIED, -1, -1, NULL, NULL, UNSET, UNSET, SET }, |
| { UNMODIFIED, -1, -1, NULL, NULL, UNSET, UNSET, UNSET }, |
| { INSERT, 0, -1, "foo bar", "foo bar", SET, UNSET, UNSET }, |
| { MODIFIED, -1, -1, NULL, NULL, SET, UNSET, SET }, |
| { UNDO, -1, -1, NULL, "", UNSET, SET, UNSET }, |
| { REDO, -1, -1, NULL, "foo bar", SET, UNSET, SET }, |
| { UNDO, -1, -1, NULL, "", UNSET, SET, UNSET }, |
| { REDO, -1, -1, NULL, "foo bar", SET, UNSET, SET }, |
| }; |
| |
| run_test (commands, G_N_ELEMENTS (commands), 0); |
| } |
| |
| static void |
| test8 (void) |
| { |
| static const Command commands[] = { |
| { INSERT, 0, -1, "foo bar", "foo bar", SET, UNSET, UNSET }, |
| { MODIFIED, -1, -1, NULL, NULL, SET, UNSET, SET }, |
| { INSERT, 0, -1, "f", "ffoo bar", SET, UNSET, SET }, |
| { UNMODIFIED, -1, -1, NULL, NULL, SET, UNSET, UNSET }, |
| { UNDO, -1, -1, NULL, "foo bar", SET, SET, SET }, |
| { UNDO, -1, -1, NULL, "", UNSET, SET, SET }, |
| { REDO, -1, -1, NULL, "foo bar", SET, SET, SET }, |
| { REDO, -1, -1, NULL, "ffoo bar", SET, UNSET, UNSET }, |
| }; |
| |
| run_test (commands, G_N_ELEMENTS (commands), 0); |
| } |
| |
| static void |
| test9 (void) |
| { |
| static const Command commands[] = { |
| { INSERT, 0, -1, "foo bar", "foo bar", SET, UNSET, UNSET }, |
| { DELETE_KEY, 0, 3, "foo", " bar", SET, UNSET, UNSET, DO_SELECT }, |
| { DELETE_KEY, 0, 4, " bar", "", SET, UNSET, UNSET, DO_SELECT }, |
| { UNDO, -1, -1, NULL, " bar", SET, SET, UNSET }, |
| { CHECK_SELECT, 0, 4, NULL, " bar", SET, SET, UNSET }, |
| { UNDO, -1, -1, NULL, "foo bar", SET, SET, UNSET }, |
| { CHECK_SELECT, 0, 3, NULL, "foo bar", SET, SET, UNSET }, |
| { BEGIN_IRREVERSIBLE, -1, -1, NULL, "foo bar", UNSET, UNSET, UNSET }, |
| { END_IRREVERSIBLE, -1, -1, NULL, "foo bar", UNSET, UNSET, UNSET }, |
| }; |
| |
| run_test (commands, G_N_ELEMENTS (commands), 0); |
| } |
| |
| static void |
| test10 (void) |
| { |
| static const Command commands[] = { |
| { BEGIN_USER }, { INSERT, 0, -1, "t", "t", UNSET, UNSET, UNSET }, { END_USER }, |
| { BEGIN_USER }, { INSERT, 1, -1, " ", "t ", UNSET, UNSET, UNSET }, { END_USER }, |
| { BEGIN_USER }, { INSERT, 2, -1, "t", "t t", UNSET, UNSET, UNSET }, { END_USER }, |
| { BEGIN_USER }, { INSERT, 3, -1, "h", "t th", UNSET, UNSET, UNSET }, { END_USER }, |
| { BEGIN_USER }, { INSERT, 4, -1, "i", "t thi", UNSET, UNSET, UNSET }, { END_USER }, |
| { BEGIN_USER }, { INSERT, 5, -1, "s", "t this", UNSET, UNSET, UNSET }, { END_USER }, |
| }; |
| |
| run_test (commands, G_N_ELEMENTS (commands), 0); |
| } |
| |
| static void |
| test11 (void) |
| { |
| /* Test backspace */ |
| static const Command commands[] = { |
| { INSERT_SEQ, 0, -1, "insert some text", "insert some text", SET, UNSET, UNSET }, |
| { BACKSPACE, 15, 16, "t", "insert some tex", SET, UNSET, UNSET }, |
| { BACKSPACE, 14, 15, "x", "insert some te", SET, UNSET, UNSET }, |
| { BACKSPACE, 13, 14, "e", "insert some t", SET, UNSET, UNSET }, |
| { BACKSPACE, 12, 13, "t", "insert some ", SET, UNSET, UNSET }, |
| { UNDO, -1, -1, NULL, "insert some text", SET, SET, UNSET }, |
| }; |
| |
| run_test (commands, G_N_ELEMENTS (commands), 0); |
| } |
| |
| static void |
| test12 (void) |
| { |
| static const Command commands[] = { |
| { INSERT_SEQ, 0, -1, "this is a test\nmore", "this is a test\nmore", SET, UNSET, UNSET }, |
| { UNDO, -1, -1, NULL, "this is a test\n", SET, SET, UNSET }, |
| { UNDO, -1, -1, NULL, "this is a test", SET, SET, UNSET }, |
| { UNDO, -1, -1, NULL, "this is a", SET, SET, UNSET }, |
| { UNDO, -1, -1, NULL, "this is", SET, SET, UNSET }, |
| { UNDO, -1, -1, NULL, "this", SET, SET, UNSET }, |
| { UNDO, -1, -1, NULL, "", UNSET, SET, UNSET }, |
| }; |
| |
| run_test (commands, G_N_ELEMENTS (commands), 0); |
| } |
| |
| static void |
| test13 (void) |
| { |
| static const Command commands[] = { |
| { INSERT_SEQ, 0, -1, "this is a test\nmore", "this is a test\nmore", SET, UNSET, UNSET }, |
| { UNDO, -1, -1, NULL, "this is a test\n", SET, SET, UNSET }, |
| { UNDO, -1, -1, NULL, "this is a test", SET, SET, UNSET }, |
| { UNDO, -1, -1, NULL, "this is a", UNSET, SET, UNSET }, |
| { UNDO, -1, -1, NULL, "this is a", UNSET, SET, UNSET }, |
| { SET_MAX_UNDO, 2, -1, NULL, "this is a", UNSET, SET, UNSET }, |
| { REDO, -1, -1, NULL, "this is a test", SET, SET, UNSET }, |
| { REDO, -1, -1, NULL, "this is a test\n", SET, UNSET, UNSET }, |
| { REDO, -1, -1, NULL, "this is a test\n", SET, UNSET, UNSET }, |
| }; |
| |
| run_test (commands, G_N_ELEMENTS (commands), 3); |
| } |
| |
| int |
| main (int argc, |
| char *argv[]) |
| { |
| g_test_init (&argc, &argv, NULL); |
| g_test_add_func ("/Gtk/TextHistory/test1", test1); |
| g_test_add_func ("/Gtk/TextHistory/test2", test2); |
| g_test_add_func ("/Gtk/TextHistory/test3", test3); |
| g_test_add_func ("/Gtk/TextHistory/test4", test4); |
| g_test_add_func ("/Gtk/TextHistory/test5", test5); |
| g_test_add_func ("/Gtk/TextHistory/test6", test6); |
| g_test_add_func ("/Gtk/TextHistory/test7", test7); |
| g_test_add_func ("/Gtk/TextHistory/test8", test8); |
| g_test_add_func ("/Gtk/TextHistory/test9", test9); |
| g_test_add_func ("/Gtk/TextHistory/test10", test10); |
| g_test_add_func ("/Gtk/TextHistory/test11", test11); |
| g_test_add_func ("/Gtk/TextHistory/test12", test12); |
| g_test_add_func ("/Gtk/TextHistory/test13", test13); |
| return g_test_run (); |
| } |