fix(move): match i3 behavior for move-to-mark on split targets

When moving a container to a mark on a workspace or split container,
match i3's con_move_to_target behavior: descend to the focused child
and add the container as a sibling (making it a direct child of the
parent), rather than adding it as a child of the focused split.

This fixes two cases:
- N_WORKSPACE: moving to a mark on a workspace now adds the container
  as a direct child of the workspace
- N_CONTAINER: moving to a mark on a split container now adds the
  container as a sibling of the focused child, not as a nested child

This enables the "move to parent" use case where a container nested
in a split can be reparented to be a direct child of its grandparent.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Vladimir Panteleev 2025-12-27 11:22:00 +00:00
parent b48239588f
commit e58e542c43
No known key found for this signature in database
GPG key ID: 5004F0FAD051576D

View file

@ -566,8 +566,33 @@ static struct cmd_results *cmd_move_container(bool no_auto_back_and_forth,
root_scratchpad_show(container);
}
switch (destination->type) {
case N_WORKSPACE:
container_move_to_workspace(container, destination->sway_workspace);
case N_WORKSPACE: {
struct sway_workspace *dest_ws = destination->sway_workspace;
// Match i3's con_move_to_target behavior:
// - Empty workspace: move directly to workspace
// - Non-empty workspace: descend to focused child, add as sibling
// (i3's _con_move_to_con goes up to parent, making con a sibling)
if (workspace_is_empty(dest_ws)) {
container_move_to_workspace(container, dest_ws);
} else {
struct sway_node *focus =
seat_get_focus_inactive(seat, destination);
if (focus && focus->type == N_CONTAINER) {
struct sway_container *focus_con = focus->sway_container;
// Match i3: add as sibling of focused child, not as child of it
if (container != focus_con &&
!container_has_ancestor(focus_con, container)) {
container_detach(container);
container->pending.width = container->pending.height = 0;
container->width_fraction = container->height_fraction = 0;
container_add_sibling(focus_con, container, 1);
if (container->view) {
ipc_event_window(container, "move");
}
}
}
}
}
break;
case N_OUTPUT: {
struct sway_output *output = destination->sway_output;
@ -579,8 +604,32 @@ static struct cmd_results *cmd_move_container(bool no_auto_back_and_forth,
container_move_to_workspace(container, ws);
}
break;
case N_CONTAINER:
container_move_to_container(container, destination->sway_container);
case N_CONTAINER: {
struct sway_container *dest_con = destination->sway_container;
// Match i3's con_move_to_target behavior:
// If destination is a split (not a leaf), descend to focused child
// and add as sibling of that child
if (!dest_con->view) {
struct sway_node *focus =
seat_get_focus_inactive(seat, destination);
if (focus && focus->type == N_CONTAINER) {
struct sway_container *focus_con = focus->sway_container;
// Add as sibling of focused child
if (container != focus_con &&
!container_has_ancestor(focus_con, container)) {
container_detach(container);
container->pending.width = container->pending.height = 0;
container->width_fraction = container->height_fraction = 0;
container_add_sibling(focus_con, container, 1);
if (container->view) {
ipc_event_window(container, "move");
}
}
}
} else {
container_move_to_container(container, dest_con);
}
}
break;
case N_ROOT:
break;