/*
* Copyright (C) 2012-2025 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(gunichar);
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, i.e. 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(gsize 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);
}