diff options
Diffstat (limited to 'src/undo.h')
-rw-r--r-- | src/undo.h | 399 |
1 files changed, 203 insertions, 196 deletions
@@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2017 Robin Haberkorn + * Copyright (C) 2012-2021 Robin Haberkorn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -14,227 +14,234 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -#ifndef __UNDO_H -#define __UNDO_H - -#include <string.h> - -#include <bsd/sys/queue.h> +#pragma once #include <glib.h> -#include <glib/gprintf.h> -#include "memory.h" +#include "sciteco.h" -#ifdef DEBUG -#include "parser.h" -#endif - -namespace SciTECO { +extern gboolean teco_undo_enabled; /** - * Undo tokens are generated to revert any - * changes to the editor state, ie. they - * define an action to take upon rubout. + * A callback to be invoked when an undo token gets executed or cleaned up. + * + * @note Unless you want to cast user_data in every callback implementation, + * you may want to cast your callback type instead to teco_undo_action_t. + * Casting to functions of different signature is theoretically undefined behavior, + * but works on all major platforms including Emscripten, as long as they differ only + * in pointer types. * - * Undo tokens are organized into an undo - * stack. + * @param user_data + * The data allocated by teco_undo_push_size() (usually a context structure). + * You are supposed to free any external resources (heap pointers etc.) referenced + * from it till the end of the callback. + * @param run + * Whether the operation should actually be performed instead of merely freeing + * the associated memory. */ -class UndoToken : public Object { -public: - SLIST_ENTRY(UndoToken) tokens; - - virtual ~UndoToken() {} - - virtual void run(void) = 0; -}; - -template <typename Type> -class UndoTokenVariable : public UndoToken { - Type *ptr; - Type value; - -public: - UndoTokenVariable(Type &variable, Type _value) - : ptr(&variable), value(_value) {} - - void - run(void) - { -#ifdef DEBUG - if ((State **)ptr == &States::current) - g_printf("undo state -> %p\n", (void *)value); -#endif - *ptr = value; - } -}; +typedef void (*teco_undo_action_t)(gpointer user_data, gboolean run); -class UndoTokenString : public UndoToken { - gchar **ptr; - gchar *str; +gpointer teco_undo_push_size(teco_undo_action_t action_cb, gsize size) + G_GNUC_ALLOC_SIZE(2); -public: - UndoTokenString(gchar *&variable, gchar *_str) - : ptr(&variable) - { - str = _str ? g_strdup(_str) : NULL; - } +#define teco_undo_push(NAME) \ + ((NAME##_t *)teco_undo_push_size((teco_undo_action_t)NAME##_action, \ + sizeof(NAME##_t))) - ~UndoTokenString() - { - g_free(str); +/** + * @defgroup undo_objects Undo objects + * + * @note + * The general meta programming approach here is similar to C++ explicit template + * instantiation. + * A macro is expanded for every object type into some compilation unit and a declaration + * into the corresponding header. + * The object's type is mangled into the generated "push"-function's name. + * In case of scalars, C11 Generics and some macro magic is then used to hide the + * type names and for "reference" style passing. + * + * Explicit instantiation could be theoretically avoided using GCC compound expressions + * and nested functions. However, GCC will inevitably generate trampolines which + * are unportable and induce a runtime penalty. + * Furthermore, nested functions are not supported by Clang, where the Blocks extension + * would have to be used instead. + * Another alternative for implicit instantiation would be preprocessing of all source + * files with some custom M4 macros. + */ +/* + * FIXME: Due to the requirements on the variable, we could be tempted to inline + * references to it directly into the action()-function, saving the `ptr` + * in the undo token. This is however often practically not possible. + * We could however add a variant for true global variables. + * + * FIXME: Sometimes, the push-function is used only in a single compilation unit, + * so it should be declared `static` or `static inline`. + * Is it worth complicating our APIs in order to support that? + * + * FIXME: Perhaps better split this into TECO_DEFINE_UNDO_OBJECT() and TECO_DEFINE_UNDO_OBJECT_OWN() + */ +#define __TECO_DEFINE_UNDO_OBJECT(NAME, TYPE, COPY, DELETE, DELETE_IF_DISABLED, DELETE_ON_RUN) \ + typedef struct { \ + TYPE *ptr; \ + TYPE value; \ + } teco_undo_object_##NAME##_t; \ + \ + static void \ + teco_undo_object_##NAME##_action(teco_undo_object_##NAME##_t *ctx, gboolean run) \ + { \ + if (run) { \ + DELETE_ON_RUN(*ctx->ptr); \ + *ctx->ptr = ctx->value; \ + } else { \ + DELETE(ctx->value); \ + } \ + } \ + \ + /** @ingroup undo_objects */ \ + TYPE * \ + teco_undo_object_##NAME##_push(TYPE *ptr) \ + { \ + teco_undo_object_##NAME##_t *ctx = teco_undo_push(teco_undo_object_##NAME); \ + if (ctx) { \ + ctx->ptr = ptr; \ + ctx->value = COPY(*ptr); \ + } else { \ + DELETE_IF_DISABLED(*ptr); \ + } \ + return ptr; \ } - void - run(void) - { - g_free(*ptr); - *ptr = str; - str = NULL; - } -}; +/** + * Defines an undo token push function that when executed restores + * the value/state of a variable of TYPE to the value it had when this + * was called. + * + * This can be used to undo changes to arbitrary variables, either + * requiring explicit memory handling or to scalars. + * + * The lifetime of the variable must be global - a pointer to it must be valid + * until the undo token could be executed. + * This will usually exclude stack-allocated variables or objects. + * + * @param NAME C identifier used for name mangling. + * @param TYPE Type of variable to restore. + * @param COPY A global function/expression to execute in order to copy VAR. + * If left empty, this is an identity operation and ownership + * of the variable is passed to the undo token. + * @param DELETE A global function/expression to execute in order to destruct + * objects of TYPE. Leave empty if destruction is not necessary. + * + * @ingroup undo_objects + */ +#define TECO_DEFINE_UNDO_OBJECT(NAME, TYPE, COPY, DELETE) \ + __TECO_DEFINE_UNDO_OBJECT(NAME, TYPE, COPY, DELETE, /* don't delete if disabled */, DELETE) +/** + * @fixme _OWN variants will invalidate the variable pointer, so perhaps + * it will be clearer to have _SET variants instead. + * + * @ingroup undo_objects + */ +#define TECO_DEFINE_UNDO_OBJECT_OWN(NAME, TYPE, DELETE) \ + __TECO_DEFINE_UNDO_OBJECT(NAME, TYPE, /* pass ownership */, DELETE, DELETE, /* don't delete if run */) -template <class Type> -class UndoTokenObject : public UndoToken { - Type **ptr; - Type *obj; +/** @ingroup undo_objects */ +#define TECO_DECLARE_UNDO_OBJECT(NAME, TYPE) \ + TYPE *teco_undo_object_##NAME##_push(TYPE *ptr) -public: - UndoTokenObject(Type *&variable, Type *_obj) - : ptr(&variable), obj(_obj) {} +/** @ingroup undo_objects */ +#define TECO_DEFINE_UNDO_SCALAR(TYPE) \ + TECO_DEFINE_UNDO_OBJECT_OWN(TYPE, TYPE, /* don't delete */) - ~UndoTokenObject() - { - delete obj; - } +/** @ingroup undo_objects */ +#define TECO_DECLARE_UNDO_SCALAR(TYPE) \ + TECO_DECLARE_UNDO_OBJECT(TYPE, TYPE) - void - run(void) - { - delete *ptr; - *ptr = obj; - obj = NULL; - } -}; - -extern class UndoStack : public Object { - /** - * Stack of UndoToken lists. - * - * Each stack element represents - * a command line character (the UndoTokens - * generated by that character), so it's OK - * to use a data structure that may need - * reallocation but is space efficient. - * This data structure allows us to omit the - * command line program counter from the UndoTokens - * but wastes a few bytes for input characters - * that produce no UndoToken (e.g. NOPs like space). - */ - GPtrArray *heads; - - void push(UndoToken *token); - -public: - bool enabled; - - UndoStack(bool _enabled = false) - : heads(g_ptr_array_new()), enabled(_enabled) {} - ~UndoStack() - { - clear(); - g_ptr_array_free(heads, TRUE); - } +/* + * FIXME: We had to add -Wno-unused-value to surpress warnings. + * Perhaps it's clearer to sacrifice the lvalue feature. + * + * TODO: Check whether generating an additional check on teco_undo_enabled here + * significantly improves batch-mode performance. + */ +TECO_DECLARE_UNDO_SCALAR(gchar); +#define teco_undo_gchar(VAR) (*teco_undo_object_gchar_push(&(VAR))) - /** - * Allocate and push undo token. - * - * This does nothing if undo is disabled and should - * not be used when ownership of some data is to be - * passed to the undo token. - */ - template <class TokenType, typename... Params> - inline void - push(Params && ... params) - { - if (enabled) - push(new TokenType(params...)); - } +TECO_DECLARE_UNDO_SCALAR(gint); +#define teco_undo_gint(VAR) (*teco_undo_object_gint_push(&(VAR))) - /** - * Allocate and push undo token, passing ownership. - * - * This creates and deletes the undo token cheaply - * if undo is disabled, so that data whose ownership - * is passed to the undo token is correctly reclaimed. - * - * @bug We must know which version of push to call - * depending on the token type. This could be hidden - * if UndoTokens had static push methods that take care - * of reclaiming memory. - */ - template <class TokenType, typename... Params> - inline void - push_own(Params && ... params) - { - if (enabled) { - push(new TokenType(params...)); - } else { - /* ensures that all memory is reclaimed */ - TokenType dummy(params...); - } - } +TECO_DECLARE_UNDO_SCALAR(guint); +#define teco_undo_guint(VAR) (*teco_undo_object_guint_push(&(VAR))) - template <typename Type> - inline Type & - push_var(Type &variable, Type value) - { - push<UndoTokenVariable<Type>>(variable, value); - return variable; - } +TECO_DECLARE_UNDO_SCALAR(gsize); +#define teco_undo_gsize(VAR) (*teco_undo_object_gsize_push(&(VAR))) - template <typename Type> - inline Type & - push_var(Type &variable) - { - return push_var<Type>(variable, variable); - } +TECO_DECLARE_UNDO_SCALAR(teco_int_t); +#define teco_undo_int(VAR) (*teco_undo_object_teco_int_t_push(&(VAR))) - inline gchar *& - push_str(gchar *&variable, gchar *str) - { - push<UndoTokenString>(variable, str); - return variable; - } - inline gchar *& - push_str(gchar *&variable) - { - return push_str(variable, variable); - } +TECO_DECLARE_UNDO_SCALAR(gboolean); +#define teco_undo_gboolean(VAR) (*teco_undo_object_gboolean_push(&(VAR))) - template <class Type> - inline Type *& - push_obj(Type *&variable, Type *obj) - { - /* pass ownership of original object */ - push_own<UndoTokenObject<Type>>(variable, obj); - return variable; - } +TECO_DECLARE_UNDO_SCALAR(gconstpointer); +#define teco_undo_ptr(VAR) \ + (*(typeof(VAR) *)teco_undo_object_gconstpointer_push((gconstpointer *)&(VAR))) - template <class Type> - inline Type *& - push_obj(Type *&variable) - { - return push_obj<Type>(variable, variable); - } +#define __TECO_GEN_STRUCT(ID, X) X arg_##ID; +//#define __TECO_GEN_ARG(ID, X) X arg_##ID, +//#define __TECO_GEN_ARG_LAST(ID, X) X arg_##ID +#define __TECO_GEN_CALL(ID, X) ctx->arg_##ID, +#define __TECO_GEN_CALL_LAST(ID, X) ctx->arg_##ID +#define __TECO_GEN_INIT(ID, X) ctx->arg_##ID = arg_##ID; - void pop(gint pc); +/** + * @defgroup undo_calls Function calls on rubout. + * @{ + */ - void clear(void); -} undo; +/** + * Create an undo token that calls FNC with arbitrary scalar parameters + * (maximum 5, captured at the time of the call). + * It defines a function undo__FNC() for actually creating the closure. + * + * All arguments must be constants or expressions evaluating to scalars though, + * since no memory management (copying/freeing) is performed. + * + * Tipp: In order to save memory in the undo token structures, it is + * often trivial to define a static inline function that calls FNC and binds + * "constant" parameters. + * + * @param FNC Name of a global function or macro to execute. + * It must be a plain C identifier. + * @param ... The parameter types of FNC (signature). + * Only the types without any variable names must be specified. + * + * @fixme Sometimes, the push-function is used only in a single compilation unit, + * so it should be declared `static` or `static inline`. + * Is it worth complicating our APIs in order to support that? + */ +#define TECO_DEFINE_UNDO_CALL(FNC, ...) \ + typedef struct { \ + TECO_FOR_EACH(__TECO_GEN_STRUCT, __TECO_GEN_STRUCT, ##__VA_ARGS__) \ + } teco_undo_call_##FNC##_t; \ + \ + static void \ + teco_undo_call_##FNC##_action(teco_undo_call_##FNC##_t *ctx, gboolean run) \ + { \ + if (run) \ + FNC(TECO_FOR_EACH(__TECO_GEN_CALL, __TECO_GEN_CALL_LAST, ##__VA_ARGS__)); \ + } \ + \ + /** @ingroup undo_calls */ \ + void \ + undo__##FNC(TECO_FOR_EACH(__TECO_GEN_ARG, __TECO_GEN_ARG_LAST, ##__VA_ARGS__)) \ + { \ + teco_undo_call_##FNC##_t *ctx = teco_undo_push(teco_undo_call_##FNC); \ + if (ctx) { \ + TECO_FOR_EACH(__TECO_GEN_INIT, __TECO_GEN_INIT, ##__VA_ARGS__) \ + } \ + } -} /* namespace SciTECO */ +/** @} */ -#endif +void teco_undo_pop(gint pc); +void teco_undo_clear(void); |