Skip to main content

rmux_core/
target.rs

1use rmux_proto::{PaneTarget, RmuxError, SessionName, Target, WindowTarget};
2
3use crate::{Pane, Session, SessionStore, Window};
4
5impl SessionStore {
6    /// Resolves any exact target to its owning session.
7    pub fn resolve_session(&self, target: &Target) -> Result<&Session, RmuxError> {
8        self.session(target.session_name())
9            .ok_or_else(|| session_not_found(target.session_name()))
10    }
11
12    /// Resolves any exact target to its owning mutable session.
13    pub fn resolve_session_mut(&mut self, target: &Target) -> Result<&mut Session, RmuxError> {
14        self.session_mut(target.session_name())
15            .ok_or_else(|| session_not_found(target.session_name()))
16    }
17
18    /// Resolves any exact target to the addressed session window.
19    pub fn resolve_window(&self, target: &Target) -> Result<&Window, RmuxError> {
20        let session = self.resolve_session(target)?;
21        session
22            .window_at(target_window_index(session, target))
23            .ok_or_else(|| window_not_found(target))
24    }
25
26    /// Resolves any exact target to the addressed mutable session window.
27    pub fn resolve_window_mut(&mut self, target: &Target) -> Result<&mut Window, RmuxError> {
28        let session = self.resolve_session_mut(target)?;
29        let window_index = target_window_index(session, target);
30        session
31            .window_at_mut(window_index)
32            .ok_or_else(|| window_not_found(target))
33    }
34
35    /// Resolves an exact pane target to an immutable pane reference.
36    pub fn resolve_pane(&self, target: &Target) -> Result<&Pane, RmuxError> {
37        match target {
38            Target::Pane(pane_target) => self
39                .resolve_window(target)?
40                .pane(pane_target.pane_index())
41                .ok_or_else(|| pane_not_found(pane_target)),
42            _ => Err(pane_target_required(target)),
43        }
44    }
45
46    /// Resolves an exact pane target to a mutable pane reference.
47    pub fn resolve_pane_mut(&mut self, target: &Target) -> Result<&mut Pane, RmuxError> {
48        match target {
49            Target::Pane(pane_target) => self
50                .resolve_window_mut(target)?
51                .pane_mut(pane_target.pane_index())
52                .ok_or_else(|| pane_not_found(pane_target)),
53            _ => Err(pane_target_required(target)),
54        }
55    }
56
57    /// Resolves a target to the effective pane used by pane-oriented commands.
58    ///
59    /// Session and window targets resolve to the session's active pane. Pane
60    /// targets resolve to the explicitly addressed pane.
61    pub fn resolve_effective_pane(&self, target: &Target) -> Result<&Pane, RmuxError> {
62        match target {
63            Target::Session(_) => self
64                .resolve_session(target)?
65                .active_pane()
66                .ok_or_else(|| active_pane_not_found(target)),
67            Target::Window(_) => self
68                .resolve_window(target)?
69                .active_pane()
70                .ok_or_else(|| active_pane_not_found(target)),
71            Target::Pane(_) => self.resolve_pane(target),
72        }
73    }
74
75    /// Resolves a target to the mutable effective pane used by pane-oriented
76    /// commands.
77    ///
78    /// Session and window targets resolve to the session's active pane. Pane
79    /// targets resolve to the explicitly addressed pane.
80    pub fn resolve_effective_pane_mut(&mut self, target: &Target) -> Result<&mut Pane, RmuxError> {
81        match target {
82            Target::Session(_) => {
83                let session = self.resolve_session_mut(target)?;
84                let active_pane_index = session.active_pane_index();
85                session
86                    .window_mut()
87                    .pane_mut(active_pane_index)
88                    .ok_or_else(|| active_pane_not_found(target))
89            }
90            Target::Window(_) => {
91                let window = self.resolve_window_mut(target)?;
92                let active_pane_index = window.active_pane_index();
93                window
94                    .pane_mut(active_pane_index)
95                    .ok_or_else(|| active_pane_not_found(target))
96            }
97            Target::Pane(_) => self.resolve_pane_mut(target),
98        }
99    }
100}
101
102fn session_not_found(session_name: &SessionName) -> RmuxError {
103    RmuxError::SessionNotFound(session_name.to_string())
104}
105
106fn pane_not_found(target: &PaneTarget) -> RmuxError {
107    RmuxError::invalid_target(target.to_string(), "pane index does not exist in session")
108}
109
110fn window_not_found(target: &Target) -> RmuxError {
111    RmuxError::invalid_target(
112        window_target_value(target),
113        "window index does not exist in session",
114    )
115}
116
117fn pane_target_required(target: &Target) -> RmuxError {
118    RmuxError::invalid_target(target.to_string(), "pane target required")
119}
120
121fn active_pane_not_found(target: &Target) -> RmuxError {
122    RmuxError::invalid_target(target.to_string(), "active pane does not exist in session")
123}
124
125fn window_target_value(target: &Target) -> String {
126    match target {
127        Target::Session(session_name) => session_name.to_string(),
128        Target::Window(target) => target.to_string(),
129        Target::Pane(target) => {
130            WindowTarget::with_window(target.session_name().clone(), target.window_index())
131                .to_string()
132        }
133    }
134}
135
136fn target_window_index(session: &Session, target: &Target) -> u32 {
137    match target {
138        Target::Session(_) => session.active_window_index(),
139        Target::Window(target) => target.window_index(),
140        Target::Pane(target) => target.window_index(),
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use crate::{PaneGeometry, SessionStore};
147    use rmux_proto::{SessionName, Target, TerminalSize};
148
149    fn session_name(value: &str) -> SessionName {
150        SessionName::new(value).expect("valid session name")
151    }
152
153    fn populated_store() -> SessionStore {
154        let mut store = SessionStore::new();
155        store
156            .create_session(
157                session_name("alpha"),
158                TerminalSize {
159                    cols: 120,
160                    rows: 40,
161                },
162            )
163            .expect("session insert succeeds");
164
165        let session_target = Target::parse("alpha").expect("valid session target");
166        store
167            .resolve_session_mut(&session_target)
168            .expect("session exists")
169            .split_active_pane()
170            .expect("split succeeds");
171        store
172    }
173
174    fn store_with_selected_second_pane() -> SessionStore {
175        let mut store = populated_store();
176        let target = Target::parse("alpha").expect("valid session target");
177        store
178            .resolve_session_mut(&target)
179            .expect("session exists")
180            .select_pane(1)
181            .expect("pane 1 exists");
182        store
183    }
184
185    fn multi_window_store() -> SessionStore {
186        let mut store = SessionStore::new();
187        store
188            .create_session(
189                session_name("alpha"),
190                TerminalSize {
191                    cols: 120,
192                    rows: 40,
193                },
194            )
195            .expect("session insert succeeds");
196
197        let session_target = Target::parse("alpha").expect("valid session target");
198        let session = store
199            .resolve_session_mut(&session_target)
200            .expect("session exists");
201        session
202            .insert_window_with_initial_pane(5, TerminalSize { cols: 90, rows: 30 })
203            .expect("window 5 insert succeeds");
204        session
205            .split_pane_in_window(5, 0)
206            .expect("window 5 split succeeds");
207        session
208            .select_pane_in_window(5, 1)
209            .expect("window 5 pane selection succeeds");
210        session
211            .select_window(5)
212            .expect("window 5 selection succeeds");
213        store
214    }
215
216    #[test]
217    fn resolve_session_accepts_session_targets() {
218        let store = populated_store();
219        let target = Target::parse("alpha").expect("valid session target");
220
221        let session = store.resolve_session(&target).expect("session exists");
222
223        assert_eq!(session.name().as_str(), "alpha");
224    }
225
226    #[test]
227    fn resolve_session_accepts_pane_targets() {
228        let store = populated_store();
229        let target = Target::parse("alpha:0.1").expect("valid pane target");
230
231        let session = store.resolve_session(&target).expect("session exists");
232
233        assert_eq!(session.name().as_str(), "alpha");
234    }
235
236    #[test]
237    fn resolve_session_reports_missing_sessions() {
238        let store = SessionStore::new();
239        let target = Target::parse("alpha").expect("valid session target");
240
241        let error = store
242            .resolve_session(&target)
243            .expect_err("session should not exist");
244
245        assert_eq!(error.to_string(), "session not found: alpha");
246    }
247
248    #[test]
249    fn resolve_window_accepts_window_targets() {
250        let store = populated_store();
251        let target = Target::parse("alpha:0").expect("valid window target");
252
253        let window = store.resolve_window(&target).expect("window exists");
254
255        assert_eq!(window.pane_count(), 2);
256    }
257
258    #[test]
259    fn resolve_window_accepts_session_targets() {
260        let store = populated_store();
261        let target = Target::parse("alpha").expect("valid session target");
262
263        let window = store.resolve_window(&target).expect("window exists");
264
265        assert_eq!(window.pane_count(), 2);
266    }
267
268    #[test]
269    fn resolve_window_accepts_pane_targets() {
270        let store = populated_store();
271        let target = Target::parse("alpha:0.1").expect("valid pane target");
272
273        let window = store.resolve_window(&target).expect("window exists");
274
275        assert_eq!(window.pane_count(), 2);
276    }
277
278    #[test]
279    fn resolve_window_accepts_non_zero_window_targets() {
280        let store = multi_window_store();
281        let target = Target::parse("alpha:5").expect("valid window target");
282
283        let window = store.resolve_window(&target).expect("window exists");
284
285        assert_eq!(window.pane_count(), 2);
286    }
287
288    #[test]
289    fn resolve_window_reports_missing_window_after_successful_parse() {
290        let store = populated_store();
291        let target = Target::parse("alpha:5").expect("valid window target");
292
293        let error = store
294            .resolve_window(&target)
295            .expect_err("window should not exist");
296
297        assert_eq!(
298            error.to_string(),
299            "invalid target 'alpha:5': window index does not exist in session"
300        );
301    }
302
303    #[test]
304    fn resolve_window_reports_missing_window_for_pane_targets_with_window_context() {
305        let store = populated_store();
306        let target = Target::parse("alpha:5.0").expect("valid pane target");
307
308        let error = store
309            .resolve_window(&target)
310            .expect_err("window should not exist");
311
312        assert_eq!(
313            error.to_string(),
314            "invalid target 'alpha:5': window index does not exist in session"
315        );
316    }
317
318    #[test]
319    fn resolve_window_mut_returns_a_mutable_window_reference() {
320        let mut store = populated_store();
321        let target = Target::parse("alpha:0").expect("valid window target");
322
323        let window = store
324            .resolve_window_mut(&target)
325            .expect("mutable window exists");
326        let pane_one_position = window.pane_position(1).expect("pane 1 exists");
327        let new_index = window.split_after_position(pane_one_position);
328
329        assert_eq!(new_index, 2);
330        assert_eq!(window.pane_count(), 3);
331    }
332
333    #[test]
334    fn resolve_pane_returns_the_addressed_pane() {
335        let store = populated_store();
336        let target = Target::parse("alpha:0.1").expect("valid pane target");
337
338        let pane = store.resolve_pane(&target).expect("pane exists");
339
340        assert_eq!(pane.index(), 1);
341    }
342
343    #[test]
344    fn resolve_pane_mut_returns_a_mutable_reference() {
345        let mut store = populated_store();
346        let target = Target::parse("alpha:0.1").expect("valid pane target");
347
348        let _ = store
349            .resolve_pane_mut(&target)
350            .expect("pane exists")
351            .target(&session_name("alpha"));
352
353        let pane = store.resolve_pane(&target).expect("pane still exists");
354        assert_eq!(pane.index(), 1);
355    }
356
357    #[test]
358    fn resolve_pane_rejects_non_pane_targets() {
359        let store = populated_store();
360        let target = Target::parse("alpha").expect("valid session target");
361
362        let error = store
363            .resolve_pane(&target)
364            .expect_err("pane resolution requires a pane target");
365
366        assert_eq!(
367            error.to_string(),
368            "invalid target 'alpha': pane target required"
369        );
370    }
371
372    #[test]
373    fn resolve_pane_reports_missing_pane_indices() {
374        let store = populated_store();
375        let target = Target::parse("alpha:0.9").expect("valid pane target");
376
377        let error = store
378            .resolve_pane(&target)
379            .expect_err("pane index should not exist");
380
381        assert_eq!(
382            error.to_string(),
383            "invalid target 'alpha:0.9': pane index does not exist in session"
384        );
385    }
386
387    #[test]
388    fn resolve_pane_reports_missing_sessions_before_missing_panes() {
389        let store = SessionStore::new();
390        let target = Target::parse("alpha:0.0").expect("valid pane target");
391
392        let error = store
393            .resolve_pane(&target)
394            .expect_err("session should not exist");
395
396        assert_eq!(error.to_string(), "session not found: alpha");
397    }
398
399    #[test]
400    fn resolve_effective_pane_maps_session_targets_to_the_active_pane() {
401        let store = store_with_selected_second_pane();
402        let target = Target::parse("alpha").expect("valid session target");
403
404        let pane = store
405            .resolve_effective_pane(&target)
406            .expect("active pane exists");
407
408        assert_eq!(pane.index(), 1);
409    }
410
411    #[test]
412    fn resolve_effective_pane_maps_session_targets_to_the_active_window() {
413        let store = multi_window_store();
414        let target = Target::parse("alpha").expect("valid session target");
415
416        let pane = store
417            .resolve_effective_pane(&target)
418            .expect("active pane exists");
419
420        assert_eq!(pane.index(), 1);
421    }
422
423    #[test]
424    fn resolve_effective_pane_maps_window_targets_to_the_active_pane() {
425        let store = store_with_selected_second_pane();
426        let target = Target::parse("alpha:0").expect("valid window target");
427
428        let pane = store
429            .resolve_effective_pane(&target)
430            .expect("active pane exists");
431
432        assert_eq!(pane.index(), 1);
433    }
434
435    #[test]
436    fn resolve_effective_pane_uses_the_addressed_window_active_pane() {
437        let store = multi_window_store();
438        let target = Target::parse("alpha:5").expect("valid window target");
439
440        let pane = store
441            .resolve_effective_pane(&target)
442            .expect("active pane exists");
443
444        assert_eq!(pane.index(), 1);
445    }
446
447    #[test]
448    fn resolve_effective_pane_keeps_explicit_pane_targets_exact() {
449        let store = store_with_selected_second_pane();
450        let target = Target::parse("alpha:0.0").expect("valid pane target");
451
452        let pane = store
453            .resolve_effective_pane(&target)
454            .expect("addressed pane exists");
455
456        assert_eq!(pane.index(), 0);
457    }
458
459    #[test]
460    fn resolve_effective_pane_mut_updates_the_active_pane_for_window_targets() {
461        let mut store = store_with_selected_second_pane();
462        let target = Target::parse("alpha:0").expect("valid window target");
463        let replacement = PaneGeometry::new(9, 8, 7, 6);
464
465        store
466            .resolve_effective_pane_mut(&target)
467            .expect("active pane exists")
468            .set_geometry(replacement);
469
470        let pane = store
471            .resolve_effective_pane(&target)
472            .expect("active pane still exists");
473        assert_eq!(pane.geometry(), replacement);
474    }
475}