Skip to content
20 changes: 15 additions & 5 deletions Docs/Help/multi-caret.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ Place multiple carets in the document and type, delete, or press Enter at all of
| **Ctrl+Click** | Toggle an additional caret at the clicked position. Click an existing additional caret to remove it. |
| **Ctrl+Alt+↑** | Add a caret on the line above the topmost caret, at the sticky visual column (VS Code parity). |
| **Ctrl+Alt+↓** | Add a caret on the line below the bottommost caret, at the sticky visual column (VS Code parity). |
| **Alt + drag** | Build a vertical column of carets from the press row through the drag row at the press column (carets only). |
| **Alt + drag** | Build a vertical column from the press row through the drag row. Zero horizontal extent creates carets; horizontal extent creates one selection per row. |
| **Ctrl+Shift+Alt+↑/↓/←/→** | Create or extend a keyboard column selection. `PgUp` / `PgDn` extend by one viewport. |
| **Escape** | Collapse back to the primary caret (clears all additional carets). |

`Ctrl+Alt+↑/↓` track a *sticky visual column*: a short or tab-indented intervening line doesn't lose the column — the next long-enough line restores it. The chords are configurable per platform via `Editor.DefaultKeyBindings`; a terminal or window manager that grabs `Ctrl+Alt+arrow` is handled by remapping in config, not a separate built-in chord.
Expand All @@ -29,7 +30,7 @@ All edits are wrapped in a single `Document.RunUpdate` scope, so **Undo (Ctrl+Z)

## Visual feedback

Additional carets are rendered as blinking, reverse-video cells by the `MultiCaretRenderer` (an `IOverlayRenderer`). The status bar in `ted` shows the total caret count when in multi-caret mode (e.g. "Ln 4, Col 1 (3 carets)").
Additional carets are rendered as blinking, reverse-video cells by the `MultiCaretRenderer` (an `IOverlayRenderer`). Additional-caret selections render with the same active selection role as the primary selection. The status bar in `ted` shows the total caret count when in multi-caret mode (e.g. "Ln 4, Col 1 (3 carets)").

## Programmatic API

Expand All @@ -47,10 +48,19 @@ editor.ClearAdditionalCarets ();

Additional carets are backed by `TextAnchor` instances, so they track insertions and deletions elsewhere in the document automatically (same mechanism as the primary caret).

## VS Code parity and intentional deviations

Column selection matches VS Code behavior: typing over a ranged column replaces each row's selection in one undo step; short rows clamp to the real line end without writing padding; dragging left of the anchor reverses the selection direction; `Esc` or a plain click collapses back to the primary caret.

Intentional deviations:

- **D1 — mouse modifier**: VS Code starts column selection with `Shift+Alt`+drag; this editor uses **`Alt`+drag** because Windows Terminal and xterm-family terminals reserve `Shift`+drag for terminal-side forced/block selection while an app has mouse mode enabled. Configurable mouse modifiers are tracked by [gui-cs/Terminal.Gui#4888](https://github.com/gui-cs/Terminal.Gui/issues/4888).
- **D2 — add caret at click**: VS Code uses `Alt`+Click; this editor keeps the existing **`Ctrl`+Click** binding. `Alt` is the column-drag modifier, so an `Alt`+Click alias would need drag-threshold disambiguation.
- **D3 — keyboard column-select**: not a deviation. `Ctrl+Shift+Alt+Arrow` and `Ctrl+Shift+Alt+PgUp/PgDn` match VS Code behavior when the terminal delivers the chord (TG's Kitty keyboard protocol support makes this available on capable terminals).
- **D4 — sticky Column Selection Mode**: VS Code's modal toggle is out of scope; this editor implements the drag and keyboard gestures, not a persistent mode with menu/status UI.
- **D5 — multi-cursor paste distribution**: VS Code can distribute N clipboard lines over N cursors. This is deferred as a separate follow-up; typing and one-line paste replacement are covered here.

## Limitations (current alpha)

- Selection is not yet per-caret; only the primary caret carries a selection.
- Find/Replace operates on the primary caret only.
- `Alt`+drag produces a column of *carets*, not a column *selection*. To replace a column, drag to place the carets, then `Shift+→`/`←` to grow each caret's selection, then type. Per-row column selection during the drag is the planned follow-up.
- The column-drag modifier is `Alt`, not VS Code's `Shift+Alt`: terminals reserve `Shift`+drag for their own forced/block text selection while an app reads the mouse, so `Shift+Alt`+drag never reaches the editor. Making the mouse modifier user-configurable (to opt back into `Shift+Alt`) is tracked by [gui-cs/Terminal.Gui#4888](https://github.com/gui-cs/Terminal.Gui/issues/4888).
- Toggling Word Wrap while a vertical block is live dismisses the block.
8 changes: 4 additions & 4 deletions examples/ted/TedApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ public TedApp (bool readOnly = false)
}
};
ShowTabsCheckBox.Value = Editor.ShowTabs ? CheckState.Checked : CheckState.UnChecked;
OverwriteShortcut = new Shortcut (Key.Empty, "INS", null) { MouseHighlightStates = MouseState.None };
LocShortcut = new Shortcut (Key.Empty, FormatLoc (1, 1), null) { MouseHighlightStates = MouseState.None };
PreviewCheckBox.ValueChanged += (_, e) =>
{
ToggleMarkdownPreview ();
Expand All @@ -139,10 +141,8 @@ public TedApp (bool readOnly = false)
new ([
new Shortcut { Title = "Language", CommandView = LanguageShortcut },
new Shortcut { Title = "Theme", CommandView = ThemeDropDown },
OverwriteShortcut = new Shortcut (Key.Empty, "INS", null)
{ MouseHighlightStates = MouseState.None },
LocShortcut = new Shortcut (Key.Empty, FormatLoc (1, 1), null)
{ MouseHighlightStates = MouseState.None }
OverwriteShortcut,
LocShortcut
])
{
AlignmentModes = AlignmentModes.IgnoreFirstOrLast
Expand Down
2 changes: 1 addition & 1 deletion specs/multi-caret/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Add multi-caret support to the Editor. Expose `IReadOnlyList<int> AdditionalCare

## Out of Scope

- Column/block selection modethe "multi-select" follow-up PR. **When built, it must also close the carried-forward selection-preservation gap:** multi-caret `Tab`/`Shift+Tab` block-indent must preserve the primary *and* per-caret selections (parity with the single-caret `IndentSelectedLines` path; today `ClearAdditionalCaretSelections ()` collapses them post-edit). See `specs/vertical-multi-caret/spec.md` § Out of Scope → *Column / box selection* for the full requirement.
- Sticky Column Selection ModeVS Code's modal toggle where ordinary clicks/arrows keep column-select behavior until disabled. The drag and keyboard column-selection gestures are in scope for `specs/vertical-multi-caret/spec.md`; the persistent mode with menu/status UI is a separate follow-up.
- Multi-caret find/replace

## Notes
Expand Down
4 changes: 2 additions & 2 deletions specs/overwrite-mode/spec.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Overwrite (Insert-Replace) Mode

**Status**: Implemented
**Issue**: [#146](https://github.com/gui-cs/Editor/issues/146)
**Status**: Implemented
**Issue**: [#146](https://github.com/gui-cs/Editor/issues/146)
**Updated**: 2026-05-17

## Summary
Expand Down
8 changes: 4 additions & 4 deletions specs/public-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,9 @@ public class Editor : View
public void ToggleCaretAt (int offset); // multi-caret (Ctrl+Click toggle)
public void ClearAdditionalCarets (); // multi-caret (Esc collapse)
// vertical-multi-caret adds NO new public API: Ctrl+Alt+CursorUp / Ctrl+Alt+CursorDown
// create a vertically-aligned column of carets at the sticky visual column, and
// Alt + LeftButton drag builds a column of carets (carets only). Both reuse the
// existing AdditionalCaretOffsets / HasMultipleCarets / ClearAdditionalCarets surface and
// are bound through the configurable Editor.DefaultKeyBindings ([ConfigurationProperty]).
// create a vertically-aligned column of carets at the sticky visual column; Alt + LeftButton
// drag and Ctrl+Shift+Alt+Arrow/Page create column selections. All reuse the existing
// AdditionalCaretOffsets / HasMultipleCarets / ClearAdditionalCarets surface.

// --- Display ---
public bool ShowLineNumbers { get; set; } // exists
Expand Down Expand Up @@ -119,4 +118,5 @@ public interface IOverlayRenderer
| 2026-05-11 | ReadOnly property landed on Editor | read-only |
| 2026-05-12 | `ISearchStrategy?` `SearchStrategy { get; set; }` landed on Editor; string-based FindNext/FindPrevious/ReplaceNext/ReplaceAll overloads retained as convenience wrappers | find-and-replace |
| 2026-05-16 | Vertical multi-caret keybindings (`Ctrl+Alt+CursorUp/Down`, `Alt+Drag`) added via `Editor.DefaultKeyBindings`; no new public Editor API (R8) | vertical-multi-caret |
| 2026-05-17 | Column selection during `Alt+Drag` and `Ctrl+Shift+Alt+Arrow/Page` added without new public Editor API | vertical-multi-caret |
| 2026-05-17 | `Editor` implements `IDesignable`; `EnableForDesign()` seeds C# sample code with syntax highlighting and line numbers | design-time |
2 changes: 1 addition & 1 deletion specs/textview-parity-gap/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ exposed today.

### 2. Overwrite / insert-replace mode → [#146](https://github.com/gui-cs/Editor/issues/146)

**TextView**: `Used` flag + `Command.ToggleOverwrite` (Insert key), `Command.EnableOverwrite`,
**TextView**: overwrite-mode state + `Command.ToggleOverwrite` (Insert key), `Command.EnableOverwrite`,
`Command.DisableOverwrite`; a distinct caret rendering for overwrite; typing replaces the rune
under the caret instead of inserting.

Expand Down
23 changes: 13 additions & 10 deletions specs/vertical-multi-caret/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
Extend the existing multi-caret machinery (`AdditionalCaretOffsets`, `HasMultipleCarets`, `ToggleCaretAt`, `ClearAdditionalCarets`) with two ergonomic ways to create a **vertically-aligned column of carets** anchored on the same visual column across consecutive lines:

1. **Keyboard**: `Ctrl+Alt+Up` / `Ctrl+Alt+Down` extends the caret block one line above the topmost / below the bottommost caret, landing on the same sticky virtual column. Matches VS Code's `editor.action.insertCursorAbove` / `Below`.
2. **Mouse**: `Alt + LeftButton drag` creates a column of carets spanning the anchor row → active row at the press column. (VS Code uses `Shift+Alt` for its column-select drag; this editor uses `Alt` because a TUI runs inside a terminal that reserves `Shift`+drag — see the Amendment above. This spec ships carets-only first; selection-per-row is a follow-up — see Out of Scope.)
2. **Mouse**: `Alt + LeftButton drag` creates a column spanning the anchor row → active row. Zero horizontal extent creates carets; horizontal extent creates one selection per row. (VS Code uses `Shift+Alt` for its column-select drag; this editor uses `Alt` because a TUI runs inside a terminal that reserves `Shift`+drag — see the Amendment above.)
3. **Keyboard column-select**: `Ctrl+Shift+Alt+Arrow` and `Ctrl+Shift+Alt+PgUp/PgDn` create or extend a column selection, matching VS Code behavior when the terminal delivers the chord.

Both flows produce a primary caret plus zero or more additional carets, all sharing the multi-caret edit pipeline (single `Document.OpenUpdateScope ()` → one undo step, R5).

Expand Down Expand Up @@ -174,12 +175,11 @@ Also add a tightly-scoped unit test (`Terminal.Gui.Editor.Tests`) for the visual

## Out of Scope

- **Column / box selection** (i.e. `Alt+Drag` producing a *selection per row* the way VS Code's `Shift+Alt+drag` does, rather than carets only). This is the natural follow-up — **the "multi-select" PR**. Per-caret selection in the existing multi-caret pipeline already works; column-extend during drag needs a new code path that extends each caret's selection anchor as the drag widens/narrows. Ship the carets-only flow first.
- **Required of that PR — selection-preservation parity (carried forward from PR #133):** multi-caret `Tab` / `Shift+Tab` block-indent currently calls `ClearAdditionalCaretSelections ()` and collapses the **primary** selection after the edit, whereas the single-caret `IndentSelectedLines` path preserves it. That is cosmetic only while carets are point-only; once this PR makes per-row *selections* a first-class column primitive, surviving a block-indent becomes load-bearing. The multi-select PR **must** restore parity: multi-caret `Tab` / `Shift+Tab` preserves the primary selection **and** every per-caret selection across the block-indent (mirror `IndentSelectedLines`' `SetSelectionRangePreservingDirection` behavior per caret), with a regression test alongside `EditorMultiCaretIndentTests`.
- **Find/replace across multi-caret selections**. Already excluded by multi-caret spec.
- **Reflowing the vertical block under WordWrap toggling**. If the user toggles `WordWrap` while a vertical block is live, the block is dismissed. This spec does not introduce reflow semantics.
- **A ted UI menu / dialog**. The keybindings ship discoverable via help text only.
- **Changing the existing `Ctrl+Click` add-caret-at-click binding to `Alt+Click`** to match VS Code / VS. Tracked as the *Alt+Click alias* open decision below.
- **Sticky Column Selection Mode** (VS Code's modal toggle with menu/status indicator) and multi-cursor paste distribution are separate follow-ups.

## Reference behavior from PR #125

Expand All @@ -197,7 +197,8 @@ Cross-walk of every user-facing behavior against the two reference editors. Wher
|---|---|---|---|
| Add caret above / below | `Ctrl+Alt+Up` / `Ctrl+Alt+Down` (Win/Linux); `Cmd+Opt+Up/Down` (Mac) | `Alt+Shift+Up` / `Alt+Shift+Down` (`Edit.InsertCaretAbove` / `Below`) | **Match VS Code**: `Ctrl+Alt+Up` / `Ctrl+Alt+Down` |
| Add caret at click | `Alt+Click` | `Alt+Click` (multi-cursor placement) | `Ctrl+Click` (existing — see multi-caret spec and *Alt+Click alias* open decision below) |
| Column / box selection by drag | `Shift+Alt + drag` produces a *selection per row* (column select) | `Shift+Alt + drag` produces a column / box selection | **`Alt + drag` produces *carets per row, no selection***. Modifier is `Alt` (not `Shift+Alt`) because the terminal reserves `Shift`+drag (Amendment / DEC-006 / TG#4888); column-select semantics out of scope this iteration |
| Column / box selection by drag | `Shift+Alt + drag` produces a *selection per row* (column select) | `Shift+Alt + drag` produces a column / box selection | **Match behavior**: ranged `Alt + drag` produces a selection per row; zero-width `Alt + drag` produces carets. Modifier differs (D1) because the terminal reserves `Shift`+drag. |
| Keyboard column-select | `Ctrl+Shift+Alt+Arrow` / `PgUp` / `PgDn` | Supported | **Match behavior**: `Ctrl+Shift+Alt+Arrow` / `PgUp` / `PgDn` create/extend a column selection when the terminal delivers the chord (D3 withdrawn). |
| Esc collapses to primary caret | Yes | Yes | Match (*Esc dismisses the vertical block* scenario) |
| Sticky desired column through short lines | Yes | Yes | Match (*Sticky virtual column survives a short intervening line* scenario / *Sticky column through short lines* requirement) |
| Tab inserts at every caret, single undo | Yes | Yes | Match (*Tab inserts at every caret* / *Tab keeps the column aligned* requirements) |
Expand All @@ -207,11 +208,13 @@ Cross-walk of every user-facing behavior against the two reference editors. Wher

### Intentional divergences (and why)

1. **`Alt+drag` produces carets only, not a column selection — and uses `Alt`, not VS Code's `Shift+Alt`.** Two divergences here: (a) VS Code's `Shift+Alt+drag` creates a *selection per row* (typing replaces a column of text); this spec ships only the carets-per-row variant first — the full column-select is the natural follow-up; per-caret selection already works in the pipeline, but extend-during-drag is a new code path. (b) The modifier is `Alt`, not `Shift+Alt`, because the terminal eats `Shift`+drag (see the Amendment; configurable parity tracked by gui-cs/Terminal.Gui#4888). **User-visible consequence**: to "replace" a column, the user must `Alt`-drag, then `Shift+Right`/`Left` to grow each caret's selection, then type. Document this in ted help.

2. **`Ctrl+Click` vs `Alt+Click` for "add caret at click".** Existing multi-caret on `develop` uses `Ctrl+Click`. VS Code and VS use `Alt+Click`. Changing the existing binding is out of scope for this spec — flagged as the *Alt+Click alias* open decision below.

3. **WordWrap toggle dismisses the block.** Both reference editors preserve carets through a wrap toggle. We dismiss because the carets' wrap-row positions are no longer well-defined under the new wrap state and we don't want to silently snap them to surprising offsets. Could revisit post-beta.
| # | Behavior | VS Code | This editor | Why | Category |
|---|---|---|---|---|---|
| **D1** | Start column/box select (mouse) | `Shift+Alt`+drag | **`Alt`+drag** | Windows Terminal / xterm-family terminals reserve `Shift`+drag for terminal-side forced/block selection while an app has mouse mode on, so `Shift+Alt`+drag never reaches the editor; `Alt`+drag is forwarded. | Terminal incompatibility — DEC-006; configurable parity tracked by gui-cs/Terminal.Gui#4888 |
| **D2** | Add caret at click | `Alt`+Click | **`Ctrl`+Click** (existing) | Pre-existing Editor binding; `Alt` is now the column-drag modifier, so a future `Alt`+Click alias needs drag-threshold disambiguation. | Terminal incompatibility knock-on + binding history |
| ~~**D3**~~ | Keyboard column-select (`Ctrl+Shift+Alt+Arrow` / `PgUp`/`PgDn`) | Supported | **WITHDRAWN — not a deviation; in scope, matches VS Code** | TG's Kitty keyboard protocol support can deliver the four-modifier chord. Legacy terminals that do not negotiate it share the same environmental limitation as other advanced chords. | Not a deviation |
| **D4** | Column Selection Mode (sticky toggle, menu/status UI) | Supported | **Out of scope** | A persistent modal input state is larger than the drag/keyboard gestures and not required for column-edit parity. | Scope |
| **D5** | Multi-cursor clipboard paste distribution | `"spread"` by default | **Deferred** | Clipboard distribution is orthogonal to creating/rendering column selections; typing and one-line paste replacement are covered here. | Scope — separate follow-up |

### Behaviors we match deliberately

Expand Down Expand Up @@ -244,4 +247,4 @@ These were open in earlier drafts of this spec and are now resolved.
- This spec rebuilds the user-facing functionality of PR #125 from the tests it shipped; it is not a "fix-forward" of that branch. The intended workflow is: open a new branch from `develop`, port the PR #125 tests verbatim (renaming as needed, **including swapping the key combos to the VS Code chords**), confirm they fail, then write the implementation against the requirements above.
- The visual-line cache fix (see *Cache invalidation on offset shift* requirement and the *Tab twice* scenario) is the most subtle defect the test set exposes. Treat it as the riskiest piece — write the unit test in `Terminal.Gui.Editor.Tests` before touching the cache.
- R5 (single `Document.OpenUpdateScope ()` per multi-caret edit) is non-negotiable. Tab at N carets is one undo step, not N.
- R8: append two lines to `specs/public-api.md` describing the new keybindings. No new public Editor API is introduced by this spec — the existing `AdditionalCaretOffsets` / `HasMultipleCarets` / `ToggleCaretAt` / `ClearAdditionalCarets` surface is sufficient.
- R8: append two lines to `specs/public-api.md` describing the new keybindings. No new public Editor API is introduced by this spec — the existing `AdditionalCaretOffsets` / `HasMultipleCarets` / `ToggleCaretAt` / `ClearAdditionalCarets` surface is sufficient.
4 changes: 4 additions & 0 deletions src/Terminal.Gui.Editor/Editor.Designable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

namespace Terminal.Gui.Editor;

/// <summary>
/// Design-time support for <see cref="Editor" /> so designers can preview representative
/// content with highlighting and line numbers enabled.
/// </summary>
public partial class Editor : IDesignable
{
/// <summary>
Expand Down
Loading
Loading