Skip to main content

rmux_client/commands/
pane.rs

1use rmux_proto::{
2    BreakPaneRequest, CancelSdkWaitRequest, ClockModeRequest, CopyModeRequest, DisplayPanesRequest,
3    JoinPaneRequest, KillPaneRequest, LastPaneRequest, MovePaneRequest, PaneBroadcastInputRequest,
4    PaneInputRequest, PaneOutputCursorRequest, PaneOutputSubscriptionId,
5    PaneOutputSubscriptionStart, PaneSnapshotRefRequest, PaneTarget, PaneTargetRef,
6    PipePaneRequest, Request, ResizePaneAdjustment, ResizePaneRequest,
7    ResizePaneTargetActionRequest, RespawnPaneRequest, Response, SdkWaitForOutputRefRequest,
8    SdkWaitId, SdkWaitOwnerId, SelectPaneAdjacentRequest, SelectPaneDirection,
9    SelectPaneMarkRequest, SelectPaneRequest, SendKeysExt2Request, SendKeysExtRequest,
10    SendKeysRequest, SendPrefixRequest, SessionName, SubscribePaneOutputRefRequest,
11    SwapPaneDirection, SwapPaneRequest, UnsubscribePaneOutputRequest, WindowTarget,
12    CAPABILITY_SDK_PANE_BROADCAST, CAPABILITY_SDK_PANE_BY_ID, CAPABILITY_SDK_WAITS,
13    CAPABILITY_TARGET_CLIENT_COMMANDS,
14};
15
16use crate::{connection::Connection, ClientError};
17
18impl Connection {
19    /// Sends a `swap-pane` request over the detached RPC channel.
20    pub fn swap_pane(
21        &mut self,
22        source: PaneTarget,
23        target: PaneTarget,
24        detached: bool,
25        preserve_zoom: bool,
26    ) -> Result<Response, ClientError> {
27        self.roundtrip(&Request::SwapPane(SwapPaneRequest {
28            source,
29            target,
30            direction: None,
31            detached,
32            preserve_zoom,
33        }))
34    }
35
36    /// Sends `swap-pane -D` over the detached RPC channel.
37    pub fn swap_pane_with_next(
38        &mut self,
39        target: PaneTarget,
40        detached: bool,
41        preserve_zoom: bool,
42    ) -> Result<Response, ClientError> {
43        self.roundtrip(&Request::SwapPane(SwapPaneRequest {
44            source: target.clone(),
45            target,
46            direction: Some(SwapPaneDirection::Down),
47            detached,
48            preserve_zoom,
49        }))
50    }
51
52    /// Sends `swap-pane -U` over the detached RPC channel.
53    pub fn swap_pane_with_previous(
54        &mut self,
55        target: PaneTarget,
56        detached: bool,
57        preserve_zoom: bool,
58    ) -> Result<Response, ClientError> {
59        self.roundtrip(&Request::SwapPane(SwapPaneRequest {
60            source: target.clone(),
61            target,
62            direction: Some(SwapPaneDirection::Up),
63            detached,
64            preserve_zoom,
65        }))
66    }
67
68    /// Sends a `last-pane` request over the detached RPC channel.
69    pub fn last_pane(&mut self, target: WindowTarget) -> Result<Response, ClientError> {
70        self.last_pane_with_zoom(target, false)
71    }
72
73    /// Sends a `last-pane` request with optional zoom preservation.
74    pub fn last_pane_with_zoom(
75        &mut self,
76        target: WindowTarget,
77        preserve_zoom: bool,
78    ) -> Result<Response, ClientError> {
79        self.last_pane_with_options(target, preserve_zoom, None)
80    }
81
82    /// Sends a `last-pane` request with tmux selection options.
83    pub fn last_pane_with_options(
84        &mut self,
85        target: WindowTarget,
86        preserve_zoom: bool,
87        input_disabled: Option<bool>,
88    ) -> Result<Response, ClientError> {
89        self.roundtrip(&Request::LastPane(LastPaneRequest {
90            target,
91            preserve_zoom,
92            input_disabled,
93        }))
94    }
95
96    /// Sends a `join-pane` request over the detached RPC channel.
97    pub fn join_pane(&mut self, request: JoinPaneRequest) -> Result<Response, ClientError> {
98        self.roundtrip(&Request::JoinPane(request))
99    }
100
101    /// Sends a `move-pane` request over the detached RPC channel.
102    pub fn move_pane(&mut self, request: MovePaneRequest) -> Result<Response, ClientError> {
103        self.roundtrip(&Request::MovePane(request))
104    }
105
106    /// Sends a `break-pane` request over the detached RPC channel.
107    pub fn break_pane(&mut self, request: BreakPaneRequest) -> Result<Response, ClientError> {
108        self.roundtrip(&Request::BreakPane(Box::new(request)))
109    }
110
111    /// Sends a `resize-pane` request over the detached RPC channel.
112    pub fn resize_pane(
113        &mut self,
114        target: PaneTarget,
115        adjustment: ResizePaneAdjustment,
116    ) -> Result<Response, ClientError> {
117        self.roundtrip(&Request::ResizePane(ResizePaneRequest {
118            target,
119            adjustment,
120        }))
121    }
122
123    /// Sends a `resize-pane` request with server-side target resolution.
124    pub fn resize_pane_target_action(
125        &mut self,
126        request: ResizePaneTargetActionRequest,
127    ) -> Result<Response, ClientError> {
128        self.roundtrip(&Request::ResizePaneTargetAction(request))
129    }
130
131    /// Sends a `display-panes` request over the detached RPC channel.
132    pub fn display_panes(
133        &mut self,
134        target: SessionName,
135        duration_ms: Option<u64>,
136        non_blocking: bool,
137        no_command: bool,
138        template: Option<String>,
139    ) -> Result<Response, ClientError> {
140        self.display_panes_target_client(
141            target,
142            duration_ms,
143            non_blocking,
144            no_command,
145            template,
146            None,
147        )
148    }
149
150    /// Sends a target-client-aware `display-panes` request.
151    #[allow(clippy::too_many_arguments)]
152    pub fn display_panes_target_client(
153        &mut self,
154        target: SessionName,
155        duration_ms: Option<u64>,
156        non_blocking: bool,
157        no_command: bool,
158        template: Option<String>,
159        target_client: Option<String>,
160    ) -> Result<Response, ClientError> {
161        let request = Request::DisplayPanes(Box::new(DisplayPanesRequest {
162            target,
163            duration_ms,
164            non_blocking,
165            no_command,
166            template,
167            target_client,
168        }));
169        if non_blocking {
170            self.roundtrip(&request)
171        } else {
172            self.roundtrip_without_read_timeout(&request)
173        }
174    }
175
176    /// Sends a `pipe-pane` request over the detached RPC channel.
177    pub fn pipe_pane(
178        &mut self,
179        target: PaneTarget,
180        stdin: bool,
181        stdout: bool,
182        once: bool,
183        command: Option<String>,
184    ) -> Result<Response, ClientError> {
185        self.roundtrip(&Request::PipePane(PipePaneRequest {
186            target,
187            stdin,
188            stdout,
189            once,
190            command,
191        }))
192    }
193
194    /// Sends a `respawn-pane` request over the detached RPC channel.
195    pub fn respawn_pane(&mut self, request: RespawnPaneRequest) -> Result<Response, ClientError> {
196        self.roundtrip(&Request::RespawnPane(Box::new(request)))
197    }
198
199    /// Sends a `select-pane` request over the detached RPC channel.
200    pub fn select_pane(&mut self, target: PaneTarget) -> Result<Response, ClientError> {
201        self.select_pane_with_title(target, None)
202    }
203
204    /// Sends a `select-pane` request with an optional title over the detached RPC channel.
205    pub fn select_pane_with_title(
206        &mut self,
207        target: PaneTarget,
208        title: Option<String>,
209    ) -> Result<Response, ClientError> {
210        self.select_pane_with_title_and_zoom(target, title, false)
211    }
212
213    /// Sends a `select-pane` request with optional title and zoom preservation.
214    pub fn select_pane_with_title_and_zoom(
215        &mut self,
216        target: PaneTarget,
217        title: Option<String>,
218        preserve_zoom: bool,
219    ) -> Result<Response, ClientError> {
220        self.select_pane_with_options(target, title, None, None, preserve_zoom)
221    }
222
223    /// Sends a `select-pane` request with optional title, style, input state, and zoom preservation.
224    pub fn select_pane_with_options(
225        &mut self,
226        target: PaneTarget,
227        title: Option<String>,
228        style: Option<String>,
229        input_disabled: Option<bool>,
230        preserve_zoom: bool,
231    ) -> Result<Response, ClientError> {
232        self.roundtrip(&Request::SelectPane(Box::new(SelectPaneRequest {
233            target,
234            title,
235            input_disabled,
236            preserve_zoom,
237            style,
238        })))
239    }
240
241    /// Sends a `select-pane -d` / `-e` input-state request.
242    pub fn select_pane_input(
243        &mut self,
244        target: PaneTarget,
245        disabled: bool,
246    ) -> Result<Response, ClientError> {
247        self.roundtrip(&Request::SelectPane(Box::new(SelectPaneRequest {
248            target,
249            title: None,
250            style: None,
251            input_disabled: Some(disabled),
252            preserve_zoom: false,
253        })))
254    }
255
256    /// Sends a directional `select-pane` request over the detached RPC channel.
257    pub fn select_pane_adjacent(
258        &mut self,
259        target: PaneTarget,
260        direction: SelectPaneDirection,
261    ) -> Result<Response, ClientError> {
262        self.select_pane_adjacent_with_zoom(target, direction, false)
263    }
264
265    /// Sends a directional `select-pane` request with optional zoom preservation.
266    pub fn select_pane_adjacent_with_zoom(
267        &mut self,
268        target: PaneTarget,
269        direction: SelectPaneDirection,
270        preserve_zoom: bool,
271    ) -> Result<Response, ClientError> {
272        self.roundtrip(&Request::SelectPaneAdjacent(SelectPaneAdjacentRequest {
273            target,
274            direction,
275            preserve_zoom,
276        }))
277    }
278
279    /// Sends `select-pane -m` or `select-pane -M` over the detached RPC channel.
280    pub fn select_pane_mark(
281        &mut self,
282        target: PaneTarget,
283        clear: bool,
284    ) -> Result<Response, ClientError> {
285        self.select_pane_mark_with_title(target, clear, None)
286    }
287
288    /// Sends `select-pane -m/-M` with an optional title over the detached RPC channel.
289    pub fn select_pane_mark_with_title(
290        &mut self,
291        target: PaneTarget,
292        clear: bool,
293        title: Option<String>,
294    ) -> Result<Response, ClientError> {
295        self.roundtrip(&Request::SelectPaneMark(SelectPaneMarkRequest {
296            target,
297            clear,
298            title,
299        }))
300    }
301
302    /// Sends a `kill-pane` request over the detached RPC channel.
303    pub fn kill_pane(&mut self, target: PaneTarget) -> Result<Response, ClientError> {
304        self.kill_pane_with_options(target, false)
305    }
306
307    /// Sends a `kill-pane` request with extended tmux flags.
308    pub fn kill_pane_with_options(
309        &mut self,
310        target: PaneTarget,
311        kill_all_except: bool,
312    ) -> Result<Response, ClientError> {
313        self.roundtrip(&Request::KillPane(KillPaneRequest {
314            target,
315            kill_all_except,
316        }))
317    }
318
319    /// Sends a `send-keys` request over the detached RPC channel.
320    pub fn send_keys(
321        &mut self,
322        target: PaneTarget,
323        keys: Vec<String>,
324    ) -> Result<Response, ClientError> {
325        self.roundtrip(&Request::SendKeys(SendKeysRequest { target, keys }))
326    }
327
328    /// Sends an extended `send-keys` request over the detached RPC channel.
329    pub fn send_keys_extended(
330        &mut self,
331        request: SendKeysExtRequest,
332    ) -> Result<Response, ClientError> {
333        self.roundtrip(&Request::SendKeysExt(request))
334    }
335
336    /// Sends a target-client aware `send-keys` request over detached RPC.
337    pub fn send_keys_extended_target_client(
338        &mut self,
339        request: SendKeysExt2Request,
340    ) -> Result<Response, ClientError> {
341        if !self.supports_capability(CAPABILITY_TARGET_CLIENT_COMMANDS)? {
342            return Err(ClientError::Protocol(
343                rmux_proto::RmuxError::UnsupportedCapability {
344                    feature: CAPABILITY_TARGET_CLIENT_COMMANDS.to_owned(),
345                    supported: Vec::new(),
346                },
347            ));
348        }
349        self.roundtrip(&Request::SendKeysExt2(Box::new(request)))
350    }
351
352    /// Sends a stable-target SDK pane input request over detached RPC.
353    pub fn pane_input(&mut self, request: PaneInputRequest) -> Result<Response, ClientError> {
354        if !self.supports_capability(CAPABILITY_SDK_PANE_BY_ID)? {
355            return Err(ClientError::Protocol(
356                rmux_proto::RmuxError::UnsupportedCapability {
357                    feature: CAPABILITY_SDK_PANE_BY_ID.to_owned(),
358                    supported: Vec::new(),
359                },
360            ));
361        }
362        self.roundtrip(&Request::PaneInput(request))
363    }
364
365    /// Sends a stable-target SDK pane input broadcast request over detached RPC.
366    pub fn pane_broadcast_input(
367        &mut self,
368        request: PaneBroadcastInputRequest,
369    ) -> Result<Response, ClientError> {
370        if !self.supports_capability(CAPABILITY_SDK_PANE_BROADCAST)? {
371            return Err(ClientError::Protocol(
372                rmux_proto::RmuxError::UnsupportedCapability {
373                    feature: CAPABILITY_SDK_PANE_BROADCAST.to_owned(),
374                    supported: Vec::new(),
375                },
376            ));
377        }
378        self.roundtrip(&Request::PaneBroadcastInput(request))
379    }
380
381    /// Captures a stable-target structured pane snapshot over detached RPC.
382    pub fn pane_snapshot_ref(&mut self, target: PaneTargetRef) -> Result<Response, ClientError> {
383        if !self.supports_capability(CAPABILITY_SDK_PANE_BY_ID)? {
384            return Err(ClientError::Protocol(
385                rmux_proto::RmuxError::UnsupportedCapability {
386                    feature: CAPABILITY_SDK_PANE_BY_ID.to_owned(),
387                    supported: Vec::new(),
388                },
389            ));
390        }
391        self.roundtrip(&Request::PaneSnapshotRef(PaneSnapshotRefRequest { target }))
392    }
393
394    /// Subscribes to stable-target pane output.
395    pub fn subscribe_pane_output_ref(
396        &mut self,
397        target: PaneTargetRef,
398        start: PaneOutputSubscriptionStart,
399    ) -> Result<Response, ClientError> {
400        if !self.supports_capability(CAPABILITY_SDK_PANE_BY_ID)? {
401            return Err(ClientError::Protocol(
402                rmux_proto::RmuxError::UnsupportedCapability {
403                    feature: CAPABILITY_SDK_PANE_BY_ID.to_owned(),
404                    supported: Vec::new(),
405                },
406            ));
407        }
408        self.roundtrip(&Request::SubscribePaneOutputRef(
409            SubscribePaneOutputRefRequest { target, start },
410        ))
411    }
412
413    /// Polls a pane-output subscription cursor.
414    pub fn pane_output_cursor(
415        &mut self,
416        subscription_id: PaneOutputSubscriptionId,
417        max_events: Option<u16>,
418    ) -> Result<Response, ClientError> {
419        self.roundtrip(&Request::PaneOutputCursor(PaneOutputCursorRequest {
420            subscription_id,
421            max_events,
422        }))
423    }
424
425    /// Unsubscribes from pane output.
426    pub fn unsubscribe_pane_output(
427        &mut self,
428        subscription_id: PaneOutputSubscriptionId,
429    ) -> Result<Response, ClientError> {
430        self.roundtrip(&Request::UnsubscribePaneOutput(
431            UnsubscribePaneOutputRequest { subscription_id },
432        ))
433    }
434
435    /// Arms a daemon-backed stable-target SDK byte wait.
436    pub fn sdk_wait_for_output_ref(
437        &mut self,
438        owner_id: SdkWaitOwnerId,
439        wait_id: SdkWaitId,
440        target: PaneTargetRef,
441        bytes: Vec<u8>,
442        start: PaneOutputSubscriptionStart,
443    ) -> Result<Response, ClientError> {
444        if !self.supports_capability(CAPABILITY_SDK_WAITS)? {
445            return Err(ClientError::Protocol(
446                rmux_proto::RmuxError::UnsupportedCapability {
447                    feature: CAPABILITY_SDK_WAITS.to_owned(),
448                    supported: Vec::new(),
449                },
450            ));
451        }
452        self.roundtrip_without_read_timeout(&Request::SdkWaitForOutputRef(
453            SdkWaitForOutputRefRequest {
454                owner_id,
455                wait_id,
456                target,
457                bytes,
458                start,
459            },
460        ))
461    }
462
463    /// Writes a stable-target SDK byte wait request without reading its response.
464    ///
465    /// Callers use this to arm a future-output wait before performing another
466    /// action, then read the response later on the same connection.
467    pub fn arm_sdk_wait_for_output_ref(
468        &mut self,
469        owner_id: SdkWaitOwnerId,
470        wait_id: SdkWaitId,
471        target: PaneTargetRef,
472        bytes: Vec<u8>,
473        start: PaneOutputSubscriptionStart,
474    ) -> Result<(), ClientError> {
475        if !self.supports_capability(CAPABILITY_SDK_WAITS)? {
476            return Err(ClientError::Protocol(
477                rmux_proto::RmuxError::UnsupportedCapability {
478                    feature: CAPABILITY_SDK_WAITS.to_owned(),
479                    supported: Vec::new(),
480                },
481            ));
482        }
483        self.write_request(&Request::SdkWaitForOutputRef(SdkWaitForOutputRefRequest {
484            owner_id,
485            wait_id,
486            target,
487            bytes,
488            start,
489        }))
490    }
491
492    /// Cancels a daemon-backed SDK byte wait.
493    pub fn cancel_sdk_wait(
494        &mut self,
495        owner_id: SdkWaitOwnerId,
496        wait_id: SdkWaitId,
497    ) -> Result<Response, ClientError> {
498        if !self.supports_capability(CAPABILITY_SDK_WAITS)? {
499            return Err(ClientError::Protocol(
500                rmux_proto::RmuxError::UnsupportedCapability {
501                    feature: CAPABILITY_SDK_WAITS.to_owned(),
502                    supported: Vec::new(),
503                },
504            ));
505        }
506        self.roundtrip(&Request::CancelSdkWait(CancelSdkWaitRequest {
507            owner_id,
508            wait_id,
509        }))
510    }
511
512    /// Sends a `send-prefix` request over the detached RPC channel.
513    pub fn send_prefix(
514        &mut self,
515        target: Option<PaneTarget>,
516        secondary: bool,
517    ) -> Result<Response, ClientError> {
518        self.roundtrip(&Request::SendPrefix(SendPrefixRequest {
519            target,
520            secondary,
521        }))
522    }
523
524    /// Sends a `copy-mode` request over the detached RPC channel.
525    pub fn copy_mode(&mut self, request: CopyModeRequest) -> Result<Response, ClientError> {
526        self.roundtrip(&Request::CopyMode(request))
527    }
528
529    /// Sends a `clock-mode` request over the detached RPC channel.
530    pub fn clock_mode(&mut self, target: Option<PaneTarget>) -> Result<Response, ClientError> {
531        self.roundtrip(&Request::ClockMode(ClockModeRequest { target }))
532    }
533}