Skip to main content

rmux_core/hooks/
targets.rs

1use std::collections::{BTreeMap, HashMap};
2
3use rmux_proto::{PaneTarget, RmuxError, SessionName, WindowTarget};
4
5use super::{HookBindings, HookStore};
6
7impl HookStore {
8    /// Removes all hooks owned by the given session.
9    pub fn remove_session(&mut self, session_name: &SessionName) -> bool {
10        let mut removed = self.sessions.remove(session_name).is_some();
11        self.window_aliases
12            .retain(|target, _| target.session_name() != session_name);
13        self.pane_aliases
14            .retain(|target, _| target.session_name() != session_name);
15        self.windows.retain(|target, _| {
16            let keep = target.session_name() != session_name;
17            removed |= !keep;
18            keep
19        });
20        self.panes.retain(|target, _| {
21            let keep = target.session_name() != session_name;
22            removed |= !keep;
23            keep
24        });
25        removed
26    }
27
28    /// Removes all hooks owned by the given window and its panes.
29    pub fn remove_window(&mut self, target: &WindowTarget) -> bool {
30        let mut removed = self.windows.remove(target).is_some();
31        self.window_aliases.remove(target);
32        self.pane_aliases.retain(|pane_target, _| {
33            pane_target.session_name() != target.session_name()
34                || pane_target.window_index() != target.window_index()
35        });
36        self.panes.retain(|pane_target, _| {
37            let keep = pane_target.session_name() != target.session_name()
38                || pane_target.window_index() != target.window_index();
39            removed |= !keep;
40            keep
41        });
42        removed
43    }
44
45    /// Swaps window and pane hooks between two winlink slots.
46    pub fn swap_window_hooks(&mut self, source: &WindowTarget, target: &WindowTarget) {
47        if source == target {
48            return;
49        }
50
51        let source_window = self.windows.remove(source);
52        let target_window = self.windows.remove(target);
53        if let Some(bindings) = source_window {
54            self.windows.insert(target.clone(), bindings);
55        }
56        if let Some(bindings) = target_window {
57            self.windows.insert(source.clone(), bindings);
58        }
59
60        let source_panes = remove_window_pane_hooks(&mut self.panes, source);
61        let target_panes = remove_window_pane_hooks(&mut self.panes, target);
62        self.panes
63            .extend(rekey_pane_hooks(source_panes, source, target));
64        self.panes
65            .extend(rekey_pane_hooks(target_panes, target, source));
66    }
67
68    /// Moves window and pane hooks from one winlink slot to another.
69    pub fn move_window_hooks(&mut self, source: &WindowTarget, target: &WindowTarget) {
70        if source == target {
71            return;
72        }
73
74        let source_window = self.windows.remove(source);
75        let _ = self.windows.remove(target);
76        if let Some(bindings) = source_window {
77            self.windows.insert(target.clone(), bindings);
78        }
79
80        let source_panes = remove_window_pane_hooks(&mut self.panes, source);
81        let _ = remove_window_pane_hooks(&mut self.panes, target);
82        self.panes
83            .extend(rekey_pane_hooks(source_panes, source, target));
84    }
85
86    /// Removes all hooks owned by the given pane.
87    pub fn remove_pane(&mut self, target: &PaneTarget) -> bool {
88        self.pane_aliases.remove(target);
89        self.panes.remove(target).is_some()
90    }
91
92    /// Rekeys window and pane hooks after a session window reindex.
93    pub fn remap_session_window_indices(
94        &mut self,
95        session_name: &SessionName,
96        index_map: &BTreeMap<u32, u32>,
97    ) -> Result<(), RmuxError> {
98        let mut remapped_windows = HashMap::with_capacity(self.windows.len());
99        for (target, bindings) in &self.windows {
100            let next_target = remapped_window_target(target, session_name, index_map);
101            if remapped_windows
102                .insert(next_target.clone(), bindings.clone())
103                .is_some()
104            {
105                return Err(RmuxError::Server(format!(
106                    "hooks already exist for {next_target}"
107                )));
108            }
109        }
110
111        let mut remapped_panes = HashMap::with_capacity(self.panes.len());
112        for (target, bindings) in &self.panes {
113            let next_target = remapped_pane_target(target, session_name, index_map);
114            if remapped_panes
115                .insert(next_target.clone(), bindings.clone())
116                .is_some()
117            {
118                return Err(RmuxError::Server(format!(
119                    "hooks already exist for {next_target}"
120                )));
121            }
122        }
123
124        self.windows = remapped_windows;
125        self.panes = remapped_panes;
126        Ok(())
127    }
128
129    /// Rekeys all hooks owned by the given session.
130    pub fn rename_session(
131        &mut self,
132        session_name: &SessionName,
133        new_name: SessionName,
134    ) -> Result<(), RmuxError> {
135        let mut renamed_sessions = HashMap::with_capacity(self.sessions.len());
136        for (name, bindings) in &self.sessions {
137            let next_name = if name == session_name {
138                new_name.clone()
139            } else {
140                name.clone()
141            };
142            if renamed_sessions
143                .insert(next_name.clone(), bindings.clone())
144                .is_some()
145            {
146                return Err(RmuxError::Server(format!(
147                    "hooks already exist for session {next_name}"
148                )));
149            }
150        }
151
152        let mut renamed_windows = HashMap::with_capacity(self.windows.len());
153        for (target, bindings) in &self.windows {
154            let next_target = if target.session_name() == session_name {
155                WindowTarget::with_window(new_name.clone(), target.window_index())
156            } else {
157                target.clone()
158            };
159            if renamed_windows
160                .insert(next_target.clone(), bindings.clone())
161                .is_some()
162            {
163                return Err(RmuxError::Server(format!(
164                    "hooks already exist for {next_target}"
165                )));
166            }
167        }
168
169        let mut renamed_panes = HashMap::with_capacity(self.panes.len());
170        for (target, bindings) in &self.panes {
171            let next_target = if target.session_name() == session_name {
172                PaneTarget::with_window(
173                    new_name.clone(),
174                    target.window_index(),
175                    target.pane_index(),
176                )
177            } else {
178                target.clone()
179            };
180            if renamed_panes
181                .insert(next_target.clone(), bindings.clone())
182                .is_some()
183            {
184                return Err(RmuxError::Server(format!(
185                    "hooks already exist for {next_target}"
186                )));
187            }
188        }
189
190        self.sessions = renamed_sessions;
191        self.windows = renamed_windows;
192        self.panes = renamed_panes;
193        Ok(())
194    }
195}
196
197fn remove_window_pane_hooks(
198    panes: &mut HashMap<PaneTarget, HookBindings>,
199    window: &WindowTarget,
200) -> Vec<(PaneTarget, HookBindings)> {
201    let pane_targets = panes
202        .keys()
203        .filter(|pane_target| {
204            pane_target.session_name() == window.session_name()
205                && pane_target.window_index() == window.window_index()
206        })
207        .cloned()
208        .collect::<Vec<_>>();
209    pane_targets
210        .into_iter()
211        .filter_map(|pane_target| {
212            panes
213                .remove(&pane_target)
214                .map(|bindings| (pane_target, bindings))
215        })
216        .collect()
217}
218
219fn rekey_pane_hooks(
220    panes: Vec<(PaneTarget, HookBindings)>,
221    source: &WindowTarget,
222    target: &WindowTarget,
223) -> Vec<(PaneTarget, HookBindings)> {
224    panes
225        .into_iter()
226        .map(move |(pane_target, bindings)| {
227            debug_assert_eq!(pane_target.session_name(), source.session_name());
228            debug_assert_eq!(pane_target.window_index(), source.window_index());
229            (
230                PaneTarget::with_window(
231                    target.session_name().clone(),
232                    target.window_index(),
233                    pane_target.pane_index(),
234                ),
235                bindings,
236            )
237        })
238        .collect()
239}
240
241fn remapped_window_target(
242    target: &WindowTarget,
243    session_name: &SessionName,
244    index_map: &BTreeMap<u32, u32>,
245) -> WindowTarget {
246    if target.session_name() != session_name {
247        return target.clone();
248    }
249    index_map.get(&target.window_index()).copied().map_or_else(
250        || target.clone(),
251        |window_index| WindowTarget::with_window(session_name.clone(), window_index),
252    )
253}
254
255fn remapped_pane_target(
256    target: &PaneTarget,
257    session_name: &SessionName,
258    index_map: &BTreeMap<u32, u32>,
259) -> PaneTarget {
260    if target.session_name() != session_name {
261        return target.clone();
262    }
263    index_map.get(&target.window_index()).copied().map_or_else(
264        || target.clone(),
265        |window_index| {
266            PaneTarget::with_window(session_name.clone(), window_index, target.pane_index())
267        },
268    )
269}