Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -450,5 +450,4 @@ var cachedRemoteProxy = Proxy<int, string>.Create(id => remoteProxy.Execute(id))
| -------------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Creational** | [Factory](docs/patterns/creational/factory/factory.md) ✓ • [Composer](docs/patterns/creational/builder/composer.md) ✓ • [ChainBuilder](docs/patterns/creational/builder/chainbuilder.md) ✓ • [BranchBuilder](docs/patterns/creational/builder/chainbuilder.md) ✓ • [MutableBuilder](docs/patterns/creational/builder/mutablebuilder.md) ✓ • [Prototype](docs/patterns/creational/prototype/prototype.md) ✓ • [Singleton](docs/patterns/creational/singleton/singleton.md) ✓ |
| **Structural** | [Adapter](docs/patterns/structural/adapter/fluent-adapter.md) ✓ • [Bridge](docs/patterns/structural/bridge/bridge.md) ✓ • [Composite](docs/patterns/structural/composite/composite.md) ✓ • [Decorator](docs/patterns/structural/decorator/decorator.md) ✓ • [Facade](docs/patterns/structural/facade/facade.md) ✓ • [Flyweight](docs/patterns/structural/flyweight/index.md) ✓ • [Proxy](docs/patterns/structural/proxy/index.md) ✓ |
| **Behavioral** | [Strategy](docs/patterns/behavioral/strategy/strategy.md) ✓ • [TryStrategy](docs/patterns/behavioral/strategy/trystrategy.md) ✓ • [ActionStrategy](docs/patterns/behavioral/strategy/actionstrategy.md) ✓ • [ActionChain](docs/patterns/behavioral/chain/actionchain.md) ✓ • [ResultChain](docs/patterns/behavioral/chain/resultchain.md) ✓ • [ReplayableSequence](docs/patterns/behavioral/iterator/replayablesequence.md) ✓ • [WindowSequence](docs/patterns/behavioral/iterator/windowsequence.md) ✓ • [Command](docs/patterns/behavioral/command/command.md) ✓ • [Mediator](docs/patterns/behavioral/mediator/mediator.md) ✓ • Memento (planned) • Observer (planned) • State (planned) • Template Method (planned) • Visitor (planned) |

| **Behavioral** | [Strategy](docs/patterns/behavioral/strategy/strategy.md) ✓ • [TryStrategy](docs/patterns/behavioral/strategy/trystrategy.md) ✓ • [ActionStrategy](docs/patterns/behavioral/strategy/actionstrategy.md) ✓ • [ActionChain](docs/patterns/behavioral/chain/actionchain.md) ✓ • [ResultChain](docs/patterns/behavioral/chain/resultchain.md) ✓ • [ReplayableSequence](docs/patterns/behavioral/iterator/replayablesequence.md) ✓ • [WindowSequence](docs/patterns/behavioral/iterator/windowsequence.md) ✓ • [Command](docs/patterns/behavioral/command/command.md) ✓ • [Mediator](docs/patterns/behavioral/mediator/mediator.md) ✓ • [Memento](docs/patterns/behavioral/memento/memento.md) ✓ • Observer (planned) • State (planned) • Template Method (planned) • Visitor (planned) |
5 changes: 5 additions & 0 deletions docs/examples/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Welcome! This section collects small, focused demos that show **how to compose b
* **Strategy-based coercion** for turning "whatever came in" into the types you actually want.
* **Ultra-minimal HTTP routing** to illustrate middleware vs. routes vs. negotiation.
* **Flyweight identity sharing** to eliminate duplicate immutable objects (glyphs, styles, tokens).
* **Snapshot history & undo/redo (Memento)** for document/editor style workflows.

## Demos in this section

Expand Down Expand Up @@ -39,6 +40,9 @@ Welcome! This section collects small, focused demos that show **how to compose b
* **Flyweight Glyph Cache & Style Sharing**
Shows a high-volume text/glyph layout where each distinct glyph (and style) is allocated **once** and reused. Demonstrates intrinsic vs extrinsic state separation, preload of hot keys (spaces), custom key comparers (case-insensitive styles), and thread-safe lazy creation—mirroring classic Flyweight scenarios (rendering, AST token metadata, icon caches).

* **Document Editing History (Memento)**
A simple document buffer with tagged snapshots, undo/redo, jump-to-version, and duplicate suppression illustrating the **Memento** pattern's practical shape in a UI/editor workflow.

## How to run

From the repo root:
Expand Down Expand Up @@ -76,6 +80,7 @@ dotnet test PatternKit.slnx -c Release
* **Payment Processor Decorators:** `PaymentProcessor*` + related tests.
* **Flyweight Glyph Cache:** `FlyweightDemo` (+ `FlyweightDemoTests`) — glyph width layout & style sharing.
* **Flyweight Structural Tests:** `Structural/Flyweight/FlyweightTests.cs` — preload, concurrency, comparer, guards.
* **Memento Document Demo:** `MementoDemo` — buffer edits with undo/redo and tags.
* **Tests:** `PatternKit.Examples.Tests/*` use TinyBDD scenarios that read like specs.

## Why these demos exist
Expand Down
150 changes: 150 additions & 0 deletions docs/examples/text-editor-memento.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Text Editor History (Memento Pattern)

A minimal in-memory text editor built on `Memento<TState>` demonstrating:

* Incremental snapshots (`Save` after each edit) with tags
* Undo / Redo navigation
* Branching edits (truncate forward history after divergent change)
* Duplicate suppression (skips snapshots when state unchanged)
* Capacity limiting (optional)
* Batched edits (group multiple operations into one snapshot)

---
## Why this demo

Typical editors need rapid, memory-conscious snapshot history with predictable semantics:

* Undo should step back to prior *logical* state
* Redo should vanish if the user edits after undoing (branching)
* Repeated caret moves / no-op edits shouldn’t bloat history
* Operations should be easy to test deterministically

`Memento<TState>` gives you a small, thread-safe engine to do exactly that.

---
## State model

The editor keeps an immutable `DocumentState` in each snapshot:

```csharp
public readonly struct DocumentState
{
public string Text { get; }
public int Caret { get; }
public int SelectionLength { get; }
public bool HasSelection => SelectionLength > 0;
}
```

Snapshots are created with a deep-ish clone (new struct copy + string reference). If you had mutable reference graphs, you’d supply a deep cloning function.

---
## Building the history engine

```csharp
var history = Memento<DocumentState>.Create()
.CloneWith(static (in DocumentState s) => new DocumentState(s.Text, s.Caret, s.SelectionLength))
.Equality(new StateEquality()) // skip consecutive duplicates
.Capacity(500) // keep last 500 edits
.Build();
```

Each editing operation mutates the live `_state` then calls `history.Save(in _state, tag)`.

---
## Editing operations covered

| Operation | Description | Snapshot Tag Example |
|----------|-------------|----------------------|
| `Insert(text)` | Inserts or replaces selection with `text`. | `insert:Hello` |
| `ReplaceSelection(text)` | Replaces current selection; falls back to `Insert` if no selection. | `replace:Hi` |
| `MoveCaret(pos)` | Moves caret (clears selection). | `caret:12` |
| `Select(start,len)` | Selects a range; zero length → empty selection. | `select:0-5` |
| `Backspace(count)` | Deletes selection or characters before caret. | `backspace:3` / `delete:sel` |
| `DeleteForward(count)` | Deletes selection or characters after caret. | `del:2` |
| `Batch(tag, func)` | Runs a lambda; if it returns true and state changed → single snapshot. | `batch:indent` |

---
## Branching example

1. Type several words.
2. Undo twice.
3. Type new characters.

The redo stack is truncated automatically (standard editor semantics): you cannot redo into the alternate future.

---
## Running the demo

```csharp
var log = MementoDemo.Run();
foreach (var line in log)
Console.WriteLine(line);
```

Sample (abridged):
```
v2:insert Hello -> 'Hello' (caret 5)
v3:insert , world -> 'Hello, world' (caret 12)
...
v12:branch insert !!! -> 'Hi brave new, world!!!' (caret 25)
FINAL:'Hi brave new, world!!!' version=12 history=12
```

(Version numbers include the initial `init` snapshot.)

---
## Undo / Redo guarantees

* `Undo()` returns false at earliest retained snapshot.
* `Redo()` returns false at latest snapshot.
* Any editing method after an `Undo` truncates forward snapshots.
* Capacity eviction removes only *oldest* snapshots; version numbers remain monotonic (never reused).

---
## Batch editing

```csharp
editor.Batch("indent-line", ed => {
var s = ed.State;
if (!s.Text.Contains('\n')) return false; // no multi-line indent
var parts = s.Text.Split('\n');
for (int i = 0; i < parts.Length; i++) parts[i] = " " + parts[i];
var joined = string.Join('\n', parts);
// Replace entire text efficiently
ed.Select(0, s.Text.Length);
ed.ReplaceSelection(joined);
return true; // commit single snapshot
});
```

If `action` returns false or produces no net state change (duplicate), no snapshot is added.

---
## Testing highlights

The accompanying tests (see `MementoDemoTests`) validate:

* Deterministic final text of the canned demo run.
* Branching semantics (redo cleared after divergent edit).
* Capacity trimming still leaves monotonic versions.
* Batch operation produces a single snapshot.

---
## When to go further

For very large documents or extremely high-frequency edits consider:

* **Diff-based snapshots** (store reversed operations instead of full text).
* **Ring-buffer storage** (lock-free, fixed memory usage).
* **Compression** of large text blocks (e.g., per 10th snapshot) if memory pressure is high.

PatternKit’s `Memento<TState>` keeps the core generic and simple so you can layer these later.

---
## See also

* [Memento Pattern Core](../patterns/behavioral/memento/memento.md)
* [Command](../patterns/behavioral/command/command.md) for reversible operations without full state copies
* Strategy / Chain for conditional, rule-based transformations prior to snapshot

3 changes: 3 additions & 0 deletions docs/examples/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@

- name: Flyweight Glyph Cache & Style Sharing
href: flyweight-glyph-cache.md

- name: Text Editor History (Memento)
href: text-editor-memento.md
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ PatternKit will grow to cover **Creational**, **Structural**, and **Behavioral**
| -------------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Creational** | [Factory](patterns/creational/factory/factory.md) ✓ • [Composer](patterns/creational/builder/composer.md) ✓ • [ChainBuilder](patterns/creational/builder/chainbuilder.md) ✓ • [BranchBuilder](patterns/creational/builder/chainbuilder.md) ✓ • [MutableBuilder](patterns/creational/builder/mutablebuilder.md) ✓ • [Prototype](patterns/creational/prototype/prototype.md) ✓ • [Singleton](patterns/creational/singleton/singleton.md) ✓ |
| **Structural** | [Adapter](patterns/structural/adapter/fluent-adapter.md) ✓ • [Bridge](patterns/structural/bridge/bridge.md) ✓ • [Composite](patterns/structural/composite/composite.md) ✓ • [Decorator](patterns/structural/decorator/index.md) ✓ • [Facade](patterns/structural/facade/facade.md) ✓ • [Flyweight](patterns/structural/flyweight/index.md) ✓ • [Proxy](patterns/structural/proxy/index.md) ✓ |
| **Behavioral** | [Strategy](patterns/behavioral/strategy/strategy.md) ✓ • [TryStrategy](patterns/behavioral/strategy/trystrategy.md) ✓ • [ActionStrategy](patterns/behavioral/strategy/actionstrategy.md) ✓ • [ActionChain](patterns/behavioral/chain/actionchain.md) ✓ • [ResultChain](patterns/behavioral/chain/resultchain.md) ✓ • [Command](patterns/behavioral/command/command.md) ✓ • [ReplayableSequence](patterns/behavioral/iterator/replayablesequence.md) ✓ • [WindowSequence](patterns/behavioral/iterator/windowsequence.md) ✓ • [Mediator](behavioral/mediator/mediator.md) ✓ • Memento (planned) • Observer (planned) • State (planned) • Template Method (planned) • Visitor (planned) |
| **Behavioral** | [Strategy](patterns/behavioral/strategy/strategy.md) ✓ • [TryStrategy](patterns/behavioral/strategy/trystrategy.md) ✓ • [ActionStrategy](patterns/behavioral/strategy/actionstrategy.md) ✓ • [ActionChain](patterns/behavioral/chain/actionchain.md) ✓ • [ResultChain](patterns/behavioral/chain/resultchain.md) ✓ • [Command](patterns/behavioral/command/command.md) ✓ • [ReplayableSequence](patterns/behavioral/iterator/replayablesequence.md) ✓ • [WindowSequence](patterns/behavioral/iterator/windowsequence.md) ✓ • [Mediator](patterns/behavioral/mediator/mediator.md) ✓ • [Memento](patterns/behavioral/memento/memento.md) ✓ • Observer (planned) • State (planned) • Template Method (planned) • Visitor (planned) |

Each pattern will ship with:

Expand Down
Loading
Loading