1use rmux_proto::{PaneTarget, RmuxError, SessionName, Target, WindowTarget};
2
3use crate::{Pane, Session, SessionStore, Window};
4
5impl SessionStore {
6 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 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 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 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 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 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 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 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}