diff --git a/Directory.Packages.props b/Directory.Packages.props
index 0100be0b05..49408983b5 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -27,7 +27,7 @@
-
+
diff --git a/Examples/UICatalog/Scenarios/ConfigurationEditor.cs b/Examples/UICatalog/Scenarios/ConfigurationEditor.cs
index 6a502024a4..e1b35674b3 100644
--- a/Examples/UICatalog/Scenarios/ConfigurationEditor.cs
+++ b/Examples/UICatalog/Scenarios/ConfigurationEditor.cs
@@ -1,8 +1,8 @@
#nullable enable
using System.Reflection;
-using Terminal.Gui.Document;
+using Terminal.Gui.Editor.Document;
using Terminal.Gui.Editor;
-using Terminal.Gui.Highlighting;
+using Terminal.Gui.Editor.Highlighting;
namespace UICatalog.Scenarios;
diff --git a/Examples/UICatalog/Scenarios/Mandelbrot.cs b/Examples/UICatalog/Scenarios/Mandelbrot.cs
index d409ce9ef6..aad2039149 100644
--- a/Examples/UICatalog/Scenarios/Mandelbrot.cs
+++ b/Examples/UICatalog/Scenarios/Mandelbrot.cs
@@ -74,12 +74,14 @@ public override void Main ()
CanFocus = true
};
- _status = new Label { X = Pos.Align (Alignment.Start), Y = Pos.Align (Alignment.Start), Width = Dim.Fill (), Height = 1 };
+ _status = new Label { X = 0, Y = Pos.AnchorEnd (1), Width = Dim.Fill (), Height = 1 };
_mandelbrotView = new MandelbrotImageView
{
- X = Pos.Center (),
- Y = Pos.Center ()
+ X = 0,
+ Y = 0,
+ Width = Dim.Fill (),
+ Height = Dim.Fill ()
};
_mandelbrotView.ImageRendered += (_, _) => UpdateMandelbrotStatus ();
_mandelbrotView.MandelbrotSettingsChanged += (_, _) => UpdateSettingsFromMandelbrotView ();
diff --git a/Examples/UICatalog/Scenarios/MarkdownTester.cs b/Examples/UICatalog/Scenarios/MarkdownTester.cs
index d8e30f6a2a..d5ea1915a4 100644
--- a/Examples/UICatalog/Scenarios/MarkdownTester.cs
+++ b/Examples/UICatalog/Scenarios/MarkdownTester.cs
@@ -1,7 +1,7 @@
// ReSharper disable AccessToDisposedClosure
using Terminal.Gui.Editor;
-using Terminal.Gui.Highlighting;
+using Terminal.Gui.Editor.Highlighting;
using TextMateSharp.Grammars;
namespace UICatalog.Scenarios;
diff --git a/Examples/UICatalog/Scenarios/Notepad.cs b/Examples/UICatalog/Scenarios/Notepad.cs
index 5344bbc3a9..fa9130f9fe 100644
--- a/Examples/UICatalog/Scenarios/Notepad.cs
+++ b/Examples/UICatalog/Scenarios/Notepad.cs
@@ -2,9 +2,9 @@
#nullable enable
-using Terminal.Gui.Document;
+using Terminal.Gui.Editor.Document;
using Terminal.Gui.Editor;
-using Terminal.Gui.Highlighting;
+using Terminal.Gui.Editor.Highlighting;
namespace UICatalog.Scenarios;
diff --git a/Scripts/tuirec/README.md b/Scripts/tuirec/README.md
index 1c11fb5ac1..ee9cc5b069 100644
--- a/Scripts/tuirec/README.md
+++ b/Scripts/tuirec/README.md
@@ -15,7 +15,33 @@ tuirec --version
# agg is auto-downloaded on first use — no separate install needed.
```
-Verify: `tuirec --version`. If not on PATH, add `$(go env GOPATH)\bin` to PATH.
+Verify: `tuirec --version`. If not on PATH, add Go's bin dir
+(`$(go env GOPATH)\bin` on Windows, `$(go env GOPATH)/bin` on Linux/macOS) to PATH.
+
+> **PowerShell vs. bash.** The snippets below are PowerShell (the project's
+> default shell). The **raster recipes are Linux/macOS only** (Windows ConPTY
+> can't capture Kitty/sixel), so where it matters this guide gives a bash version
+> too. The mechanical translations: `Select-String -Pattern 'x'` →
+> `grep -o 'x' | wc -l`, `Copy-Item a b` → `cp a b`, `$ks = '...'` →
+> `ks='...'`, and the backtick line-continuation `` ` `` → `\`.
+
+### Fresh container / clean clone
+
+`tuirec` (Go) and `ScenarioRunner` (.NET) both need toolchains the Install
+section assumes are present. On a clean Linux box:
+
+```bash
+# .NET SDK — match global.json (read the version from it; currently 10.0.100)
+curl -sSL https://dot.net/v1/dotnet-install.sh | bash -s -- --version 10.0.100 --install-dir ~/.dotnet
+export PATH="$HOME/.dotnet:$PATH"
+
+# Go 1.22+ (if missing: distro package manager, or https://go.dev/dl)
+# tuirec installs into GOPATH/bin, which is often off-PATH:
+go install github.com/gui-cs/tuirec/cmd/tuirec@latest
+export PATH="$(go env GOPATH)/bin:$PATH"
+
+tuirec --version && dotnet --version
+```
## Quick Start — Recording a UICatalog Scenario
@@ -252,6 +278,110 @@ tuirec record `
---
+## Raster graphics: Kitty (default) and sixel
+
+Terminal.Gui's `ImageView` (with `UseRasterGraphics = true`) picks the best
+raster protocol the terminal advertises: **Kitty graphics** when available,
+otherwise **sixel**, otherwise cell rendering. Which one a recording captures
+depends on what identity `tuirec` presents to the app.
+
+- **`tuirec` ≥ v0.9.0 defaults to Kitty graphics.** It advertises a deterministic
+ Kitty identity (a `KITTY_WINDOW_ID` marker) to the recorded app, so apps that
+ prefer Kitty emit Kitty image escapes (`ESC _ G … ST`). The pinned
+ `agg` (`v1.11.0-sixel`, built on a Kitty-capable `avt`) renders them in the GIF.
+ This is the path the UICatalog **Mandelbrot** and **Images** scenarios take by
+ default. Terminal.Gui detects Kitty support purely from the environment, so the
+ app reports `Kitty … active` in its capability matrix with no extra flags.
+- **Sixel** is still used when the app does not support Kitty, or you force the
+ sixel path in-app (e.g. the Mandelbrot scenario's "Sixel" protocol option).
+- **Both are Linux/macOS only.** Windows ConPTY strips both Kitty graphics APC
+ strings and sixel DCS from the output stream, so neither is captured there.
+
+**Confirm which protocol the cast captured** (the `.cast` is JSON, so the escape
+introducer shows up as ``):
+
+```powershell
+# PowerShell
+Select-String -Path artifacts/.cast -Pattern 'u001b_G' | Measure-Object # Kitty
+Select-String -Path artifacts/.cast -Pattern 'u001bPq' | Measure-Object # sixel
+```
+
+```bash
+# Linux/macOS — the raster recipes only run here
+grep -o 'u001b_G' artifacts/.cast | wc -l # Kitty (expected by default)
+grep -o 'u001bPq' artifacts/.cast | wc -l # sixel (only when forced)
+```
+
+The sixel cell-size verification below applies to the **sixel** path; the
+[#84](https://github.com/gui-cs/tuirec/issues/84) cell-resolution mismatch is a
+sixel concern and does not apply when the app renders via Kitty graphics.
+
+> **The `adjusted agg font-size … to align the sixel cell grid (#84)` log line is
+> expected, not an error.** `tuirec` ≥ v0.9.0 auto-calibrates agg's font size to
+> close the #84 mismatch during recording. It's harmless for the Kitty path —
+> don't chase it.
+
+> **Smooth zoom/pan recordings.** Each keystroke pauses `--keystroke-delay` ms
+> (default 200). For continuous-looking motion (e.g. zooming/panning an image),
+> use a shorter delay (`--keystroke-delay 130`) and many small steps rather than
+> a few large ones. Note that in-app *mouse-wheel* zoom may not work under
+> `tuirec` (some views bind the wheel to pan); prefer the keyboard zoom keys.
+
+### Exact recipe — `docfx/images/Mandelbrot.gif`
+
+This reproduces the committed Mandelbrot hero GIF in one shot. Because raster
+capture is **Linux/macOS only**, the recipe is shown in bash; build
+`ScenarioRunner` first (see Prerequisites), then run from the repo root. (In
+PowerShell on macOS, translate per the *PowerShell vs. bash* note above:
+`$ks = '...'`, backtick line-continuations, `Copy-Item`.)
+
+```bash
+dll="./Examples/ScenarioRunner/bin/Release/net10.0/ScenarioRunner.dll"
+# Tour: full set → zoom into the seahorse valley → pan across it → zoom out → reset
+ks='wait:1600,PageUp,wait:150,CursorLeft,CursorDown,CursorDown,wait:300,PageUp,wait:120,PageUp,wait:120,PageUp,wait:120,PageUp,wait:850,CursorRight,wait:150,CursorRight,wait:150,CursorUp,wait:180,CursorLeft,wait:150,CursorLeft,wait:150,CursorLeft,wait:150,CursorDown,wait:180,CursorRight,wait:150,CursorRight,wait:600,PageDown,wait:120,PageDown,wait:120,PageDown,wait:120,PageDown,wait:120,PageDown,wait:300,Home,wait:1100,Esc'
+
+tuirec record --binary dotnet --args "$dll,run,Mandelbrot" --name Mandelbrot \
+ --title "Mandelbrot" --keystrokes "$ks" \
+ --startup-delay 2000 --drain 1200 --cols 120 --rows 30 --keystroke-delay 130
+
+cp artifacts/Mandelbrot.gif docfx/images/Mandelbrot.gif
+```
+
+Validate (robust invariants — see the softened counts below):
+
+```bash
+grep -o 'u001b_G' artifacts/Mandelbrot.cast | wc -l # Kitty: expect thousands
+grep -o 'u001bPq' artifacts/Mandelbrot.cast | wc -l # sixel: expect 0
+# Extract a mid-zoom frame without ImageMagick/ffmpeg (needs python3 + Pillow):
+python3 -c "from PIL import Image; im=Image.open('artifacts/Mandelbrot.gif'); im.seek(im.n_frames//2); im.convert('RGB').save('/tmp/mid.png')"
+```
+
+**Why each part matters (don't "improve" these blindly):**
+
+- **The image view fills the display.** The scenario anchors `MandelbrotImageView`
+ at `(0,0)` with `Dim.Fill()` so the fractal is large enough for motion to read.
+ If you record a small centered image, the demo looks cramped.
+- **Zoom is keyboard-only and center-anchored.** `PageUp`/`PageDown` zoom about
+ the *center* (the in-app mouse wheel pans, it does not zoom). So you must pan
+ the target to the center first, then zoom.
+- **Target = the seahorse valley**, center ≈ `(-0.745, +0.11)` — the cusp where
+ the main cardioid meets the period-2 bulb. The opening `PageUp` shrinks the pan
+ step so `CursorLeft,CursorDown,CursorDown` lands at ≈ `(-0.74, +0.105)` instead
+ of overshooting onto the (mostly black) antenna filament at `-0.8`.
+- **Stop around span ≈ 1.0** (about 5 `PageUp`s total). At the scenario's default
+ 80 iterations the valley goes mostly black past ~span 0.5; span ≈ 1.0 keeps the
+ colorful seahorse filaments. For a deeper dive you'd raise the iteration count
+ first (the Iterations control, or `DEFAULT_ITERATIONS`).
+- **Validate against invariants, not exact counts.** The cast holds **thousands**
+ of `u001b_G` (Kitty) payloads and **zero** `u001bPq` (sixel), and the GIF is
+ **~0.9 MB**. The precise payload count drifts (±a few hundred) with timing and
+ the auto font-size adjust — don't treat it as a target. Then open `/tmp/mid.png`
+ (extracted above) and confirm the in-app readout reads **Center X ≈ −0.74,
+ Center Y ≈ 0.105, Span ≈ 1.0** over colorful seahorse structure — measuring the
+ landmark, per this guide's "measure, don't eyeball" tenet, not eyeballing a vibe.
+
+---
+
## Verifying Placement and Size (measure — don't eyeball)
**The recurring trap.** Confirming a sixel *appears* in the GIF — or that agg
@@ -311,6 +441,9 @@ After every recording, verify:
```powershell
Select-String -Path artifacts/.cast -Pattern "error|unknown|not found|usage:" -CaseSensitive:$false
```
+ ```bash
+ grep -iE "error|unknown|not found|usage:" artifacts/.cast
+ ```
- [ ] **GIF is not blank** — file size > 100KB for a typical scenario recording.
(A blank/static GIF is typically < 50KB.)
- [ ] **Visual check** — open the GIF (`--open` flag) and confirm:
@@ -321,10 +454,17 @@ After every recording, verify:
```
Examples/UICatalog/Scenarios//.gif
```
-- [ ] **Sixel content recorded on Linux/macOS** — sixel DCS cannot be captured
- through Windows ConPTY. Confirm sixel made it into the cast with:
+- [ ] **Raster content recorded on Linux/macOS** — Kitty graphics APC and sixel
+ DCS cannot be captured through Windows ConPTY. Confirm the expected protocol
+ made it into the cast (Kitty is the default for raster apps; see *Raster
+ graphics* above):
```powershell
- Select-String -Path artifacts/.cast -Pattern 'u001bP' | Measure-Object
+ Select-String -Path artifacts/.cast -Pattern 'u001b_G' | Measure-Object # Kitty
+ Select-String -Path artifacts/.cast -Pattern 'u001bPq' | Measure-Object # sixel
+ ```
+ ```bash
+ grep -o 'u001b_G' artifacts/.cast | wc -l # Kitty
+ grep -o 'u001bPq' artifacts/.cast | wc -l # sixel
```
- [ ] **Grid-anchored sixel measured, not eyeballed** — if the sixel is sized or
aligned to the text grid, calibrate agg's real cell and confirm the rendered
@@ -338,7 +478,8 @@ After every recording, verify:
| Problem | Cause | Fix |
|---------|-------|-----|
-| No sixel output on Windows | **Windows ConPTY strips sixel DCS** and does not pass through the DA1 sixel handshake — the app detects `Sixel support: False` | Record sixel content on Linux/macOS (see `tuirec agent-guide`). On Windows you can still verify the app's sixel code path runs (e.g., via an app-level force flag) by checking redraw activity in the `.cast`, but flame/image pixels will not appear |
+| No raster output on Windows | **Windows ConPTY strips Kitty graphics APC and sixel DCS** and does not pass the DA1 sixel handshake — the app detects no raster support | Record raster content (Kitty or sixel) on Linux/macOS (see `tuirec agent-guide`). On Windows you can still verify the app's raster code path runs (e.g., via an app-level force flag) by checking redraw activity in the `.cast`, but image pixels will not appear |
+| Image renders via sixel instead of Kitty (or vice versa) | The app picks its preferred protocol from what the terminal advertises; `tuirec` ≥ v0.9.0 advertises Kitty by default | Confirm the captured protocol in the cast (`u001b_G` for Kitty, `u001bPq` for sixel). To force sixel, use the app's own protocol control (e.g. the Mandelbrot scenario's "Sixel" option) |
| Sixel renders ~4% too small / short of a border | tuirec advertises a cell resolution that doesn't match agg's rendered font cell ([#84](https://github.com/gui-cs/tuirec/issues/84)) | App is correct (fills on a real terminal). Verify by measurement (see *Verifying Placement and Size*); attribute to tuirec, not the app. Until fixed, only a tuirec-specific over-render hack would close the gap |
| Wide glyphs misaligned in GIF | Emoji/CJK chars are 2-cell wide; agg renders per-cell | Avoid emoji/CJK categories; use single-width ranges (Arrows, Box Drawing, etc.) |
| Nav keys ignored with `--kitty-keyboard` | tuirec bug [#54](https://github.com/gui-cs/tuirec/issues/54) — sends wrong codepoints | Remove `--kitty-keyboard` |
diff --git a/Terminal.Gui/Input/Command.cs b/Terminal.Gui/Input/Command.cs
index 31e35f016b..d47cc94a32 100644
--- a/Terminal.Gui/Input/Command.cs
+++ b/Terminal.Gui/Input/Command.cs
@@ -17,6 +17,14 @@ namespace Terminal.Gui.Input;
///
/// See the Commands Deep Dive for more information: .
///
+///
+/// Every member has an explicit, frozen integer value. These values are an ABI contract:
+/// separately-compiled assemblies (e.g. the Terminal.Gui.Editor package) bake the integer
+/// of each into their key bindings. Inserting or reordering members would
+/// silently change those integers and re-map already-compiled bindings to the wrong commands —
+/// e.g. Backspace invoking (gui-cs/Editor#241). When adding a command,
+/// append it with the next unused number; never insert, reorder, or renumber existing members.
+///
///
///
///
@@ -40,7 +48,7 @@ public enum Command
/// events.
///
///
- Accept,
+ Accept = 1,
///
/// Performs a hot key action (e.g. setting focus, accepting, and/or moving focus to the next View).
@@ -49,7 +57,7 @@ public enum Command
/// and if that is not handled, invokes .
///
///
- HotKey,
+ HotKey = 2,
///
/// Activates the View or an item in the View, changing its state or preparing it for interaction
@@ -59,129 +67,129 @@ public enum Command
/// handled, will be called and the event will be raised.
///
///
- Activate,
+ Activate = 3,
#endregion
#region Movement Commands
/// Moves up one (cell, line, etc...).
- Up,
+ Up = 4,
/// Moves down one item (cell, line, etc...).
- Down,
+ Down = 5,
///
/// Moves left one (cell, line, etc...).
///
- Left,
+ Left = 6,
///
/// Moves right one (cell, line, etc...).
///
- Right,
+ Right = 7,
/// Move one page up.
- PageUp,
+ PageUp = 8,
/// Move one page down.
- PageDown,
+ PageDown = 9,
/// Moves to the left page.
- PageLeft,
+ PageLeft = 10,
/// Moves to the right page.
- PageRight,
+ PageRight = 11,
/// Moves to the top of page.
- StartOfPage,
+ StartOfPage = 12,
/// Moves to the bottom of page.
- EndOfPage,
+ EndOfPage = 13,
/// Moves to the start (e.g. the top or home).
- Start,
+ Start = 14,
/// Moves or resets to the home position.
- Home,
+ Home = 15,
/// Moves to the end (e.g. the bottom).
- End,
+ End = 16,
/// Moves left to the start on the current row/line.
- LeftStart,
+ LeftStart = 17,
/// Moves right to the end on the current row/line.
- RightEnd,
+ RightEnd = 18,
/// Moves to the start of the previous word.
- WordLeft,
+ WordLeft = 19,
/// Moves the start of the next word.
- WordRight,
+ WordRight = 20,
#endregion
#region Movement With Extension Commands
/// Extends the selection up one item (cell, line, etc...).
- UpExtend,
+ UpExtend = 21,
/// Extends the selection down one (cell, line, etc...).
- DownExtend,
+ DownExtend = 22,
///
/// Extends the selection left one item (cell, line, etc...)
///
- LeftExtend,
+ LeftExtend = 23,
///
/// Extends the selection right one item (cell, line, etc...)
///
- RightExtend,
+ RightExtend = 24,
/// Extends the selection to the start of the previous word.
- WordLeftExtend,
+ WordLeftExtend = 25,
/// Extends the selection to the start of the next word.
- WordRightExtend,
+ WordRightExtend = 26,
/// Move one page down extending the selection to cover revealed objects/characters.
- PageDownExtend,
+ PageDownExtend = 27,
/// Move one page up extending the selection to cover revealed objects/characters.
- PageUpExtend,
+ PageUpExtend = 28,
/// Extends the selection to start (e.g. home or top).
- StartExtend,
+ StartExtend = 29,
/// Extends the selection to end (e.g. bottom).
- EndExtend,
+ EndExtend = 30,
/// Extends the selection to the start on the current row/line.
- LeftStartExtend,
+ LeftStartExtend = 31,
/// Extends the selection to the right on the current row/line.
- RightEndExtend,
+ RightEndExtend = 32,
/// Toggles the selection (or a specific element of the selection).
- ToggleExtend,
+ ToggleExtend = 33,
#endregion
#region Editing Commands
/// Deletes the word to the right of the cursor.
- KillWordRight,
+ KillWordRight = 34,
/// Deletes the word to left to the cursor.
- KillWordLeft,
+ KillWordLeft = 35,
///
/// Toggles overwrite mode such that newly typed text overwrites the text that is already there (typically
/// associated with the Insert key).
///
- ToggleOverwrite,
+ ToggleOverwrite = 36,
// QUESTION: What is the difference between EnableOverwrite and ToggleOverwrite?
@@ -189,185 +197,185 @@ public enum Command
/// Enables overwrite mode such that newly typed text overwrites the text that is already there (typically
/// associated with the Insert key).
///
- EnableOverwrite,
+ EnableOverwrite = 37,
///
/// Inserts a character.
///
- Insert,
+ Insert = 38,
/// Disables overwrite mode ()
- DisableOverwrite,
+ DisableOverwrite = 39,
/// Deletes the character on the right.
- DeleteCharRight,
+ DeleteCharRight = 40,
/// Deletes the character on the left.
- DeleteCharLeft,
+ DeleteCharLeft = 41,
/// Selects all objects.
- SelectAll,
+ SelectAll = 42,
/// Deletes all objects.
- DeleteAll,
+ DeleteAll = 43,
/// Inserts a new item.
- NewLine,
+ NewLine = 44,
/// Unix emulation.
- UnixEmulation,
+ UnixEmulation = 45,
/// Inserts a tab character or spaces at the cursor or selection.
- InsertTab,
+ InsertTab = 46,
/// Removes one level of indentation from the current line or selection.
- Unindent,
+ Unindent = 47,
#endregion
#region Search Commands
/// Opens or activates a find/search UI.
- Find,
+ Find = 48,
/// Finds the next match.
- FindNext,
+ FindNext = 49,
/// Finds the previous match.
- FindPrevious,
+ FindPrevious = 50,
/// Opens or activates a find-and-replace UI.
- Replace,
+ Replace = 51,
#endregion
#region Tree Commands
/// Moves down to the last child node of the branch that holds the current selection.
- LineDownToLastBranch,
+ LineDownToLastBranch = 52,
/// Moves up to the first child node of the branch that holds the current selection.
- LineUpToFirstBranch,
+ LineUpToFirstBranch = 53,
#endregion
#region Scroll Commands
/// Scrolls down one (cell, line, etc...).
- ScrollDown,
+ ScrollDown = 54,
/// Scrolls up one item (cell, line, etc...).
- ScrollUp,
+ ScrollUp = 55,
/// Scrolls one item (cell, character, etc...) to the left.
- ScrollLeft,
+ ScrollLeft = 56,
/// Scrolls one item (cell, character, etc...) to the right.
- ScrollRight,
+ ScrollRight = 57,
#endregion
#region Clipboard Commands
/// Undo changes.
- Undo,
+ Undo = 58,
/// Redo changes.
- Redo,
+ Redo = 59,
/// Copies the current selection.
- Copy,
+ Copy = 60,
/// Cuts the current selection.
- Cut,
+ Cut = 61,
/// Pastes the current selection.
- Paste,
+ Paste = 62,
/// Cuts to the clipboard the characters from the current position to the end of the line.
- CutToEndOfLine,
+ CutToEndOfLine = 63,
/// Cuts to the clipboard the characters from the current position to the start of the line.
- CutToStartOfLine,
+ CutToStartOfLine = 64,
#endregion
#region Navigation Commands
/// Moves focus to the next .
- NextTabStop,
+ NextTabStop = 65,
/// Moves focus to the previous .
- PreviousTabStop,
+ PreviousTabStop = 66,
/// Moves focus to the next .
- NextTabGroup,
+ NextTabGroup = 67,
/// Moves focus to the next.
- PreviousTabGroup,
+ PreviousTabGroup = 68,
/// Enables arrange mode.
- Arrange,
+ Arrange = 69,
#endregion
#region Action Commands
/// Toggles something (e.g. the expanded or collapsed state of a list).
- Toggle,
+ Toggle = 70,
/// Expands a list or item (with subitems).
- Expand,
+ Expand = 71,
/// Recursively Expands all child items and their child items (if any).
- ExpandAll,
+ ExpandAll = 72,
/// Collapses a list or item (with subitems).
- Collapse,
+ Collapse = 73,
/// Recursively collapses a list items of their children (if any).
- CollapseAll,
+ CollapseAll = 74,
/// Cancels an action or any temporary states on the control e.g. expanding a combo list.
- Cancel,
+ Cancel = 75,
/// Quit.
- Quit,
+ Quit = 76,
/// Refresh.
- Refresh,
+ Refresh = 77,
/// Suspend an application (Only implemented in UnixDriver).
- Suspend,
+ Suspend = 78,
/// Open the selected item or invoke a UI for opening something.
- Open,
+ Open = 79,
/// Saves the current document.
- Save,
+ Save = 80,
/// Saves the current document with a new name.
- SaveAs,
+ SaveAs = 81,
/// Creates a new document.
- New,
+ New = 82,
/// Shows context about the item (e.g. a context menu).
- Context,
+ Context = 83,
///
/// Invokes a user interface for editing or configuring something.
///
- Edit,
+ Edit = 84,
/// Centers the current item or viewport.
- Center,
+ Center = 85,
/// Zooms in.
- ZoomIn,
+ ZoomIn = 86,
/// Zooms out.
- ZoomOut,
+ ZoomOut = 87,
#endregion
@@ -379,7 +387,7 @@ public enum Command
/// editor.action.insertCursorAbove. Views that support multi-caret bind this
/// through or their configurable default key bindings.
///
- InsertCaretAbove,
+ InsertCaretAbove = 88,
///
/// Adds an additional caret one line below the bottommost caret (multi-caret editing),
@@ -387,17 +395,17 @@ public enum Command
/// editor.action.insertCursorBelow. Views that support multi-caret bind this
/// through or their configurable default key bindings.
///
- InsertCaretBelow,
+ InsertCaretBelow = 89,
#endregion
#region Mouse Selection Commands
/// Starts extending a selection via pointing-device input.
- StartSelection,
+ StartSelection = 90,
/// Starts extending a rectangular selection via pointing-device input.
- StartRectangleSelection,
+ StartRectangleSelection = 91,
#endregion
}
diff --git a/Tests/UnitTestsParallelizable/Input/CommandFrozenValueTests.cs b/Tests/UnitTestsParallelizable/Input/CommandFrozenValueTests.cs
new file mode 100644
index 0000000000..a3b88b0466
--- /dev/null
+++ b/Tests/UnitTestsParallelizable/Input/CommandFrozenValueTests.cs
@@ -0,0 +1,78 @@
+using Terminal.Gui.Input;
+
+namespace InputTests;
+
+///
+/// Locks the explicit integer value of every member. These values are an ABI
+/// contract: separately-compiled assemblies (notably the Terminal.Gui.Editor package) bake the
+/// integer of each command into their key bindings, so inserting/reordering/renumbering a member
+/// silently re-maps already-compiled bindings to the wrong command — the gui-cs/Editor#241 regression
+/// where Backspace invoked .
+///
+/// When you ADD a command, append it with the next unused number and add a line here. If this test
+/// fails because an existing value changed, that is the bug — restore the value, do not edit the
+/// expectation.
+///
+///
+public class CommandFrozenValueTests
+{
+ // The complete, frozen map. Index == the wire value the enum must keep forever.
+ private static readonly (Command Command, int Value) [] Frozen =
+ [
+ (Command.NotBound, 0),
+ (Command.Accept, 1),
+ (Command.HotKey, 2),
+ (Command.Activate, 3),
+ (Command.Up, 4),
+ (Command.Down, 5),
+ (Command.Left, 6),
+ (Command.Right, 7),
+ (Command.PageUp, 8),
+ (Command.PageDown, 9),
+ (Command.PageLeft, 10),
+ (Command.PageRight, 11),
+ (Command.StartOfPage, 12),
+ (Command.EndOfPage, 13),
+ (Command.Start, 14),
+ (Command.Home, 15),
+ (Command.End, 16),
+ (Command.LeftStart, 17),
+ (Command.RightEnd, 18),
+ (Command.WordLeft, 19),
+ (Command.DeleteCharRight, 40),
+ (Command.DeleteCharLeft, 41),
+ (Command.SelectAll, 42),
+ ];
+
+ [Fact]
+ public void FrozenCommands_HaveTheirContractValue ()
+ {
+ foreach ((Command command, int value) in Frozen)
+ {
+ Assert.Equal (value, (int)command);
+ }
+ }
+
+ [Fact]
+ public void NotBound_IsZero ()
+ {
+ Assert.Equal (0, (int)Command.NotBound);
+ }
+
+ [Fact]
+ public void AllValues_AreUnique ()
+ {
+ int [] values = Enum.GetValues ().Cast ().Select (c => (int)c).ToArray ();
+ Assert.Equal (values.Length, values.Distinct ().Count ());
+ }
+
+ [Fact]
+ public void BindingCriticalCommands_DoNotCollide ()
+ {
+ // The exact trio behind gui-cs/Editor#241: a one-off shift made Backspace's bound command
+ // (DeleteCharLeft) resolve to SelectAll. They must stay distinct and in this fixed order.
+ Assert.Equal (40, (int)Command.DeleteCharRight);
+ Assert.Equal (41, (int)Command.DeleteCharLeft);
+ Assert.Equal (42, (int)Command.SelectAll);
+ }
+}
diff --git a/docfx/docs/drawing.md b/docfx/docs/drawing.md
index 854efe500c..6a15aa807f 100644
--- a/docfx/docs/drawing.md
+++ b/docfx/docs/drawing.md
@@ -127,7 +127,7 @@ To customize Sixel encoding, assign `ImageView.SixelEncoder` before setting `Ima
The UICatalog Images scenario demonstrates runtime protocol selection, and the Mandelbrot scenario demonstrates a custom `ImageView` that re-renders fractal pixels while zooming and panning.
-
+
## Cell
diff --git a/docfx/images/Mandelbrot.gif b/docfx/images/Mandelbrot.gif
index 383f96f572..0d6b00e99d 100644
Binary files a/docfx/images/Mandelbrot.gif and b/docfx/images/Mandelbrot.gif differ