Skip to main content

rmux_core/session/pane_transfer/
break_window.rs

1use super::super::pane_transfer_shared::resolve_break_destination_index;
2use super::super::target_error::{invalid_pane_target, invalid_window_target};
3use super::super::{BreakPaneOptions, Session, SessionPaneTarget};
4use crate::Window;
5use rmux_proto::RmuxError;
6
7impl Session {
8    /// Breaks one pane out into another window in the same session.
9    pub fn break_pane(
10        &mut self,
11        source: SessionPaneTarget,
12        options: BreakPaneOptions,
13    ) -> Result<u32, RmuxError> {
14        let original_source_window = self
15            .window_at(source.window_index)
16            .ok_or_else(|| invalid_window_target(&self.name, source.window_index))?;
17        if original_source_window.pane(source.pane_index).is_none() {
18            return Err(invalid_pane_target(
19                &self.name,
20                source.window_index,
21                source.pane_index,
22                "pane index does not exist in session",
23            ));
24        }
25        let source_is_single_pane = original_source_window.pane_count() == 1;
26        let (destination_index, source_window_index) = prepare_break_destination(
27            self,
28            source.window_index,
29            options.target_window_index,
30            options.after,
31            options.before,
32        )?;
33
34        if source_is_single_pane {
35            self.window_at_mut(source_window_index)
36                .expect("prepared source window must exist")
37                .renumber_single_pane_to_zero();
38            self.move_window(
39                source_window_index,
40                destination_index,
41                false,
42                !options.detached,
43            )?;
44            if let Some(name) = options.name {
45                self.rename_window(destination_index, name)?;
46            }
47            return Ok(destination_index);
48        }
49
50        let source_size = self
51            .window_at(source_window_index)
52            .expect("prepared source window must exist")
53            .size();
54        let source_window = self
55            .window_at_mut(source_window_index)
56            .expect("source window must exist");
57        source_window.auto_unzoom();
58        let moved_pane = source_window
59            .extract_pane(source.pane_index)
60            .expect("validated source pane must extract");
61        let mut new_window =
62            Window::new_with_initial_pane(source_size, moved_pane.id(), self.allocate_window_id());
63        if let Some(name) = options.name {
64            new_window.set_name(name);
65        }
66        self.insert_existing_window(destination_index, new_window)?;
67        if !options.detached {
68            self.select_window(destination_index)?;
69        }
70
71        Ok(destination_index)
72    }
73}
74
75fn prepare_break_destination(
76    session: &mut Session,
77    source_window_index: u32,
78    target_window_index: Option<u32>,
79    after: bool,
80    before: bool,
81) -> Result<(u32, u32), RmuxError> {
82    if !(after || before) {
83        let destination_index = resolve_break_destination_index(
84            session,
85            target_window_index,
86            Some(source_window_index),
87        )?;
88        return Ok((destination_index, source_window_index));
89    }
90
91    let anchor_index = target_window_index.unwrap_or(session.active_window_index());
92    if target_window_index.is_some() && session.window_at(anchor_index).is_none() {
93        return Err(invalid_window_target(&session.name, anchor_index));
94    }
95    let destination_index = if before {
96        anchor_index
97    } else {
98        anchor_index
99            .checked_add(1)
100            .ok_or_else(|| RmuxError::Server("window index space exhausted".to_owned()))?
101    };
102    shift_windows_up_from(session, destination_index)?;
103    let shifted_source = if source_window_index >= destination_index {
104        source_window_index
105            .checked_add(1)
106            .ok_or_else(|| RmuxError::Server("window index space exhausted".to_owned()))?
107    } else {
108        source_window_index
109    };
110    Ok((destination_index, shifted_source))
111}
112
113fn shift_windows_up_from(session: &mut Session, start_index: u32) -> Result<(), RmuxError> {
114    if session
115        .windows
116        .keys()
117        .next_back()
118        .is_some_and(|window_index| *window_index == u32::MAX)
119    {
120        return Err(RmuxError::Server("window index space exhausted".to_owned()));
121    }
122
123    let shifted_windows = session
124        .windows
125        .range(start_index..)
126        .map(|(window_index, _)| *window_index)
127        .collect::<Vec<_>>();
128    for window_index in shifted_windows.into_iter().rev() {
129        let new_index = window_index
130            .checked_add(1)
131            .ok_or_else(|| RmuxError::Server("window index space exhausted".to_owned()))?;
132        let window = session
133            .windows
134            .remove(&window_index)
135            .expect("shifted window must exist");
136        let flags = session
137            .winlink_alert_flags
138            .remove(&window_index)
139            .unwrap_or_else(crate::AlertFlags::empty);
140        let replaced_window = session.windows.insert(new_index, window);
141        debug_assert!(replaced_window.is_none());
142        let replaced_flags = session.winlink_alert_flags.insert(new_index, flags);
143        debug_assert!(replaced_flags.is_none());
144    }
145    if session.active_window >= start_index {
146        session.active_window = session.active_window.saturating_add(1);
147    }
148    if let Some(last_window) = session.last_window.filter(|index| *index >= start_index) {
149        session.last_window = Some(last_window.saturating_add(1));
150    }
151    Ok(())
152}