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
11 changes: 11 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,17 @@ Text input system based on Rope data structure:
else reads as a competing context. A callback receiving both takes the GPUI one as `cx`
and names the other after what it holds (`trigger`, `state`).

## Pull Request Reviews

Every PR review must present its conclusions in this order:

1. Explain the PR's purpose: the problem it addresses and the scope of its changes.
2. Assess whether the requirement and proposed approach are reasonable.
3. Report concrete findings, followed by validation performed and its limitations.

Do not start a review with findings alone; include the purpose and reasonableness
assessment even when no issues are found.

## Code Style

- Follow naming and organization patterns from existing code
Expand Down
78 changes: 78 additions & 0 deletions crates/base/src/dock/dock_area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -623,11 +623,89 @@ impl DockArea {
let Some(region) = self.placement_of_panel(panel) else {
return;
};
let focused = window.focused(cx);
let had_focus = self
.panel(panel)
.is_some_and(|view| view.focus_handle(cx).contains_focused(window, cx));
// Prefer the same group, then the groups following it in layout order,
// then preceding groups from nearest to farthest. Preserve this order
// before normalization removes an emptied group or collapses its split.
let mut candidates = Vec::new();
if had_focus && let Some(tree) = self.layout(region) {
tree.root().walk(&mut |node| {
if matches!(node.kind(), PaneRef::Tabs { .. }) {
candidates.push(node.id());
}
});
if let Some(ix) = tree
.find_panel_node(panel)
.and_then(|node| candidates.iter().position(|candidate| *candidate == node))
{
candidates = candidates[ix..]
.iter()
.chain(candidates[..ix].iter().rev())
.copied()
.collect();
}
}
let Some(tree) = self.tree_mut(region) else {
return;
};
let result = tree.remove_panel(panel);
self.commit(result, window, cx);

// on_removed may deliberately focus another control (or clear focus).
// Automatic handoff is only the fallback when the callback left it alone.
if !had_focus || window.focused(cx) != focused {
return;
}
let next = candidates
.into_iter()
.find_map(|node| self.focus_target_in_group(node, cx))
.or_else(|| {
[
DockPlacement::Center,
DockPlacement::Left,
DockPlacement::Right,
DockPlacement::Bottom,
]
.into_iter()
.filter(|placement| *placement != region)
.filter(|placement| {
*placement == DockPlacement::Center
|| self
.docks
.get(placement)
.is_some_and(|pane| pane.dock.is_open())
})
.find_map(|placement| {
self.layout(placement)
.and_then(|tree| self.focus_target_in_node(tree.root(), cx))
})
});
if let Some(next) = next {
next.focus_handle(cx).focus(window, cx);
}
}

fn focus_target_in_group(&self, node: NodeId, cx: &App) -> Option<Arc<dyn PanelView>> {
if self.zoomed.is_some_and(|zoom| zoom != Zoomed::Group(node)) {
return None;
}
let group = self.groups.get(&node)?.entity.read(cx);
if group.is_collapsed() {
return None;
}
group.active_panel(cx)
}

fn focus_target_in_node(&self, node: &PaneNode, cx: &App) -> Option<Arc<dyn PanelView>> {
match node.kind() {
PaneRef::Tabs { .. } => self.focus_target_in_group(node.id(), cx),
PaneRef::Split { children, .. } => children
.iter()
.find_map(|child| self.focus_target_in_node(child, cx)),
}
}
}

Expand Down
Loading
Loading