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.roundtrip(&Request::ShowOptions(ShowOptionsRequest {
140 scope,
141 name,
142 value_only,
143 include_inherited,
144 quiet,
145 }))
146 }
147
148 pub fn show_environment(
150 &mut self,
151 scope: ScopeSelector,
152 name: Option<String>,
153 hidden: bool,
154 shell_format: bool,
155 ) -> Result<Response, ClientError> {
156 self.roundtrip(&Request::ShowEnvironment(ShowEnvironmentRequest {
157 scope,
158 name,
159 hidden,
160 shell_format,
161 }))
162 }
163
164 pub fn show_hooks(
166 &mut self,
167 scope: ScopeSelector,
168 window: bool,
169 pane: bool,
170 hook: Option<HookName>,
171 ) -> Result<Response, ClientError> {
172 self.roundtrip(&Request::ShowHooks(ShowHooksRequest {
173 scope,
174 window,
175 pane,
176 hook,
177 }))
178 }
179
180 #[allow(clippy::too_many_arguments)]
182 pub fn source_file(
183 &mut self,
184 paths: Vec<String>,
185 quiet: bool,
186 parse_only: bool,
187 verbose: bool,
188 expand_paths: bool,
189 target: Option<PaneTarget>,
190 stdin: Option<String>,
191 ) -> Result<Response, ClientError> {
192 self.roundtrip_without_read_timeout(&Request::SourceFile(Box::new(SourceFileRequest {
193 paths,
194 quiet,
195 parse_only,
196 verbose,
197 expand_paths,
198 target,
199 caller_cwd: current_working_directory(),
200 stdin,
201 })))
202 }
203}
204
205fn current_working_directory() -> Option<PathBuf> {
206 std::env::current_dir().ok()
207}
208
209#[cfg(all(test, unix))]
210mod tests {
211 use std::io::{self, Read};
212 use std::os::unix::net::UnixStream;
213
214 use rmux_proto::{OptionName, RmuxError, ScopeSelector, SessionName, SetOptionMode};
215
216 use super::Connection;
217 use crate::ClientError;
218
219 #[test]
220 fn set_option_rejects_invalid_requests_before_writing_to_the_socket() {
221 let (client_stream, mut server_stream) = UnixStream::pair().expect("create stream pair");
222 server_stream
223 .set_nonblocking(true)
224 .expect("set read end nonblocking");
225 let mut connection = Connection::new(client_stream).expect("connection");
226
227 let error = connection
228 .set_option(
229 ScopeSelector::Session(SessionName::new("alpha").expect("valid session")),
230 OptionName::DefaultTerminal,
231 "tmux-256color".to_owned(),
232 SetOptionMode::Replace,
233 )
234 .expect_err("invalid request should fail");
235
236 assert!(matches!(
237 error,
238 ClientError::Protocol(RmuxError::InvalidSetOption(message))
239 if message == "default-terminal is only supported at global scope"
240 ));
241
242 let mut buffer = [0_u8; 1];
243 let read_error = server_stream
244 .read(&mut buffer)
245 .expect_err("validation should happen before any bytes are written");
246 assert_eq!(read_error.kind(), io::ErrorKind::WouldBlock);
247 }
248}