ktsu.UndoRedo.Core 1.0.3-pre.1

Prefix Reserved
This is a prerelease version of ktsu.UndoRedo.Core.
dotnet add package ktsu.UndoRedo.Core --version 1.0.3-pre.1
                    
NuGet\Install-Package ktsu.UndoRedo.Core -Version 1.0.3-pre.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ktsu.UndoRedo.Core" Version="1.0.3-pre.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ktsu.UndoRedo.Core" Version="1.0.3-pre.1" />
                    
Directory.Packages.props
<PackageReference Include="ktsu.UndoRedo.Core" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ktsu.UndoRedo.Core --version 1.0.3-pre.1
                    
#r "nuget: ktsu.UndoRedo.Core, 1.0.3-pre.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ktsu.UndoRedo.Core@1.0.3-pre.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ktsu.UndoRedo.Core&version=1.0.3-pre.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=ktsu.UndoRedo.Core&version=1.0.3-pre.1&prerelease
                    
Install as a Cake Tool

ktsu.UndoRedo

A comprehensive .NET library for implementing undo/redo functionality with advanced features including save boundaries, change visualization, and external navigation integration.

Overview

ktsu.UndoRedo provides a robust and flexible undo/redo stack implementation that goes beyond basic command pattern implementations. It's designed for applications that need sophisticated change tracking, visual feedback, and integration with navigation systems.

Features

  • Command Pattern Implementation: Clean, extensible command interface
  • Save Boundaries: Track which changes have been saved and identify unsaved work
  • Change Visualization: Rich metadata for displaying change history in UI
  • Navigation Integration: Automatically navigate to where changes were made during undo/redo
  • Command Merging: Intelligent merging of related commands (e.g., typing)
  • Composite Commands: Group multiple operations into atomic units
  • Events: Comprehensive event system for UI synchronization
  • Stack Management: Configurable stack size limits and automatic cleanup
  • Async Support: Full async/await support for navigation operations

Installation

Add the NuGet package:

dotnet add package ktsu.UndoRedo

Quick Start

Basic Usage

using ktsu.UndoRedo.Core;

// Create an undo/redo stack
var undoRedoStack = new UndoRedoStack();

// Create a simple command using delegates
var command = new DelegateCommand(
    description: "Set value to 42",
    executeAction: () => myObject.Value = 42,
    undoAction: () => myObject.Value = oldValue,
    changeType: ChangeType.Modify,
    affectedItems: new[] { "myObject.Value" }
);

// Execute the command
undoRedoStack.Execute(command);

// Undo and redo
if (undoRedoStack.CanUndo)
    undoRedoStack.Undo();

if (undoRedoStack.CanRedo)
    undoRedoStack.Redo();

Save Boundaries

// Mark the current state as saved
undoRedoStack.MarkAsSaved("Auto-save checkpoint");

// Check if there are unsaved changes
if (undoRedoStack.HasUnsavedChanges)
{
    // Prompt user to save or undo to last save point
    var lastSave = undoRedoStack.SaveBoundaries.LastOrDefault();
    if (lastSave != null)
    {
        await undoRedoStack.UndoToSaveBoundaryAsync(lastSave);
    }
}
// Implement navigation provider
public class MyNavigationProvider : INavigationProvider
{
    public async Task<bool> NavigateToAsync(string context, CancellationToken cancellationToken = default)
    {
        // Navigate to the location where the change was made
        // context might be something like "file:line:column" or "elementId"
        return await NavigateToLocation(context);
    }

    public bool IsValidContext(string context) => !string.IsNullOrEmpty(context);
}

// Set up navigation
var navigationProvider = new MyNavigationProvider();
undoRedoStack.SetNavigationProvider(navigationProvider);

// Commands with navigation context will automatically navigate on undo/redo
var command = new DelegateCommand(
    "Edit text",
    executeAction,
    undoAction,
    navigationContext: "editor:45:12" // Line 45, column 12
);

Custom Commands

public class TextEditCommand : BaseCommand
{
    private readonly ITextEditor _editor;
    private readonly int _position;
    private readonly string _oldText;
    private readonly string _newText;

    public override string Description => $"Replace '{_oldText}' with '{_newText}'";

    public TextEditCommand(ITextEditor editor, int position, string oldText, string newText)
        : base(ChangeType.Modify, new[] { $"text:{position}" }, $"editor:{GetLineColumn(position)}")
    {
        _editor = editor;
        _position = position;
        _oldText = oldText;
        _newText = newText;
    }

    public override void Execute()
    {
        _editor.ReplaceText(_position, _oldText.Length, _newText);
    }

    public override void Undo()
    {
        _editor.ReplaceText(_position, _newText.Length, _oldText);
    }

    public override bool CanMergeWith(ICommand other)
    {
        // Allow merging consecutive character insertions
        return other is TextEditCommand textCmd &&
               textCmd._position == _position + _newText.Length &&
               _newText.Length == 1 && textCmd._newText.Length == 1;
    }

    public override ICommand MergeWith(ICommand other)
    {
        var textCmd = (TextEditCommand)other;
        return new TextEditCommand(_editor, _position, _oldText, _newText + textCmd._newText);
    }
}

Composite Commands

// Group multiple operations into a single undoable action
var commands = new[]
{
    new DelegateCommand("Move item", () => item.Position = newPos, () => item.Position = oldPos),
    new DelegateCommand("Resize item", () => item.Size = newSize, () => item.Size = oldSize),
    new DelegateCommand("Change color", () => item.Color = newColor, () => item.Color = oldColor)
};

var composite = new CompositeCommand("Transform item", commands, "item:" + item.Id);
undoRedoStack.Execute(composite);

Change Visualization

// Get visualization data for UI display
var visualizations = undoRedoStack.GetChangeVisualizations(maxItems: 20);

foreach (var viz in visualizations)
{
    Console.WriteLine($"{(viz.IsExecuted ? "✓" : "○")} {viz.Command.Description}");

    if (viz.HasSaveBoundary)
        Console.WriteLine("  📁 Save point");

    Console.WriteLine($"  📊 {viz.Command.Metadata.ChangeType} affecting {viz.Command.Metadata.AffectedItems.Count} items");
    Console.WriteLine($"  🕒 {viz.Command.Metadata.Timestamp:HH:mm:ss}");
}

Events

// Subscribe to events for UI updates
undoRedoStack.CommandExecuted += (sender, e) =>
{
    UpdateUI();
    LogAction($"Executed: {e.Command.Description}");
};

undoRedoStack.CommandUndone += (sender, e) =>
{
    UpdateUI();
    LogAction($"Undone: {e.Command.Description}");
};

undoRedoStack.SaveBoundaryCreated += (sender, e) =>
{
    UpdateSaveIndicator(saved: true);
};

Advanced Configuration

// Configure stack behavior
var undoRedoStack = new UndoRedoStack(
    maxStackSize: 500,        // Limit to 500 commands
    autoMergeCommands: true   // Automatically merge compatible commands
);

// Set up navigation with custom behavior
undoRedoStack.SetNavigationProvider(navigationProvider);

// Use async operations for better responsiveness
await undoRedoStack.UndoAsync(navigateToChange: true);
await undoRedoStack.RedoAsync(navigateToChange: true);

Integration Examples

Text Editor Integration

public class TextEditorUndoRedo
{
    private readonly UndoRedoStack _undoRedo = new();
    private readonly ITextEditor _editor;

    public void OnTextChanged(TextChangeEventArgs e)
    {
        var command = new TextEditCommand(_editor, e.Position, e.OldText, e.NewText);
        _undoRedo.Execute(command);
    }

    public void OnSave()
    {
        _undoRedo.MarkAsSaved($"Saved {DateTime.Now:HH:mm:ss}");
    }
}

WPF Integration

public class DocumentViewModel : INotifyPropertyChanged
{
    private readonly UndoRedoStack _undoRedo = new();

    public ICommand UndoCommand => new RelayCommand(
        execute: () => _undoRedo.Undo(),
        canExecute: () => _undoRedo.CanUndo
    );

    public ICommand RedoCommand => new RelayCommand(
        execute: () => _undoRedo.Redo(),
        canExecute: () => _undoRedo.CanRedo
    );

    public bool HasUnsavedChanges => _undoRedo.HasUnsavedChanges;
}

API Reference

Core Classes

  • UndoRedoStack: Main class managing the undo/redo operations
  • ICommand: Interface for implementing undoable commands
  • BaseCommand: Base class with common command functionality
  • DelegateCommand: Simple command using delegates
  • CompositeCommand: Command containing multiple sub-commands
  • SaveBoundary: Represents a save point in the stack

Key Interfaces

  • INavigationProvider: Interface for implementing navigation to changes
  • ChangeMetadata: Rich metadata about changes for visualization
  • ChangeVisualization: Data structure for displaying change history

License

MIT License. Copyright (c) ktsu.dev

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.3-pre.1 118 7/9/2025
1.0.2 237 6/13/2025

## v1.0.3-pre.1 (prerelease)

Changes since v1.0.2:

- Bump the microsoft group with 2 updates ([@dependabot[bot]](https://github.com/dependabot[bot]))
## v1.0.2 (patch)

Changes since v1.0.1:

- Update package references, improve project structure, and enhance documentation ([@matt-edmondson](https://github.com/matt-edmondson))
- Add script to automate winget manifest updates ([@matt-edmondson](https://github.com/matt-edmondson))
- Refactor JsonUndoRedoSerializer and UndoRedoService error handling ([@matt-edmondson](https://github.com/matt-edmondson))
- Enhance JsonUndoRedoSerializer and UndoRedoService functionality ([@matt-edmondson](https://github.com/matt-edmondson))
- Fix issues in UndoRedo functionality and tests ([@matt-edmondson](https://github.com/matt-edmondson))
## v1.0.1 (patch)

Changes since v1.0.0:

- Add serialization support for undo/redo stack state ([@matt-edmondson](https://github.com/matt-edmondson))
## v1.0.1-pre.1 (prerelease)

Incremental prerelease update.
## v1.0.0 (major)

- Initial commit for UndoRedo ([@matt-edmondson](https://github.com/matt-edmondson))
- Enhance testing documentation in derived-cursor-rules.mdc with detailed commands for restoring dependencies, building, and running tests. Add new testing discussion document outlining test execution steps and troubleshooting for the UndoRedo project. ([@matt-edmondson](https://github.com/matt-edmondson))
- Add project configuration files and ignore rules; remove sample applications ([@matt-edmondson](https://github.com/matt-edmondson))
- Add project configuration files: Directory.Packages.props for centralized package version management and global.json for SDK and MSBuild SDK versions. Update UndoRedo.Core and UndoRedo.Test project files to remove specific versioning for dependencies and SDKs. ([@matt-edmondson](https://github.com/matt-edmondson))
- Add .cursorignore for SpecStory backup files, enhance README with detailed library overview, features, and usage examples; add architecture and best practices documentation; implement core undo/redo functionality with command management and dependency injection support. ([@matt-edmondson](https://github.com/matt-edmondson))