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
21 changes: 21 additions & 0 deletions .claude/rules/event-patterns.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Event Patterns

## When to Use `-ing` vs `-ed` Events

Terminal.Gui exposes paired events — `Accepting`/`Accepted`, `Activating`/`Activated`, `ValueChanging`/`ValueChanged`, etc.

**Rule:** Use `-ed` (past-tense) for side-effects. Use `-ing` (present-progressive) only when you need to inspect or cancel the in-flight operation.

```csharp
// ✅ Correct — fire-and-forget side-effect
button.Accepted += (_, _) => DoTheThing ();

// ✅ Correct — actually cancels
button.Accepting += (_, e) => { if (!CanProceed ()) e.Handled = true; };

// ❌ Wrong — handler ignores EventArgs; use Accepted instead
button.Accepting += (_, _) => DoTheThing ();
```

If the handler body doesn't reference `e` at all (or ignores `e.Handled`, `e.Cancel`, and the candidate value), it belongs on the `-ed` event.

The `-ing` event runs synchronously in the middle of the dispatch path; subscribing when you don't need to cancel adds unnecessary overhead and misleads readers.

## Lambda Parameters

**Replace unused parameters with discards `_`:**
Expand Down
45 changes: 45 additions & 0 deletions Terminal.Gui/ViewBase/View.Command.cs
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,16 @@ private void SetupCommands ()
/// <para>
/// See <see cref="View.RaiseAccepting"/> for more information.
/// </para>
/// <para>
/// <strong>When to use:</strong> Subscribe to <see cref="Accepting"/> only when you need to inspect or cancel the
/// in-flight Accept operation (e.g., set <c>e.Handled = true</c> to prevent the accept). For simple side-effects
/// that don't need to cancel, subscribe to <see cref="Accepted"/> instead — it is lighter-weight and communicates
/// intent more clearly.
/// </para>
/// <para>
/// <strong>Rule of thumb:</strong> If your handler doesn't read or set anything on <see cref="CommandEventArgs"/>
/// (no <c>e.Handled</c>, no inspection of context), use <see cref="Accepted"/>.
/// </para>
/// </remarks>
public event EventHandler<CommandEventArgs>? Accepting;

Expand Down Expand Up @@ -543,6 +553,19 @@ protected virtual void OnAccepted (ICommandContext? ctx) { }
/// <para>
/// See <see cref="RaiseAccepted"/> for more information.
/// </para>
/// <para>
/// <strong>When to use:</strong> Subscribe to <see cref="Accepted"/> for fire-and-forget side-effects — things that
/// happen <em>after</em> the accept has completed and cannot be cancelled. This is the right choice for the vast
/// majority of button-click–style handlers.
/// </para>
/// <para>
/// <strong>Example:</strong>
/// <code>
/// button.Accepted += (_, _) =&gt; DoTheThing (); // correct — side-effect only
/// button.Accepting += (_, e) =&gt; { if (!CanProceed ()) e.Handled = true; }; // correct — cancels
/// button.Accepting += (_, _) =&gt; DoTheThing (); // wrong — use Accepted instead
/// </code>
/// </para>
/// </remarks>
public event EventHandler<CommandEventArgs>? Accepted;

Expand Down Expand Up @@ -843,6 +866,18 @@ private void BubbleActivatedUp (ICommandContext? ctx, bool compositeOnly = false
/// Set CommandEventArgs.Handled to <see langword="true"/> to indicate the event was handled and processing should
/// stop.
/// </summary>
/// <remarks>
/// <para>
/// <strong>When to use:</strong> Subscribe to <see cref="Activating"/> only when you need to inspect or cancel the
/// in-flight Activate operation (e.g., set <c>e.Handled = true</c> to prevent the state change). For simple
/// side-effects that don't need to cancel, subscribe to <see cref="Activated"/> instead — it is lighter-weight and
/// communicates intent more clearly.
/// </para>
/// <para>
/// <strong>Rule of thumb:</strong> If your handler doesn't read or set anything on <see cref="CommandEventArgs"/>
/// (no <c>e.Handled</c>, no inspection of context), use <see cref="Activated"/>.
/// </para>
/// </remarks>
public event EventHandler<CommandEventArgs>? Activating;

/// <summary>
Expand Down Expand Up @@ -913,6 +948,16 @@ protected virtual void OnActivated (ICommandContext? ctx) { }
/// Event raised when the user has performed an action (e.g. <see cref="Command.Activate"/>) causing the
/// View to change state or preparing it for interaction.
/// </summary>
/// <remarks>
/// <para>
/// Unlike <see cref="Activating"/>, this event cannot be cancelled. It is raised after the View has activated.
/// </para>
/// <para>
/// <strong>When to use:</strong> Subscribe to <see cref="Activated"/> for fire-and-forget side-effects — things
/// that happen <em>after</em> the activation has completed and cannot be cancelled. This is the right choice for
/// the vast majority of state-change–reaction handlers.
/// </para>
/// </remarks>
public event EventHandler<EventArgs<ICommandContext?>>? Activated;

#endregion Activate
Expand Down
31 changes: 31 additions & 0 deletions docfx/docs/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,37 @@ Use this decision tree to choose the right pattern:
| Simple notification (no cancel) | `EventHandler` | [Recipe 3](#recipe-3-simple-notification) |
| Property notification (MVVM) | `INotifyPropertyChanged` | [Recipe 4](#recipe-4-mvvm-property-notification) |

## When to Use `-ing` vs `-ed` Events

Terminal.Gui exposes paired events on many surfaces — `Accepting`/`Accepted`, `Activating`/`Activated`, `ValueChanging`/`ValueChanged`, etc. Use this rule to choose:

> **Use `-ed` (past-tense) events for side-effects. Use `-ing` (present-progressive) events only when you actually need to inspect or cancel the in-flight operation.**

If your handler doesn't read or set anything on the `EventArgs` (no `e.Handled`, no `e.Cancel`, no inspection of the candidate value), you want the `-ed` event. The `-ing` event runs synchronously in the middle of the dispatch path and is heavier for both the framework and the reader of your code.

### Concrete Examples

```csharp
// ✅ Correct — fire-and-forget side-effect belongs on the -ed event
button.Accepted += (_, _) => DoTheThing ();

// ✅ Correct — actually needs to cancel, so -ing is right
button.Accepting += (_, e) => { if (!CanProceed ()) e.Handled = true; };

// ❌ Wrong — handler ignores EventArgs; should use Accepted
button.Accepting += (_, _) => DoTheThing ();
```

The same rule applies to every other paired event in the framework:

| Use `-ed` (side-effect) | Use `-ing` (inspect / cancel) |
|-------------------------|-------------------------------|
| `Accepted` | `Accepting` |
| `Activated` | `Activating` |
| `ValueChanged` | `ValueChanging` |
| `TextChanged` | `TextChanging` |
| `TitleChanged` | `TitleChanging` |

## See Also

* [Cancellable Work Pattern](cancellable-work-pattern.md) - Conceptual overview
Expand Down
Loading