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 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 #[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 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 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 #[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 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 #[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 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 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 #[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}