From 4310fc72a141333d09194a71551346ae4fa7e092 Mon Sep 17 00:00:00 2001 From: aiongg Date: Tue, 20 Oct 2026 15:09:34 +0000 Subject: [PATCH] docking: window.panel.dock to a side never anchors on what moves On the first panel of the last column, the command picked that panel as its own anchor and failed with InvalidPlacement. The anchor is a panel of the last column outside the moving unit; when the unit already is that column, the command does nothing. --- crates/ui/src/docking/command.rs | 12 ++++++++---- crates/ui/src/docking/command_tests.rs | 10 ++++++++++ docs/ui-docking.md | 3 ++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/crates/ui/src/docking/command.rs b/crates/ui/src/docking/command.rs index e377f256..93151e6e 100644 --- a/crates/ui/src/docking/command.rs +++ b/crates/ui/src/docking/command.rs @@ -399,7 +399,11 @@ impl DockCommand

{ } if let Some(side) = side { let to = match last_column(layout, *side) { - Some(anchor) => Target::At { anchor, placement: Placement::Column { after: true } }, + Some(column) => match column.into_iter().find(|p| !members(layout, panel, unit).contains(p)) { + Some(anchor) => Target::At { anchor, placement: Placement::Column { after: true } }, + // What moves already is that dock's last column. + None => return Ok(Vec::new()), + }, None => Target::NewDock { side: *side }, }; let unit = match (unit, &to) { @@ -548,12 +552,12 @@ impl

DockCommand

{ } } -/// The first panel of the last column of the dock on `side`. -fn last_column(layout: &Layout

, side: Side) -> Option

{ +/// The panels of the last column of the dock on `side`, or `None` without such a dock. +fn last_column(layout: &Layout

, side: Side) -> Option> { let path = layout.dock_path(side)?; let node = layout.root.as_ref().and_then(|root| super::dock_model::node_at(root, &path))?; match node { - super::Node::Dock { columns, .. } => columns.last()?.groups.first()?.panels.first().cloned(), + super::Node::Dock { columns, .. } => Some(columns.last()?.groups.iter().flat_map(|g| g.panels.iter().cloned()).collect()), _ => None, } } diff --git a/crates/ui/src/docking/command_tests.rs b/crates/ui/src/docking/command_tests.rs index 1f071169..9397e62a 100644 --- a/crates/ui/src/docking/command_tests.rs +++ b/crates/ui/src/docking/command_tests.rs @@ -409,3 +409,13 @@ fn dock_with_a_side_zone_and_no_anchor_makes_an_outer_edge_frame() { ); rejected(json!({"command": "window.panel.dock", "panel": "color", "zone": "left", "before": null}), DockError::InvalidPlacement); } + +#[test] +fn dock_to_the_side_a_panel_already_leads_the_last_column_of() { + // Layers leads the dock's only column: it moves out to a new column after it. + let (workspace, _) = applied(json!({"command": "window.panel.dock", "panel": "layers", "side": "Start"})); + assert!(matches!(workspace.layout.place(&s("layers")), Some(dock_model::Place::Dock { column: 1, .. }))); + assert!(matches!(workspace.layout.place(&s("channels")), Some(dock_model::Place::Dock { column: 0, .. }))); + // The whole column already is the last one: nothing to do. + assert_eq!(actions(json!({"command": "window.panel.dock", "panel": "layers", "column": true, "side": "Start"})), vec![]); +} diff --git a/docs/ui-docking.md b/docs/ui-docking.md index 76423b13..912a37cc 100644 --- a/docs/ui-docking.md +++ b/docs/ui-docking.md @@ -231,7 +231,8 @@ let closed = command.apply(&mut app.workspace, style.right_to_left, &space, |pan `Placement::for_zone`, so beside a docked panel they make a new column or group. `apply` and `to_actions` take the layout's `right_to_left` for that. - `group` moves the panel's whole group; `column` moves its dock column, or its whole window when - it floats. `side` is `Start` or `End`. + it floats. `side` is `Start` or `End`: what moves becomes a new last column of that dock, or + a new dock; when it already is that dock's last column, the command does nothing. - `window.panel.dock` with `left`, `right`, `top` or `bottom` and no `anchor` or `side` makes a new frame on that outer edge (`Placement::Edge`). A hidden panel is shown first. - `window.panel.float` without a rect floats what it moves at the size a drag floats it at (see