Skip to content
Draft
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
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.
31 changes: 16 additions & 15 deletions examples/ted/TedApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,33 +142,34 @@ public TedApp (bool readOnly = false, string? configPath = null)
}
};
ShowTabsCheckBox.Value = Editor.ShowTabs ? CheckState.Checked : CheckState.UnChecked;
PreviewCheckBox.ValueChanged += (_, e) =>
{
ToggleMarkdownPreview ();
_previewMarkdownMenuItem.Title = ToggleTitle (e.NewValue == CheckState.Checked, "_Preview Markdown");
};
OverwriteShortcut = new Shortcut (Key.Empty, "INS", null) { MouseHighlightStates = MouseState.None };
LocShortcut = new Shortcut (Key.Empty, FormatLoc (1, 1), null) { MouseHighlightStates = MouseState.None };
LoadStatusSpinner = new SpinnerView
{
Style = new SpinnerStyle.Aesthetic (),
Width = 8,
AutoSpin = false,
Visible = false
};
LoadSpinnerShortcut = new Shortcut
{
CommandView = LoadStatusSpinner,
Title = string.Empty,
MouseHighlightStates = MouseState.None
};
PreviewCheckBox.ValueChanged += (_, e) =>
{
ToggleMarkdownPreview ();
_previewMarkdownMenuItem.Title = ToggleTitle (e.NewValue == CheckState.Checked, "_Preview Markdown");
};

StatusBar statusBar =
new ([
new Shortcut { Title = "Language", CommandView = LanguageShortcut },
new Shortcut { Title = "Theme", CommandView = ThemeDropDown },
LoadSpinnerShortcut = new Shortcut
{
CommandView = LoadStatusSpinner,
Title = string.Empty,
MouseHighlightStates = MouseState.None
},
OverwriteShortcut = new Shortcut (Key.Empty, "INS", null)
{ MouseHighlightStates = MouseState.None },
LocShortcut = new Shortcut (Key.Empty, FormatLoc (1, 1), null)
{ MouseHighlightStates = MouseState.None }
LoadSpinnerShortcut,
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 @@ -36,10 +36,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 @@ -157,5 +156,6 @@ public readonly record struct TextDocumentProgress (
| 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 | Streaming `TextDocument.LoadAsync` / `TextDocument.SaveAsync`, `TextDocumentProgress`, `TextDocument.Encoding`, and delegating `Editor.LoadAsync` / `Editor.SaveAsync` landed | file-io |
| 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
Loading