mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-12-25 21:21:21 +00:00
texthistory: add GtkTextHistory helper
The GtkTextHistory helper provides the fundamental undo/redo stack that can be integrated with other text widgets. It allows coalescing related actions to reduce both the number of undo actions to the user as well as the memory overhead. A new istring helper is used by GtkTextHistory to allow for "inline strings" that gracefully grow to using allocations with g_realloc(). This ensure that most undo operations require no additional allocations other than the struct for the action itself. A queue of undoable and redoable actions are maintained and the link for the queue is embedded in the undo action union. This allows again, for reducing the number of allocations involved for undo operations.
This commit is contained in:
parent
fbea677a5c
commit
5e341210a1
171
gtk/gtkistringprivate.h
Normal file
171
gtk/gtkistringprivate.h
Normal file
@ -0,0 +1,171 @@
|
||||
#ifndef __GTK_ISTRING_PRIVATE_H__
|
||||
#define __GTK_ISTRING_PRIVATE_H__
|
||||
|
||||
#include <glib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
guint n_bytes;
|
||||
guint n_chars;
|
||||
union {
|
||||
char buf[24];
|
||||
char *str;
|
||||
} u;
|
||||
} IString;
|
||||
|
||||
static inline gboolean
|
||||
istring_is_inline (const IString *str)
|
||||
{
|
||||
return str->n_bytes <= (sizeof str->u.buf - 1);
|
||||
}
|
||||
|
||||
static inline char *
|
||||
istring_str (IString *str)
|
||||
{
|
||||
if (istring_is_inline (str))
|
||||
return str->u.buf;
|
||||
else
|
||||
return str->u.str;
|
||||
}
|
||||
|
||||
static inline void
|
||||
istring_clear (IString *str)
|
||||
{
|
||||
if (istring_is_inline (str))
|
||||
str->u.buf[0] = 0;
|
||||
else
|
||||
g_clear_pointer (&str->u.str, g_free);
|
||||
|
||||
str->n_bytes = 0;
|
||||
str->n_chars = 0;
|
||||
}
|
||||
|
||||
static inline void
|
||||
istring_set (IString *str,
|
||||
const char *text,
|
||||
guint n_bytes,
|
||||
guint n_chars)
|
||||
{
|
||||
if G_LIKELY (n_bytes <= (sizeof str->u.buf - 1))
|
||||
{
|
||||
memcpy (str->u.buf, text, n_bytes);
|
||||
str->u.buf[n_bytes] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
str->u.str = g_strndup (text, n_bytes);
|
||||
}
|
||||
|
||||
str->n_bytes = n_bytes;
|
||||
str->n_chars = n_chars;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
istring_empty (IString *str)
|
||||
{
|
||||
return str->n_bytes == 0;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
istring_ends_with_space (IString *str)
|
||||
{
|
||||
return g_ascii_isspace (istring_str (str)[str->n_bytes - 1]);
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
istring_starts_with_space (IString *str)
|
||||
{
|
||||
return g_unichar_isspace (g_utf8_get_char (istring_str (str)));
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
istring_contains_unichar (IString *str,
|
||||
gunichar ch)
|
||||
{
|
||||
return g_utf8_strchr (istring_str (str), str->n_bytes, ch) != NULL;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
istring_only_contains_space (IString *str)
|
||||
{
|
||||
const char *iter;
|
||||
|
||||
for (iter = istring_str (str); *iter; iter = g_utf8_next_char (iter))
|
||||
{
|
||||
if (!g_unichar_isspace (g_utf8_get_char (iter)))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
istring_contains_space (IString *str)
|
||||
{
|
||||
const char *iter;
|
||||
|
||||
for (iter = istring_str (str); *iter; iter = g_utf8_next_char (iter))
|
||||
{
|
||||
if (g_unichar_isspace (g_utf8_get_char (iter)))
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static inline void
|
||||
istring_prepend (IString *str,
|
||||
IString *other)
|
||||
{
|
||||
if G_LIKELY (str->n_bytes + other->n_bytes < sizeof str->u.buf - 1)
|
||||
{
|
||||
memmove (str->u.buf + other->n_bytes, str->u.buf, str->n_bytes);
|
||||
memcpy (str->u.buf, other->u.buf, other->n_bytes);
|
||||
str->n_bytes += other->n_bytes;
|
||||
str->n_chars += other->n_chars;
|
||||
str->u.buf[str->n_bytes] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
gchar *old = NULL;
|
||||
|
||||
if (!istring_is_inline (str))
|
||||
old = str->u.str;
|
||||
|
||||
str->u.str = g_strconcat (istring_str (str), istring_str (other), NULL);
|
||||
str->n_bytes += other->n_bytes;
|
||||
str->n_chars += other->n_chars;
|
||||
|
||||
g_free (old);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
istring_append (IString *str,
|
||||
IString *other)
|
||||
{
|
||||
const gchar *text = istring_str (other);
|
||||
guint n_bytes = other->n_bytes;
|
||||
guint n_chars = other->n_chars;
|
||||
|
||||
if G_LIKELY (istring_is_inline (str))
|
||||
{
|
||||
if G_LIKELY (str->n_bytes + n_bytes <= (sizeof str->u.buf - 1))
|
||||
memcpy (str->u.buf + str->n_bytes, text, n_bytes);
|
||||
else
|
||||
str->u.str = g_strconcat (str->u.buf, text, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
str->u.str = g_realloc (str->u.str, str->n_bytes + n_bytes + 1);
|
||||
memcpy (str->u.str + str->n_bytes, text, n_bytes);
|
||||
}
|
||||
|
||||
str->n_bytes += n_bytes;
|
||||
str->n_chars += n_chars;
|
||||
|
||||
istring_str (str)[str->n_bytes] = 0;
|
||||
}
|
||||
|
||||
#endif /* __GTK_ISTRING_PRIVATE_H__ */
|
1062
gtk/gtktexthistory.c
Normal file
1062
gtk/gtktexthistory.c
Normal file
File diff suppressed because it is too large
Load Diff
84
gtk/gtktexthistoryprivate.h
Normal file
84
gtk/gtktexthistoryprivate.h
Normal file
@ -0,0 +1,84 @@
|
||||
/* Copyright (C) 2019 Red Hat, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __GTK_TEXT_HISTORY_PRIVATE_H__
|
||||
#define __GTK_TEXT_HISTORY_PRIVATE_H__
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GTK_TYPE_TEXT_HISTORY (gtk_text_history_get_type())
|
||||
|
||||
typedef struct _GtkTextHistoryFuncs GtkTextHistoryFuncs;
|
||||
|
||||
G_DECLARE_FINAL_TYPE (GtkTextHistory, gtk_text_history, GTK, TEXT_HISTORY, GObject)
|
||||
|
||||
struct _GtkTextHistoryFuncs
|
||||
{
|
||||
void (*change_state) (gpointer funcs_data,
|
||||
gboolean is_modified,
|
||||
gboolean can_undo,
|
||||
gboolean can_redo);
|
||||
void (*insert) (gpointer funcs_data,
|
||||
guint begin,
|
||||
guint end,
|
||||
const char *text,
|
||||
guint len);
|
||||
void (*delete) (gpointer funcs_data,
|
||||
guint begin,
|
||||
guint end,
|
||||
const char *expected_text,
|
||||
guint len);
|
||||
void (*select) (gpointer funcs_data,
|
||||
int selection_insert,
|
||||
int selection_bound);
|
||||
};
|
||||
|
||||
GtkTextHistory *gtk_text_history_new (const GtkTextHistoryFuncs *funcs,
|
||||
gpointer funcs_data);
|
||||
void gtk_text_history_begin_user_action (GtkTextHistory *self);
|
||||
void gtk_text_history_end_user_action (GtkTextHistory *self);
|
||||
void gtk_text_history_begin_irreversible_action (GtkTextHistory *self);
|
||||
void gtk_text_history_end_irreversible_action (GtkTextHistory *self);
|
||||
gboolean gtk_text_history_get_can_undo (GtkTextHistory *self);
|
||||
gboolean gtk_text_history_get_can_redo (GtkTextHistory *self);
|
||||
void gtk_text_history_undo (GtkTextHistory *self);
|
||||
void gtk_text_history_redo (GtkTextHistory *self);
|
||||
guint gtk_text_history_get_max_undo_levels (GtkTextHistory *self);
|
||||
void gtk_text_history_set_max_undo_levels (GtkTextHistory *self,
|
||||
guint max_undo_levels);
|
||||
void gtk_text_history_modified_changed (GtkTextHistory *self,
|
||||
gboolean modified);
|
||||
void gtk_text_history_selection_changed (GtkTextHistory *self,
|
||||
int selection_insert,
|
||||
int selection_bound);
|
||||
void gtk_text_history_text_inserted (GtkTextHistory *self,
|
||||
guint position,
|
||||
const char *text,
|
||||
int len);
|
||||
void gtk_text_history_text_deleted (GtkTextHistory *self,
|
||||
guint begin,
|
||||
guint end,
|
||||
const char *text,
|
||||
int len);
|
||||
gboolean gtk_text_history_get_enabled (GtkTextHistory *self);
|
||||
void gtk_text_history_set_enabled (GtkTextHistory *self,
|
||||
gboolean enabled);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GTK_TEXT_HISTORY_PRIVATE_H__ */
|
@ -145,6 +145,7 @@ gtk_private_sources = files([
|
||||
'gtkstylecascade.c',
|
||||
'gtkstyleproperty.c',
|
||||
'gtktextbtree.c',
|
||||
'gtktexthistory.c',
|
||||
'gtktextviewchild.c',
|
||||
'gtktrashmonitor.c',
|
||||
'gtktreedatalist.c',
|
||||
|
@ -126,6 +126,7 @@ gtk_tests = [
|
||||
['testblur'],
|
||||
['testtexture'],
|
||||
['testwindowdrag'],
|
||||
['testtexthistory', ['../gtk/gtktexthistory.c']],
|
||||
]
|
||||
|
||||
if os_unix
|
||||
|
600
tests/testtexthistory.c
Normal file
600
tests/testtexthistory.c
Normal file
@ -0,0 +1,600 @@
|
||||
#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 gchar *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,
|
||||
gint selection_insert,
|
||||
gint 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 ();
|
||||
}
|
Loading…
Reference in New Issue
Block a user