Skip to main content

rmux_client/commands/
config.rs

1use rmux_proto::request::{SetHookMutationRequest, ShowHooksRequest};
2use rmux_proto::types::OptionScopeSelector;
3use rmux_proto::{
4    HookLifecycle, HookName, OptionName, PaneTarget, Request, Response, ScopeSelector,
5    SetEnvironmentMode, SetEnvironmentRequest, SetHookRequest, SetOptionByNameRequest,
6    SetOptionMode, SetOptionRequest, ShowEnvironmentRequest, ShowOptionsRequest, SourceFileRequest,
7};
8use std::path::PathBuf;
9
10use crate::{connection::Connection, ClientError};
11
12impl Connection {
13    /// Sends a `set-option` request over the detached RPC channel.
14    pub fn set_option(
15        &mut self,
16        scope: ScopeSelector,
17        option: OptionName,
18        value: String,
19        mode: SetOptionMode,
20    ) -> Result<Response, ClientError> {
21        let request = SetOptionRequest {
22            scope,
23            option,
24            value,
25            mode,
26        };
27        rmux_core::validate_option_mutation(
28            request.option,
29            &request.scope,
30            request.mode,
31            &request.value,
32        )?;
33        self.roundtrip(&Request::SetOption(request))
34    }
35
36    /// Sends a string-keyed `set-option` request over the detached RPC channel.
37    #[allow(clippy::too_many_arguments)]
38    pub fn set_option_by_name(
39        &mut self,
40        scope: OptionScopeSelector,
41        name: String,
42        value: Option<String>,
43        mode: SetOptionMode,
44        only_if_unset: bool,
45        unset: bool,
46        unset_pane_overrides: bool,
47    ) -> Result<Response, ClientError> {
48        let request = SetOptionByNameRequest {
49            scope,
50            name,
51            value,
52            mode,
53            only_if_unset,
54            unset,
55            unset_pane_overrides,
56            format: false,
57            format_target: None,
58        };
59        rmux_core::validate_option_name_mutation(
60            &request.name,
61            &request.scope,
62            request.mode,
63            request.value.as_deref(),
64            request.unset,
65        )?;
66        self.roundtrip(&Request::SetOptionByName(Box::new(request)))
67    }
68
69    /// Sends a `set-environment` request over the detached RPC channel.
70    pub fn set_environment(
71        &mut self,
72        scope: ScopeSelector,
73        name: String,
74        value: String,
75        mode: Option<SetEnvironmentMode>,
76        hidden: bool,
77        format: bool,
78    ) -> Result<Response, ClientError> {
79        self.roundtrip(&Request::SetEnvironment(Box::new(SetEnvironmentRequest {
80            scope,
81            name,
82            value,
83            mode,
84            hidden,
85            format,
86        })))
87    }
88
89    /// Sends a `set-hook` request over the detached RPC channel.
90    pub fn set_hook(
91        &mut self,
92        scope: ScopeSelector,
93        hook: HookName,
94        command: String,
95        lifecycle: HookLifecycle,
96    ) -> Result<Response, ClientError> {
97        self.roundtrip(&Request::SetHook(SetHookRequest {
98            scope,
99            hook,
100            command,
101            lifecycle,
102        }))
103    }
104
105    /// Sends an extended `set-hook` mutation over the detached RPC channel.
106    #[allow(clippy::too_many_arguments)]
107    pub fn set_hook_mutation(
108        &mut self,
109        scope: ScopeSelector,
110        hook: HookName,
111        command: Option<String>,
112        lifecycle: HookLifecycle,
113        append: bool,
114        unset: bool,
115        run_immediately: bool,
116        index: Option<u32>,
117    ) -> Result<Response, ClientError> {
118        self.roundtrip(&Request::SetHookMutation(SetHookMutationRequest {
119            scope,
120            hook,
121            command,
122            lifecycle,
123            append,
124            unset,
125            run_immediately,
126            index,
127        }))
128    }
129
130    /// Sends a `show-options` request over the detached RPC channel.
131    pub fn show_options(
132        &mut self,
133        scope: OptionScopeSelector,
134        name: Option<String>,
135        value_only: bool,
136        include_inherited: bool,
137        quiet: bool,
138    ) -> Result<Response, ClientError> {
139        self.show_options_extended(scope, name, value_only, include_inherited, quiet, false)
140    }
141
142    /// Sends an extended `show-options` request.
143    #[allow(clippy::too_many_arguments)]
144    pub fn show_options_extended(
145        &mut self,
146        scope: OptionScopeSelector,
147        name: Option<String>,
148        value_only: bool,
149        include_inherited: bool,
150        quiet: bool,
151        include_hooks: bool,
152    ) -> Result<Response, ClientError> {
153        self.roundtrip(&Request::ShowOptions(ShowOptionsRequest {
154            scope,
155            name,
156            value_only,
157            include_inherited,
158            quiet,
159            include_hooks,
160        }))
161    }
162
163    /// Sends a `show-environment` request over the detached RPC channel.
164    pub fn show_environment(
165        &mut self,
166        scope: ScopeSelector,
167        name: Option<String>,
168        hidden: bool,
169        shell_format: bool,
170    ) -> Result<Response, ClientError> {
171        self.roundtrip(&Request::ShowEnvironment(ShowEnvironmentRequest {
172            scope,
173            name,
174            hidden,
175            shell_format,
176        }))
177    }
178
179    /// Sends a `show-hooks` request over the detached RPC channel.
180    pub fn show_hooks(
181        &mut self,
182        scope: ScopeSelector,
183        window: bool,
184        pane: bool,
185        hook: Option<HookName>,
186    ) -> Result<Response, ClientError> {
187        self.roundtrip(&Request::ShowHooks(ShowHooksRequest {
188            scope,
189            window,
190            pane,
191            hook,
192        }))
193    }
194
195    /// Sends a `source-file` request over the detached RPC channel.
196    #[allow(clippy::too_many_arguments)]
197    pub fn source_file(
198        &mut self,
199        paths: Vec<String>,
200        quiet: bool,
201        parse_only: bool,
202        verbose: bool,
203        expand_paths: bool,
204        target: Option<PaneTarget>,
205        stdin: Option<String>,
206    ) -> Result<Response, ClientError> {
207        self.roundtrip_without_read_timeout(&Request::SourceFile(Box::new(SourceFileRequest {
208            paths,
209            quiet,
210            parse_only,
211            verbose,
212            expand_paths,
213            target,
214            caller_cwd: current_working_directory(),
215            stdin,
216        })))
217    }
218}
219
220fn current_working_directory() -> Option<PathBuf> {
221    std::env::current_dir().ok()
222}
223
224#[cfg(all(test, unix))]
225mod tests {
226    use std::io::{self, Read};
227    use std::os::unix::net::UnixStream;
228
229    use rmux_proto::{OptionName, RmuxError, ScopeSelector, SetOptionMode};
230
231    use super::Connection;
232    use crate::ClientError;
233
234    #[test]
235    fn set_option_rejects_invalid_requests_before_writing_to_the_socket() {
236        let (client_stream, mut server_stream) = UnixStream::pair().expect("create stream pair");
237        server_stream
238            .set_nonblocking(true)
239            .expect("set read end nonblocking");
240        let mut connection = Connection::new(client_stream).expect("connection");
241
242        let error = connection
243            .set_option(
244                ScopeSelector::Global,
245                OptionName::BaseIndex,
246                "abc".to_owned(),
247                SetOptionMode::Replace,
248            )
249            .expect_err("invalid request should fail");
250
251        assert!(matches!(
252            error,
253            ClientError::Protocol(RmuxError::InvalidSetOption(message))
254                if message == "value is invalid: abc"
255        ));
256
257        let mut buffer = [0_u8; 1];
258        let read_error = server_stream
259            .read(&mut buffer)
260            .expect_err("validation should happen before any bytes are written");
261        assert_eq!(read_error.kind(), io::ErrorKind::WouldBlock);
262    }
263}