ktsu.UndoRedo.Core
1.0.17
Prefix Reserved
dotnet add package ktsu.UndoRedo.Core --version 1.0.17
NuGet\Install-Package ktsu.UndoRedo.Core -Version 1.0.17
<PackageReference Include="ktsu.UndoRedo.Core" Version="1.0.17" />
<PackageVersion Include="ktsu.UndoRedo.Core" Version="1.0.17" />
<PackageReference Include="ktsu.UndoRedo.Core" />
paket add ktsu.UndoRedo.Core --version 1.0.17
#r "nuget: ktsu.UndoRedo.Core, 1.0.17"
#:package ktsu.UndoRedo.Core@1.0.17
#addin nuget:?package=ktsu.UndoRedo.Core&version=1.0.17
#tool nuget:?package=ktsu.UndoRedo.Core&version=1.0.17
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;
// 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);
}
}
Navigation Integration
// 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);
};
Serialization and Persistence
// Configure JSON serializer for persistence
var serializer = new JsonUndoRedoSerializer();
undoRedoStack.SetSerializer(serializer);
// Save stack state to byte array
byte[] data = await undoRedoStack.SaveStateAsync();
await File.WriteAllBytesAsync("undo_stack.json", data);
// Load stack state from byte array
byte[] loadedData = await File.ReadAllBytesAsync("undo_stack.json");
bool success = await undoRedoStack.LoadStateAsync(loadedData);
// For commands that need custom serialization, implement ISerializableCommand
public class MyCommand : BaseCommand, ISerializableCommand
{
public string SerializeData() => JsonSerializer.Serialize(myData);
public void DeserializeData(string data) => myData = JsonSerializer.Deserialize<MyData>(data);
}
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 operationsICommand: Interface for implementing undoable commandsBaseCommand: Base class with common command functionalityDelegateCommand: Simple command using delegatesCompositeCommand: Command containing multiple sub-commandsSaveBoundary: Represents a save point in the stack
Key Interfaces
INavigationProvider: Interface for implementing navigation to changesChangeMetadata: Rich metadata about changes for visualizationChangeVisualization: Data structure for displaying change history
License
MIT License. Copyright (c) ktsu.dev
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. 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 is compatible. 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. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.Extensions.DependencyInjection (>= 10.0.11)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.11)
- System.Memory (>= 4.6.3)
- System.Text.Json (>= 10.0.11)
- System.Threading.Tasks.Extensions (>= 4.6.3)
-
.NETStandard 2.1
- Microsoft.Extensions.DependencyInjection (>= 10.0.11)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.11)
- System.Text.Json (>= 10.0.11)
-
net10.0
- Microsoft.Extensions.DependencyInjection (>= 10.0.11)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.11)
-
net6.0
- Microsoft.Extensions.DependencyInjection (>= 10.0.11)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.11)
-
net7.0
- Microsoft.Extensions.DependencyInjection (>= 10.0.11)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.11)
-
net8.0
- Microsoft.Extensions.DependencyInjection (>= 10.0.11)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.11)
-
net9.0
- Microsoft.Extensions.DependencyInjection (>= 10.0.11)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.11)
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.17 | 72 | 8/21/2026 |
| 1.0.16 | 77 | 8/20/2026 |
| 1.0.15 | 82 | 8/19/2026 |
| 1.0.14 | 99 | 8/17/2026 |
| 1.0.13 | 255 | 7/2/2026 |
| 1.0.12 | 112 | 7/1/2026 |
| 1.0.11 | 118 | 6/30/2026 |
| 1.0.10 | 117 | 6/28/2026 |
| 1.0.10-pre.1 | 99 | 2/17/2026 |
| 1.0.9 | 157 | 2/16/2026 |
| 1.0.9-pre.1 | 82 | 2/16/2026 |
| 1.0.8 | 127 | 2/14/2026 |
| 1.0.7 | 122 | 2/14/2026 |
| 1.0.7-pre.5 | 85 | 2/6/2026 |
| 1.0.7-pre.4 | 92 | 2/5/2026 |
| 1.0.7-pre.3 | 88 | 2/3/2026 |
| 1.0.7-pre.2 | 107 | 2/1/2026 |
| 1.0.7-pre.1 | 89 | 1/31/2026 |
| 1.0.6 | 143 | 1/31/2026 |
| 1.0.5 | 126 | 1/30/2026 |
## v1.0.17 (patch)
Changes since v1.0.16:
- Bump the ktsu group with 9 updates ([@dependabot[bot]](https://github.com/dependabot[bot]))