/* * Copyright (C) 2012-2024 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 * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "sciteco.h" #include "cmdline.h" #include "undo.h" //#define DEBUG TECO_DEFINE_UNDO_SCALAR(gchar); TECO_DEFINE_UNDO_SCALAR(gint); TECO_DEFINE_UNDO_SCALAR(guint); TECO_DEFINE_UNDO_SCALAR(gsize); TECO_DEFINE_UNDO_SCALAR(teco_int_t); TECO_DEFINE_UNDO_SCALAR(gboolean); TECO_DEFINE_UNDO_SCALAR(gconstpointer); /** * An undo token. * * Undo tokens are generated to revert any * changes to the editor state, ie. they * define an action to take upon rubout. * * Undo tokens are organized into an undo stack. */ typedef struct teco_undo_token_t { struct teco_undo_token_t *next; teco_undo_action_t action_cb; guint8 user_data[]; } teco_undo_token_t; /** * Stack of teco_undo_token_t lists. * * Each stack element represents * a command line character (the undo tokens * 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 teco_undo_token_t * but wastes a few bytes for input characters * that produce no undo tokens (e.g. NOPs like space). */ static GPtrArray *teco_undo_heads; gboolean teco_undo_enabled = FALSE; static void __attribute__((constructor)) teco_undo_init(void) { teco_undo_heads = g_ptr_array_new(); } /** * 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. */ gpointer teco_undo_push_size(teco_undo_action_t action_cb, gsize size) { if (!teco_undo_enabled) return NULL; teco_undo_token_t *token = g_malloc(sizeof(teco_undo_token_t) + size); token->action_cb = action_cb; #ifdef DEBUG g_printf("UNDO PUSH %p\n", token); #endif /* * There can very well be 0 undo tokens * per input character (e.g. NOPs like space). */ while (teco_undo_heads->len <= teco_cmdline.pc) g_ptr_array_add(teco_undo_heads, NULL); g_assert(teco_undo_heads->len == teco_cmdline.pc+1); token->next = g_ptr_array_index(teco_undo_heads, teco_undo_heads->len-1); g_ptr_array_index(teco_undo_heads, teco_undo_heads->len-1) = token; return token->user_data; } void teco_undo_pop(gint pc) { while ((gint)teco_undo_heads->len > pc) { teco_undo_token_t *top = g_ptr_array_remove_index(teco_undo_heads, teco_undo_heads->len-1); while (top) { teco_undo_token_t *next = top->next; #ifdef DEBUG g_printf("UNDO POP %p\n", top); fflush(stdout); #endif top->action_cb(top->user_data, TRUE); g_free(top); top = next; } } } void teco_undo_clear(void) { while (teco_undo_heads->len) { teco_undo_token_t *top = g_ptr_array_remove_index(teco_undo_heads, teco_undo_heads->len-1); while (top) { teco_undo_token_t *next = top->next; top->action_cb(top->user_data, FALSE); g_free(top); top = next; } } } /* * NOTE: This destructor should always be run, even with NDEBUG, * as there are undo tokens that release more than memory (e.g. files). */ static void __attribute__((destructor)) teco_undo_cleanup(void) { teco_undo_clear(); g_ptr_array_free(teco_undo_heads, TRUE); }