14 KiB
Cancellable Work Pattern (CWP) Analysis Report
Executive Summary
This report analyzes all instances of the Cancellable Work Pattern (CWP) in the Terminal.Gui codebase to assess the potential impact of reversing the calling order from:
- Current: Virtual method first → Event second
- Proposed: Event first → Virtual method second
Background
The CWP pattern currently calls the virtual method (OnXxx) before invoking the event (Xxx). This gives inherited code (via override) priority over external code (via event subscription). Issue #3714 raises concerns about this order, particularly for mouse events where external code cannot prevent events from reaching overridden methods in views.
Analysis Methodology
- Identified all
protected virtual bool OnXxxmethods in Terminal.Gui - Located their corresponding event invocations
- Analyzed the calling context and dependencies
- Assessed impact of order reversal
Detailed Analysis
1. Mouse Events
1.1 OnMouseEvent / MouseEvent
File: Terminal.Gui/ViewBase/View.Mouse.cs:348
Calling Context:
public bool RaiseMouseEvent (MouseEventArgs mouseEvent)
{
if (OnMouseEvent (mouseEvent) || mouseEvent.Handled)
{
return true;
}
MouseEvent?.Invoke (this, mouseEvent);
return mouseEvent.Handled;
}
Current Order: OnMouseEvent → MouseEvent
Impact Analysis:
- HIGH IMPACT: This is the primary mouse event handler
- Problem: Views like
Sliderthat overrideOnMouseEventcannot be prevented from handling mouse events by external code - Dependencies: Many views override
OnMouseEventexpecting priority - Reversing Order Would:
- Allow external code to cancel before view's override processes
- Break existing assumptions in views that expect first access
- Require review of all
OnMouseEventoverrides
1.2 OnMouseClick / MouseClick
File: Terminal.Gui/ViewBase/View.Mouse.cs:515
Calling Context:
protected bool RaiseMouseClickEvent (MouseEventArgs args)
{
if (OnMouseClick (args) || args.Handled)
{
return args.Handled;
}
MouseClick?.Invoke (this, args);
// ...
}
Current Order: OnMouseClick → MouseClick
Impact Analysis:
- HIGH IMPACT: Same issue as
OnMouseEvent - Problem: External code cannot prevent clicks from reaching view's override
- Reversing Order Would:
- Enable external mouse click prevention
- Consistent with
OnMouseEventchanges - Requires coordination with all click handlers
1.3 OnMouseEnter / MouseEnter
File: Terminal.Gui/ViewBase/View.Mouse.cs:125
Calling Context:
internal bool? NewMouseEnterEvent (CancelEventArgs eventArgs)
{
if (OnMouseEnter (eventArgs))
{
return true;
}
MouseEnter?.Invoke (this, eventArgs);
// ...
}
Current Order: OnMouseEnter → MouseEnter
Impact Analysis:
- MEDIUM IMPACT: Less commonly overridden
- Reversing Order Would:
- Allow external code to prevent enter notifications
- Minimal breaking changes expected
1.4 OnMouseWheel / MouseWheel
File: Terminal.Gui/ViewBase/View.Mouse.cs:610
Calling Context:
protected bool RaiseMouseWheelEvent (MouseEventArgs args)
{
if (OnMouseWheel (args) || args.Handled)
{
return args.Handled;
}
MouseWheel?.Invoke (this, args);
// ...
}
Current Order: OnMouseWheel → MouseWheel
Impact Analysis:
- MEDIUM IMPACT: Wheel handling is specific
- Reversing Order Would:
- Enable external scroll prevention
- Useful for modal dialogs or locked views
2. Keyboard Events
2.1 OnKeyDown / KeyDown
File: Terminal.Gui/ViewBase/View.Keyboard.cs:367
Calling Context:
public bool NewKeyDownEvent (Key k)
{
if (OnKeyDown (k) || k.Handled)
{
return true;
}
KeyDown?.Invoke (this, k);
return k.Handled;
}
Current Order: OnKeyDown → KeyDown
Impact Analysis:
- HIGH IMPACT: Core keyboard input handling
- Dependencies: Many views override for input processing
- Reversing Order Would:
- Allow external key interception before view processing
- Could break views expecting first access to keys
- Major behavioral change for input handling
2.2 OnKeyDownNotHandled / KeyDownNotHandled
File: Terminal.Gui/ViewBase/View.Keyboard.cs:399
Current Order: OnKeyDownNotHandled → KeyDownNotHandled
Impact Analysis:
- LOW IMPACT: Secondary handler for unhandled keys
- Reversing Order Would:
- Minor change, already handles unhandled keys
- Low risk
2.3 OnKeyUp / KeyUp
File: Terminal.Gui/ViewBase/View.Keyboard.cs (implementation in NewKeyUpEvent)
Current Order: OnKeyUp → KeyUp
Impact Analysis:
- LOW-MEDIUM IMPACT: Less commonly used than KeyDown
- Reversing Order Would:
- Enable external key-up prevention
- Lower risk than KeyDown
3. Command Events
3.1 OnAccepting / Accepting
File: Terminal.Gui/ViewBase/View.Command.cs:190
Impact Analysis:
- MEDIUM-HIGH IMPACT: Core command pattern
- Used by: CheckBox, RadioGroup, Button, etc.
- Reversing Order Would:
- Allow external cancellation before view logic
- May break views expecting to set state first
3.2 OnSelecting / Selecting
File: Terminal.Gui/ViewBase/View.Command.cs:245
Impact Analysis:
- MEDIUM IMPACT: Selection behavior
- Reversing Order Would:
- Enable external selection control
- Useful for validation scenarios
3.3 OnHandlingHotKey / HandlingHotKey
File: Terminal.Gui/ViewBase/View.Command.cs:291
Impact Analysis:
- LOW-MEDIUM IMPACT: HotKey processing
- Reversing Order Would:
- Allow hotkey override by external code
- Could be useful for dynamic hotkey management
3.4 OnCommandNotBound / CommandNotBound
File: Terminal.Gui/ViewBase/View.Command.cs:93
Impact Analysis:
- LOW IMPACT: Fallback for unmapped commands
- Reversing Order Would:
- Minor impact, handles unbound commands
4. Drawing Events
4.1 Drawing Pipeline Events
Files: Terminal.Gui/ViewBase/View.Drawing.cs
OnDrawingAdornments/DrawingAdornmentsOnClearingViewport/ClearingViewportOnDrawingText/DrawingTextOnDrawingContent/DrawingContentOnDrawingSubViews/DrawingSubViewsOnRenderingLineCanvas/RenderingLineCanvas
Impact Analysis:
- LOW-MEDIUM IMPACT: Rendering customization
- Reversing Order Would:
- Allow external drawing interception
- Useful for debugging/tracing
- Lower risk as drawing is less state-dependent
5. Navigation Events
5.1 OnAdvancingFocus / AdvancingFocus
File: Terminal.Gui/ViewBase/View.Navigation.cs:208
Impact Analysis:
- MEDIUM-HIGH IMPACT: Focus management
- Reversing Order Would:
- Allow external focus control
- Could break focus flow expectations
5.2 OnHasFocusChanging / HasFocusChanging
File: Terminal.Gui/ViewBase/View.Navigation.cs:717
Impact Analysis:
- MEDIUM-HIGH IMPACT: Focus state management
- Reversing Order Would:
- Enable external focus validation
- Useful for preventing focus changes
6. Property Change Events
6.1 OnSchemeNameChanging / SchemeNameChanging
File: Terminal.Gui/ViewBase/View.Drawing.Scheme.cs:51
Impact Analysis:
- LOW-MEDIUM IMPACT: Scheme change validation
- Uses:
CWPPropertyHelper - Reversing Order Would:
- Allow external scheme validation
- Low risk
6.2 OnGettingScheme / GettingScheme
File: Terminal.Gui/ViewBase/View.Drawing.Scheme.cs:170
Impact Analysis:
- LOW-MEDIUM IMPACT: Scheme resolution
- Reversing Order Would:
- Allow external scheme override
- Useful for theming
6.3 OnSettingScheme / SettingScheme
File: Terminal.Gui/ViewBase/View.Drawing.Scheme.cs:241
Impact Analysis:
- LOW-MEDIUM IMPACT: Scheme application
- Reversing Order Would:
- Enable external scheme interception
- Low risk
6.4 OnVisibleChanging / VisibleChanging
File: Terminal.Gui/ViewBase/View.cs:382
Impact Analysis:
- MEDIUM IMPACT: Visibility state management
- Reversing Order Would:
- Allow external visibility control
- Could affect layout calculations
6.5 OnOrientationChanging (commented out)
File: Terminal.Gui/ViewBase/Orientation/OrientationHelper.cs:166
Impact Analysis:
- N/A: Currently commented out
- Note: OrientationHelper uses callback pattern
7. View-Specific Events
7.1 OnCellActivated (TableView)
File: Terminal.Gui/Views/TableView/TableView.cs:1277
Impact Analysis:
- LOW IMPACT: TableView-specific
- Reversing Order Would:
- Allow external cell activation control
- Isolated to TableView
7.2 OnPositionChanging (ScrollBar, ScrollSlider)
Files:
Terminal.Gui/Views/ScrollBar/ScrollBar.cs:375Terminal.Gui/Views/ScrollBar/ScrollSlider.cs:245Impact Analysis:- LOW-MEDIUM IMPACT: Scrollbar-specific
- Reversing Order Would:
- Enable external scroll validation
- Useful for scroll constraints
7.3 OnCheckedStateChanging (CheckBox)
File: Terminal.Gui/Views/CheckBox.cs:187
Impact Analysis:
- LOW-MEDIUM IMPACT: CheckBox state management
- Reversing Order Would:
- Allow external state validation
- Useful for validation logic
8. Other Events
8.1 OnBorderStyleChanged
File: Terminal.Gui/ViewBase/View.Adornments.cs:162
Impact Analysis:
- LOW IMPACT: Border styling
- Reversing Order Would:
- Enable external border control
- Minimal risk
8.2 OnMouseIsHeldDownTick (MouseHeldDown)
File: Terminal.Gui/ViewBase/MouseHeldDown.cs:71
Impact Analysis:
- LOW IMPACT: Continuous mouse press
- Reversing Order Would:
- Allow external hold-down control
- Minimal risk
8.3 OnGettingAttributeForRole
File: Terminal.Gui/ViewBase/View.Drawing.Attribute.cs:78
Impact Analysis:
- LOW IMPACT: Attribute resolution
- Reversing Order Would:
- Enable external attribute override
- Useful for accessibility
Summary Statistics
Total CWP Implementations Found: 33
By Impact Level:
- HIGH IMPACT: 3 (Mouse/Keyboard core events)
- MEDIUM-HIGH IMPACT: 4 (Commands, Navigation)
- MEDIUM IMPACT: 8 (Various UI events)
- LOW-MEDIUM IMPACT: 10 (Property changes, view-specific)
- LOW IMPACT: 8 (Specialized/rare events)
Code Dependencies on Current Order
Tests That Validate Order
Located in Tests/UnitTestsParallelizable/View/Orientation/OrientationTests.cs:
[Fact]
public void OrientationChanging_VirtualMethodCalledBeforeEvent()
{
// Test explicitly validates virtual method is called before event
}
[Fact]
public void OrientationChanged_VirtualMethodCalledBeforeEvent()
{
// Test explicitly validates virtual method is called before event
}
Impact: These tests will FAIL if order is reversed - they explicitly test current behavior.
Views With Override Dependencies
Based on code search, many views override these methods and may depend on being called first:
Slider.OnMouseEvent- Core issue from #3714- Various views override
OnKeyDownfor input handling - Command handlers in Button, CheckBox, RadioGroup
- Custom drawing in various views
Recommendations
Option 1: Reverse Order Globally
Pros:
- Solves #3714 completely
- Consistent pattern across all events
- External code gets priority (looser coupling)
Cons:
- MAJOR BREAKING CHANGE
- Requires updating all views that override CWP methods
- Extensive testing required
- May break user code
Effort: HIGH (4-6 weeks) Risk: HIGH
Option 2: Add "Before" Events
Pros:
- Non-breaking
- Explicit control for when needed
- Gradual migration path
Cons:
- More API surface
- Complexity in naming/documentation
- Two patterns coexist
Effort: MEDIUM (2-3 weeks) Risk: LOW
Option 3: Add IgnoreMouse Property
Pros:
- Minimal change
- Solves immediate #3714 issue
- No breaking changes
Cons:
- Band-aid solution
- Doesn't address root cause
- Only solves mouse issue
Effort: LOW (1 week) Risk: VERY LOW
Option 4: Reverse Order for Mouse Events Only
Pros:
- Solves #3714
- Limited scope reduces risk
- Mouse is primary concern
Cons:
- Inconsistent pattern
- Still breaking for mouse overrides
- Confusion with different orders
Effort: MEDIUM (2 weeks) Risk: MEDIUM
Conclusion
The analysis reveals that reversing CWP order globally would be a significant breaking change affecting 33+ event pairs across the codebase. The highest impact would be on:
- Mouse events (OnMouseEvent, OnMouseClick) - direct issue #3714
- Keyboard events (OnKeyDown) - core input handling
- Command events (OnAccepting, OnSelecting) - state management
- Navigation events (OnAdvancingFocus, OnHasFocusChanging) - focus flow
Recommended Approach:
- Short-term: Option 3 (IgnoreMouse property) or Option 2 (Before events for mouse only)
- Long-term: Consider Option 2 with gradual migration, or accept current design as intentional
The current design prioritizes inheritance over composition, which may be appropriate for a UI framework where tight coupling through inheritance is common. However, it limits external control, which is the root cause of #3714.
Next Steps
- Review this analysis with maintainers
- Decide on approach based on project priorities
- If proceeding with order reversal:
- Create comprehensive test plan
- Update all affected views
- Update documentation
- Provide migration guide
- If proceeding with alternative:
- Implement chosen solution
- Update documentation with rationale
- Add examples for working around current limitation