ktsu.UndoRedo.Core
1.0.3-pre.1
Prefix Reserved
dotnet add package ktsu.UndoRedo.Core --version 1.0.3-pre.1
NuGet\Install-Package ktsu.UndoRedo.Core -Version 1.0.3-pre.1
<PackageReference Include="ktsu.UndoRedo.Core" Version="1.0.3-pre.1" />
<PackageVersion Include="ktsu.UndoRedo.Core" Version="1.0.3-pre.1" />
<PackageReference Include="ktsu.UndoRedo.Core" />
paket add ktsu.UndoRedo.Core --version 1.0.3-pre.1
#r "nuget: ktsu.UndoRedo.Core, 1.0.3-pre.1"
#:package ktsu.UndoRedo.Core@1.0.3-pre.1
#addin nuget:?package=ktsu.UndoRedo.Core&version=1.0.3-pre.1&prerelease
#tool nuget:?package=ktsu.UndoRedo.Core&version=1.0.3-pre.1&prerelease
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);
}
}
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);
};
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 | 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. |
-
net9.0
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))