This document covers the Tab and Pane system in Windows Terminal: the binary tree pane structure, split operations, focus navigation, zoom, detach/attach, and broadcast input. The system enables multiple terminal instances to be displayed within a single window through tabs and hierarchically organized split-panes.
For information about the Terminal Page that hosts these tabs, see page 4.1.
The tab and pane system is built around three primary abstractions:
| Class / Interface | File | Role |
|---|---|---|
Tab | src/cascadia/TerminalApp/Tab.h18 | Manages a single tab entry in the TabView, owns the root Pane |
Pane | src/cascadia/TerminalApp/Pane.h62 | Binary tree node — either a leaf holding IPaneContent, or a parent with two children |
IPaneContent | TerminalApp.idl | WinRT interface implemented by TerminalPaneContent and SettingsPaneContent; provides the XAML element that fills a leaf pane |
The Tab WinRT runtime class is declared in src/cascadia/TerminalApp/Tab.idl8-30 and implemented by struct Tab : TabT<Tab> in src/cascadia/TerminalApp/Tab.h18
Runtime Object Hierarchy
Sources:
Tab (src/cascadia/TerminalApp/Tab.h18) is the C++ implementation of the WinRT Tab runtime class. It manages a single tab and its pane hierarchy.
Key private members
| Member | Type | Purpose |
|---|---|---|
_rootPane | std::shared_ptr<Pane> | Root of the pane binary tree |
_activePane | std::shared_ptr<Pane> | Currently focused leaf pane |
_zoomedPane | std::shared_ptr<Pane> | Pane currently zoomed (or nullptr) |
_mruPanes | std::vector<uint32_t> | Most-recently-used pane IDs, most recent first |
_nextPaneId | uint32_t | Monotonically increasing ID counter for new leaf panes |
_contentEvents | unordered_map<uint32_t, ContentEventTokens> | Event revokers keyed by pane ID |
Key public methods
| Method | Purpose |
|---|---|
Tab(shared_ptr<Pane>) | Constructor; assigns IDs, finds active pane, calls _Setup() |
Initialize() | Attaches event handlers to all panes and their content |
SplitPane(SplitDirection, float, shared_ptr<Pane>) | Splits the active pane |
NavigateFocus(FocusDirection) | Moves focus between panes |
ResizePane(ResizeDirection) | Moves the separator between panes |
SwapPane(FocusDirection) | Swaps active pane with a neighbor |
FocusPane(uint32_t) | Focuses pane by ID |
DetachPane() / AttachPane(shared_ptr<Pane>) | Remove or add a pane subtree |
ToggleSplitOrientation() | Flips split axis of parent pane |
ToggleZoom() | Enters or exits zoom on active pane |
Sources:
Tab Constructor and Initialization
The Tab constructor (src/cascadia/TerminalApp/Tab.cpp32-71) performs:
_rootPane to the provided pane_rootPane->WalkTree() to assign IDs to leaf panes_activePane by finding the pane with _lastActive set_mruPanes vector_Setup() to initialize UI elementsThe Initialize() method (src/cascadia/TerminalApp/Tab.cpp289-301) walks the tree again to attach event handlers to each pane and its content.
Tab Shutdown Sequence
Sources:
Pane (src/cascadia/TerminalApp/Pane.h62) is a node in a binary tree. Each instance is either a leaf (holds an IPaneContent) or a parent (splits space between two child Pane instances). Pane is not a WinRT class; it is a plain C++ shared_ptr-managed object.
Leaf vs. Parent Pane
Core Pane Members
| Member | Type | Leaf / Parent | Purpose |
|---|---|---|---|
_content | IPaneContent | Leaf only | The hosted terminal or settings UI |
_firstChild | shared_ptr<Pane> | Parent only | First (top/left) child |
_secondChild | shared_ptr<Pane> | Parent only | Second (bottom/right) child |
_splitState | SplitState | Parent only | None, Horizontal, or Vertical |
_desiredSplitPosition | float | Parent only | Split ratio, range 0.0–1.0 |
_id | optional<uint32_t> | Leaf only | Pane ID assigned by Tab |
_lastActive | bool | Both | Whether this pane holds focus |
_root | Grid | Both | XAML container |
_borderFirst | Border | Both | Surrounds _firstChild (or content) |
_borderSecond | Border | Parent only | Surrounds _secondChild |
_borders | Borders (flags) | Both | Which border edges to draw |
_broadcastEnabled | bool | Leaf only | Whether broadcast input is active |
Sources:
IPaneContent is the WinRT interface that all leaf pane content must implement. Concrete implementations include:
| Implementation | Content |
|---|---|
TerminalPaneContent | A live terminal session backed by TermControl |
SettingsPaneContent | The settings editor page |
The interface exposes events (TitleChanged, TabColorChanged, BellRequested, ConnectionStateChanged, ReadOnlyChanged, FocusRequested, TaskbarProgressChanged, CloseRequested) and methods (GetRoot(), GetNewTerminalArgs(), Focus(), Title()). Tab::_AttachEventHandlersToContent() (src/cascadia/TerminalApp/Tab.cpp1041-1178) subscribes to all of these.
Sources:
Example Tree Structure After Splits
This tree represents:
Sources:
Leaf Pane Constructor (src/cascadia/TerminalApp/Pane.cpp30-52)
Pane(IPaneContent content, bool lastFocused):
_setPaneContent() to store _content and revoke any previous content handlers_borderFirst.Child() to content.GetRoot()GotFocus / LostFocus handlers on the XAML root_firstChild and _secondChild remain nullptrParent Pane Constructor (src/cascadia/TerminalApp/Pane.cpp54-87)
Pane(shared_ptr<Pane> first, shared_ptr<Pane> second, SplitState splitState, float splitPosition, bool lastFocused):
_firstChild, _secondChild, _splitState, _desiredSplitPosition_CreateRowColDefinitions() to create XAML RowDefinition / ColumnDefinition entries_ApplySplitDefinitions() to assign Grid.Row / Grid.Column to the borders_SetupChildCloseHandlers() to forward child Closed eventsSources:
WalkTree (src/cascadia/TerminalApp/Pane.h170-202) is a key template that enables ad-hoc depth-first traversal of the pane tree. It accepts any callable f(shared_ptr<Pane>):
f returns void: called on every node in pre-order (self → first → second).f returns a value with operator bool: iteration stops and that value is returned when f returns truthy.Nearly every multi-pane operation in Tab uses WalkTree:
| Operation | Usage |
|---|---|
| Assign IDs to leaves | Tab constructor |
| Attach event handlers | Tab::Initialize(), Tab::SplitPane() |
| Update settings | Tab::UpdateSettings() |
| Collect taskbar states | Tab::GetCombinedTaskbarState() |
| Enable broadcast | Tab::ToggleBroadcastInput() |
| Shutdown all content | Tab::Shutdown() |
Sources:
Split operations convert a leaf pane into a parent pane with two children. The SplitDirection enum determines where the new pane appears:
| SplitDirection | SplitState Created | First Child | Second Child | Split Position Meaning |
|---|---|---|---|---|
Left | Vertical | New pane | Original | Distance from left |
Right | Vertical | Original | New pane | Distance from left |
Up | Horizontal | New pane | Original | Distance from top |
Down | Horizontal | Original | New pane | Distance from top |
Sources:
Tab-Level Split Entry Point
The Tab::SplitPane() method (src/cascadia/TerminalApp/Tab.cpp616-668) performs:
WalkTree() to attach event handlers_nextPaneId++_activePane->Split() to perform the split_activePane to point to the original pane_UpdateActivePane(newPane) to shift focusPane-Level Split Logic
Pane::Split() (src/cascadia/TerminalApp/Pane.cpp1272-1313) resolves the direction and delegates to _Split():
Pane::Split / _Split call chain
Leaf to Parent Conversion (src/cascadia/TerminalApp/Pane.cpp1330-1411):
_takePaneContent() extracts _content and revokes its handlersPane leaf objects are created — one for the old content, one for newPanethis is converted in-place to a parent: _firstChild and _secondChild are set, _content is cleared_CreateRowColDefinitions() populates Grid rows or columns using _desiredSplitPosition_ApplySplitDefinitions() sets Grid.Row / Grid.Column on _borderFirst and _borderSecondSources:
The _CreateRowColDefinitions() method (src/cascadia/TerminalApp/Pane.cpp1519-1577) sets up the Grid layout:
For Horizontal Split:
_root.RowDefinitions().Clear();
auto firstRow = RowDefinition();
firstRow.Height(GridLengthHelper::FromValueAndType(
_desiredSplitPosition, GridUnitType::Star));
auto secondRow = RowDefinition();
secondRow.Height(GridLengthHelper::FromValueAndType(
1.0 - _desiredSplitPosition, GridUnitType::Star));
For Vertical Split:
_root.ColumnDefinitions().Clear();
auto firstCol = ColumnDefinition();
firstCol.Width(GridLengthHelper::FromValueAndType(
_desiredSplitPosition, GridUnitType::Star));
auto secondCol = ColumnDefinition();
secondCol.Width(GridLengthHelper::FromValueAndType(
1.0 - _desiredSplitPosition, GridUnitType::Star));
Sources:
The Tab::NavigateFocus() method (src/cascadia/TerminalApp/Tab.cpp865-887) delegates to Pane::NavigateDirection():
| FocusDirection | Behavior | Implementation |
|---|---|---|
Up, Down, Left, Right | Find visually adjacent pane | Geometric adjacency check |
NextInOrder, PreviousInOrder | Tree traversal order | Pre-order DFS |
First | First leaf pane | Find first leaf |
Previous | Previous in MRU list | Lookup in _mruPanes |
Parent | Parent of source pane | _FindParentOfPane() |
Child | Child of source pane | Navigate to child, prefer previous path |
Sources:
High-Level Navigation Flow
Sources:
For directional navigation (Up/Down/Left/Right), the algorithm uses _FindPaneAndNeighbor() (src/cascadia/TerminalApp/Pane.cpp892-943) which:
_FindNeighborForPane() when source is foundThe _FindNeighborForPane() method (src/cascadia/TerminalApp/Pane.cpp848-880) checks:
_IsAdjacent()PanePoint Structure
The PanePoint (src/cascadia/TerminalApp/Pane.h352-358) represents a pane's position and size in normalized coordinates (0.0-1.0).
Adjacency Check Example
The _IsAdjacent() method (src/cascadia/TerminalApp/Pane.cpp734-794) checks if two panes share a border:
For FocusDirection::Right:
Sources:
NextPane and PreviousPane
NextPane() (src/cascadia/TerminalApp/Pane.cpp458-513):
WalkTree() to traverse all nodes in pre-orderPreviousPane() (src/cascadia/TerminalApp/Pane.cpp521-560) mirrors this but tracks the most recently seen leaf before the target, returning it when the target is found. If the target is the first leaf, it wraps to the last.
Sources:
FocusDirection value | Algorithm | Key method |
|---|---|---|
Left / Right / Up / Down | Geometric adjacency in normalized coordinate space | _FindPaneAndNeighbor() → _IsAdjacent() |
NextInOrder | Pre-order DFS, next leaf | NextPane() |
PreviousInOrder | Pre-order DFS, previous leaf | PreviousPane() |
Previous | MRU list lookup | FindPane(_mruPanes[1]) |
First | First leaf in tree | WalkTree() |
Parent | Walk up to containing pane | _FindParentOfPane() |
Child | Walk down to first (or cached) child | _parentChildPath weak_ptr |
Sources:
The Tab::ResizePane() method (src/cascadia/TerminalApp/Tab.cpp848-855) delegates to Pane::ResizePane():
DirectionMatchesSplit Helper
The DirectionMatchesSplit() template (src/cascadia/TerminalApp/Pane.h332-350) checks if a resize direction matches the split type:
| SplitState | Matching Directions |
|---|---|
Horizontal | Up, Down |
Vertical | Left, Right |
None | None (returns false) |
Resize Implementation
The _Resize() method (src/cascadia/TerminalApp/Pane.cpp246-274) adjusts the split position:
DirectionMatchesSplit() - returns false if mismatch_desiredSplitPosition by 0.05 (5%)_ClampSplitPosition() to prevent invalid sizes_CreateRowColDefinitions() to update layoutSources:
The SwapPane() method (src/cascadia/TerminalApp/Tab.cpp897-920) exchanges two panes' positions:
The swap operation (src/cascadia/TerminalApp/Pane.cpp585-724):
_FindParentOfPane()_firstChild and _secondChildSources:
The zoom feature (src/cascadia/TerminalApp/Tab.cpp1606-1682) temporarily maximizes a pane:
Zoom State Management
| Tab Member | Purpose |
|---|---|
_zoomedPane | Stores the currently zoomed pane (or nullptr) |
Zoom Methods:
EnterZoom() - Calls _rootPane->Maximize(_activePane)ExitZoom() - Calls _rootPane->Restore(_zoomedPane)ToggleZoom() - Toggles between zoomed and normal stateUpdateZoom(newFocus) - Updates zoom when focus changesThe Pane::Maximize() method (src/cascadia/TerminalApp/Pane.cpp2181-2212) walks the tree and collapses all panes except the target. The Pane::Restore() method (src/cascadia/TerminalApp/Pane.cpp2214-2245) reverses this operation.
Sources:
Detaching a Pane
The Tab::DetachPane() method (src/cascadia/TerminalApp/Tab.cpp677-698) removes the active pane:
_rootPane == _activePane, calls DetachRoot() to close the tab_rootPane->DetachPane(_activePane)_activePane to remaining paneThe Pane::DetachPane() method (src/cascadia/TerminalApp/Pane.cpp1939-2036) removes a child:
Detached event on removed paneAttaching a Pane
The Tab::AttachPane() method (src/cascadia/TerminalApp/Tab.cpp734-776) adds a pane:
_activePane->AttachPane(pane, Automatic)_activePane and focuses new contentSources:
The Tab::BuildStartupActions() method (src/cascadia/TerminalApp/Tab.cpp529-604) serializes the tab state into executable actions:
Pane-Level Serialization
The Pane::BuildStartupActions() method (src/cascadia/TerminalApp/Pane.cpp118-233) recursively builds actions:
For leaf panes:
BuildStartupKind::MovePane: creates a SplitPane actionFor parent panes:
BuildStartupActions() on both childrenSplitPane action for the second childMoveFocus actions to navigate and execute child actionsAction Generation Example
For a tab with two panes split vertically:
1. NewTab { profile: "PowerShell" }
2. SplitPane { direction: Right, size: 0.5, profile: "Ubuntu" }
Sources:
The Tab class maintains event connections through the _contentEvents map (src/cascadia/TerminalApp/Tab.h176-193):
ContentEventTokens Structure
Event Registration Flow
The _AttachEventHandlersToContent() method (src/cascadia/TerminalApp/Tab.cpp1041-1178) registers handlers:
| Event | Handler Action |
|---|---|
TitleChanged | Calls UpdateTitle() (throttled 200ms) |
TabColorChanged | Calls _RecalculateAndApplyTabColor() |
TaskbarProgressChanged | Calls _UpdateProgressState() (throttled 200ms) |
ConnectionStateChanged | Calls _UpdateConnectionClosedState() |
ReadOnlyChanged | Calls _RecalculateAndApplyReadOnly() |
BellRequested | Shows bell indicator, raises TabRaiseVisualBell |
FocusRequested | Focuses the content if tab is focused |
Sources:
The _AttachEventHandlersToPane() method (src/cascadia/TerminalApp/Tab.cpp1347-1416) registers pane-level events:
Sources:
The _mruPanes vector (src/cascadia/TerminalApp/Tab.h197) tracks most-recently-used panes:
Update Logic in _UpdateActivePane()
This enables FocusDirection::Previous navigation to return to the last focused pane.
Sources:
Each pane maintains visual borders that adapt based on its position in the tree:
Border Tracking
The _borders member (src/cascadia/TerminalApp/Pane.h259) is a bitmask of Borders enum:
Border Update Algorithm
The _UpdateBorders() method (src/cascadia/TerminalApp/Pane.cpp1694-1740) determines which borders to show:
Border Color Computation
The _ComputeBorderColor() method (src/cascadia/TerminalApp/Pane.cpp1742-1762) selects the border brush:
| Condition | Border Brush |
|---|---|
_lastActive (focused) | _themeResources.focusedBorderBrush |
_broadcastEnabled | _themeResources.broadcastBorderBrush |
| Otherwise | _themeResources.unfocusedBorderBrush |
Sources:
XAML Structure
The Grid uses RowDefinitions or ColumnDefinitions based on _splitState:
_desiredSplitPosition_desiredSplitPositionSources:
The broadcast input feature (src/cascadia/TerminalApp/Tab.cpp1796-1813) sends input to all leaf panes:
Enabling Broadcast
When enabled, the tab registers KeySent, CharSent, and StringSent handlers for each terminal control that broadcast input to all other terminals.
Sources:
The snap size feature helps with pane layouts by calculating sizes that align with character cell boundaries.
CalcSnappedDimension Method
The Pane::CalcSnappedDimension() method (src/cascadia/TerminalApp/Pane.cpp2259-2359) recursively calculates snapped dimensions:
_CalcSnappedDimension() to find nearest valid sizeThis enables smooth resizing without partial character cells.
Sources:
The Tab and Pane Management system in Windows Terminal provides a flexible and powerful way to organize multiple terminal instances within a single window. The architecture uses a hierarchical approach with tabs containing panes, and panes potentially containing other panes in a tree structure.
Key characteristics of the system:
This architecture allows Windows Terminal to provide a modern, flexible interface for command-line operations while maintaining compatibility with traditional terminal functionality.
Refresh this wiki
This wiki was recently refreshed. Please wait 4 days to refresh again.