Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
447de15
spec: vertical multi-caret (Alt+Up/Down, Alt+Drag) — re-spec of PR #125
claude May 15, 2026
41c4a4a
spec: add VS Code / Visual Studio 2026 comparison and OD-4/OD-5
claude May 15, 2026
f2d11f7
spec: match VS Code keybindings, drop FR-### identifiers
claude May 15, 2026
075f5ad
spec: per-platform default keybindings, no fallback chord
claude May 16, 2026
775ce85
Merge branch 'develop' into claude/spec-pr-125-reimplementation-rVWsC
tig May 16, 2026
dc835c6
feat: vertical multi-caret (Ctrl+Alt+Up/Down, Shift+Alt+Drag)
tig May 16, 2026
c90ea24
fix: column-drag uses Alt (not Shift+Alt) — Windows Terminal conflict
tig May 17, 2026
93e4dc0
fix: Shift+Tab unindents at every caret (Codex P1)
tig May 17, 2026
8e3d73f
Merge branch 'develop' into claude/spec-pr-125-reimplementation-rVWsC
tig May 17, 2026
c286837
Merge branch 'develop' into claude/spec-pr-125-reimplementation-rVWsC
tig May 17, 2026
0845d40
fix: correct multi-caret shift-tab and reverse vertical expansion
Copilot May 17, 2026
ef5a4fa
fix: resolve review follow-up and command binding syntax
Copilot May 17, 2026
ca12b79
fix: keep shift-tab line unindent behavior and retain reverse collapse
Copilot May 17, 2026
9df67c0
docs: wire TG#5318 into the bespoke-Command-id workaround tracking
tig May 17, 2026
29200ed
fix: drop (Command)1001/1002 workaround — use real TG enum members
tig May 17, 2026
a6fb52c
fix: render additional carets reverse-video + blink (not underline)
tig May 17, 2026
7685378
Merge origin/develop into vertical-multi-caret (PR #133)
tig May 17, 2026
64acb01
fix: multi-caret Tab/Shift+Tab block-indents multi-line selections (C…
tig May 17, 2026
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
18 changes: 18 additions & 0 deletions Docs/Help/keyboard-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,24 @@ This page lists all default keyboard shortcuts. All shortcuts are `Command`-boun
| `Tab` | Indent line / indent selected lines |
| `Shift+Tab` | Un-indent line / un-indent selected lines |

## Multi-Caret

| Key | Action |
|---|---|
| `Ctrl+Click` | Toggle an additional caret at the clicked position |
| `Ctrl+Alt+↑` | Add a caret on the line above (at the sticky column) — VS Code parity |
| `Ctrl+Alt+↓` | Add a caret on the line below (at the sticky column) — VS Code parity |
| `Alt`+drag | Build a vertical column of carets from the press row to the drag row (carets only) |
| `Tab` / `Shift+Tab` | Indent / un-indent at every caret (one undo step) |
| `Esc` | Collapse back to the primary caret |

`Ctrl+Alt+↑/↓` is configurable per platform via `Terminal.Gui.Editor.Editor.DefaultKeyBindings`
(see *Remapping shortcuts* below) — there is no separate built-in fallback chord for terminals/WMs
that grab `Ctrl+Alt+arrow`. The column-drag uses `Alt`+drag (not VS Code's `Shift+Alt`): inside
a terminal, `Shift`+drag is the terminal's own forced text-selection and `Alt` makes it a
rectangular block, so `Shift+Alt`+drag would never reach the editor. The mouse modifier is not
yet user-configurable — tracked by [gui-cs/Terminal.Gui#4888](https://github.com/gui-cs/Terminal.Gui/issues/4888).

## Clipboard

| Key | Action |
Expand Down
11 changes: 9 additions & 2 deletions Docs/Help/multi-caret.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ Place multiple carets in the document and type, delete, or press Enter at all of
| Action | Effect |
|---|---|
| **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). |
| **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.

The primary caret (the one controlled by normal navigation keys) is never removed by Ctrl+Click.

## Editing with multiple carets
Expand All @@ -24,7 +29,7 @@ All edits are wrapped in a single `Document.RunUpdate` scope, so **Undo (Ctrl+Z)

## Visual feedback

Additional carets are rendered as inverted-attribute cells by the `MultiCaretRenderer` (an `IBackgroundRenderer`). 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`). 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 @@ -46,4 +51,6 @@ Additional carets are backed by `TextAnchor` instances, so they track insertions

- Selection is not yet per-caret; only the primary caret carries a selection.
- Find/Replace operates on the primary caret only.
- Ctrl+Click is the only gesture for adding carets; column-select (Alt+Shift+Arrow) is planned.
- `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.
2 changes: 1 addition & 1 deletion Docs/Help/selection.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ All edits are wrapped in a single `Document.RunUpdate` scope, so **Undo (Ctrl+Z)

## Visual feedback

Additional carets are rendered as inverted-attribute cells by the `MultiCaretRenderer` (an `IBackgroundRenderer`). 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`). 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 Down
22 changes: 22 additions & 0 deletions specs/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,25 @@ Decisions are recorded here when an open question from the plan is resolved. Eac
**Rationale**: Matches VS Code's default behavior with `editor.wrappingIndent: "none"`. Simplifies implementation — no need to compute or track the original line's indentation level for each wrap segment. Revisit in a future version if users need indented continuation lines.

**Date**: 2026-05-13

---

### DEC-006: Vertical multi-caret keybindings (VS Code keyboard parity; `Alt`+drag mouse modifier)

**Decision**: Add-caret-above/below use the VS Code keyboard chords `Ctrl+Alt+CursorUp` / `Ctrl+Alt+CursorDown`, shipped as a `[ConfigurationProperty]` `PlatformKeyBinding` entry in `Editor.DefaultKeyBindings` with **no** editor-specific fallback chord (a terminal/WM that grabs the chord is handled by user override via `View.ViewKeyBindings`). The two commands are registered via `AddCommand` against the real `Command.InsertCaretAbove` / `Command.InsertCaretBelow` enum members and bound through the same configurable path — not an inline `if` in `OnKeyDownNotHandled`. (Those members were added upstream by [gui-cs/Terminal.Gui#5318](https://github.com/gui-cs/Terminal.Gui/issues/5318) / PR [#5319](https://github.com/gui-cs/Terminal.Gui/pull/5319) and are consumed by pinning `$(TerminalGuiVersion)` to `2.1.1-develop.98`.)

The column-of-carets mouse gesture uses **`Alt` + LeftButton drag**, **not** VS Code's `Shift+Alt`. Windows Terminal — and the xterm family it emulates — reserves `Shift`+drag as the user's *forced* text-selection override while an application has mouse mode enabled, and `Alt` turns that into a *block/rectangular* selection ([MS docs](https://learn.microsoft.com/en-us/windows/terminal/customize-settings/interaction); cf. microsoft/terminal#9608). So `Shift+Alt`+drag is swallowed by the terminal's own rectangular-select and never reaches the editor; `Alt`+drag is forwarded. The mouse modifier is currently **not** user-configurable (unlike the keybindings) — that gap, and restoring optional `Shift+Alt` parity, is tracked upstream by [gui-cs/Terminal.Gui#4888](https://github.com/gui-cs/Terminal.Gui/issues/4888) (*"Extend the configurable `KeyBindings` to `MouseBindings` (and combos)"*), to be prioritized.

**Rationale**: Keyboard parity preserves muscle memory and is fully user-overridable via the TG-standard `[ConfigurationProperty]` + `PlatformKeyBinding` mechanism. For the *mouse* modifier, terminal reality wins over GUI-editor parity: a TUI lives inside a terminal emulator, so a gesture the terminal eats is simply unusable — and unlike a key, the mouse modifier has no config override yet. `Alt`+drag is terminal-safe today; full configurable parity follows once TG#4888 lands. **Command-enum debt — RESOLVED 2026-05-17:** the two commands were *temporarily* registered as `(Command) 1001/1002` casts (a sanctioned short-term workaround per Constitution "This Is TG", filed as the great TG issue [gui-cs/Terminal.Gui#5318](https://github.com/gui-cs/Terminal.Gui/issues/5318)). That issue shipped the real `Command.InsertCaretAbove` / `Command.InsertCaretBelow` members (TG PR [#5319](https://github.com/gui-cs/Terminal.Gui/pull/5319), in `Terminal.Gui 2.1.1-develop.98`); `$(TerminalGuiVersion)` is now pinned to `2.1.1-develop.98`, the magic-int casts and the workaround block in `Editor.Commands.cs` are deleted, and the bindings use the real members. The broader "should *any* view be able to contribute commands without casting ints" design question is parked (deliberately, as a possibly-YAGNI hypothetical) in [gui-cs/Terminal.Gui#5320](https://github.com/gui-cs/Terminal.Gui/issues/5320).

**Date**: 2026-05-16 (mouse-modifier amendment same day, after Windows Terminal validation; Command-enum debt resolved 2026-05-17 — TG#5318/#5319 shipped, pinned `2.1.1-develop.98`)

---

### DEC-007: `ClearAdditionalCarets` stays `public`

**Decision**: `Editor.ClearAdditionalCarets ()` remains `public` (resolves spec Open Decision "ClearAdditionalCarets visibility").

**Rationale**: It is already shipped multi-caret API documented in `specs/public-api.md`, and `Editor` itself is a `src/` consumer (Esc handler, plain-click handler, the `Alt` column-drag reset). R9 requires a `src/`/`examples/` consumer (tests don't count) — that bar is met, so demoting to `internal` would be a gratuitous breaking change to documented surface.

**Date**: 2026-05-16
6 changes: 6 additions & 0 deletions specs/public-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ public class Editor : View
public bool HasMultipleCarets { get; } // multi-caret
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]).

// --- Display ---
public bool ShowLineNumbers { get; set; } // exists
Expand Down Expand Up @@ -108,3 +113,4 @@ public interface IOverlayRenderer
| 2026-05-11 | Caret and selection storage migrated to TextAnchor-backed tracking | caret-anchors |
| 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 |
Loading
Loading