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 };
57 rmux_core::validate_option_name_mutation(
58 &request.name,
59 &request.scope,
60 request.mode,
61 request.value.as_deref(),
62 request.unset,
63 )?;
64 self.roundtrip(&Request::SetOptionByName(request))
65 }
66
67 pub fn set_environment(
69 &mut self,
70 scope: ScopeSelector,
71 name: String,
72 value: String,
73 mode: Option<SetEnvironmentMode>,
74 hidden: bool,
75 format: bool,
76 ) -> Result<Response, ClientError> {
77 self.roundtrip(&Request::SetEnvironment(SetEnvironmentRequest {
78 scope,
79 name,
80 value,
81 mode,
82 hidden,
83 format,
84 }))
85 }
86
87 pub fn set_hook(
89 &mut self,
90 scope: ScopeSelector,
91 hook: HookName,
92 command: String,
93 lifecycle: HookLifecycle,
94 ) -> Result<Response, ClientError> {
95 self.roundtrip(&Request::SetHook(SetHookRequest {
96 scope,
97 hook,
98 command,
99 lifecycle,
100 }))
101 }
102
103 #[allow(clippy::too_many_arguments)]
105 pub fn set_hook_mutation(
106 &mut self,
107 scope: ScopeSelector,
108 hook: HookName,
109 command: Option<String>,
110 lifecycle: HookLifecycle,
111 append: bool,
112 unset: bool,
113 run_immediately: bool,
114 index: Option<u32>,
115 ) -> Result<Response, ClientError> {
116 self.roundtrip(&Request::SetHookMutation(SetHookMutationRequest {
117 scope,
118 hook,
119 command,
120 lifecycle,
121 append,
122 unset,
123 run_immediately,
124 index,
125 }))
126 }
127
128 pub fn show_options(
130 &mut self,
131 scope: OptionScopeSelector,
132 name: Option<String>,
133 value_only: bool,
134 ) -> Result<Response, ClientError> {
135 self.roundtrip(&Request::ShowOptions(ShowOptionsRequest {
136 scope,
137 name,
138 value_only,
139 }))
140 }
141
142 pub fn show_environment(
144 &mut self,
145 scope: ScopeSelector,
146 name: Option<String>,
147 hidden: bool,
148 shell_format: bool,
149 ) -> Result<Response, ClientError> {
150 self.roundtrip(&Request::ShowEnvironment(ShowEnvironmentRequest {
151 scope,
152 name,
153 hidden,
154 shell_format,
155 }))
156 }
157
158 pub fn show_hooks(
160 &mut self,
161 scope: ScopeSelector,
162 window: bool,
163 pane: bool,
164 hook: Option<HookName>,
165 ) -> Result<Response, ClientError> {
166 self.roundtrip(&Request::ShowHooks(ShowHooksRequest {
167 scope,
168 window,
169 pane,
170 hook,
171 }))
172 }
173
174 #[allow(clippy::too_many_arguments)]
176 pub fn source_file(
177 &mut self,
178 paths: Vec<String>,
179 quiet: bool,
180 parse_only: bool,
181 verbose: bool,
182 expand_paths: bool,
183 target: Option<PaneTarget>,
184 stdin: Option<String>,
185 ) -> Result<Response, ClientError> {
186 self.roundtrip_without_read_timeout(&Request::SourceFile(SourceFileRequest {
187 paths,
188 quiet,
189 parse_only,
190 verbose,
191 expand_paths,
192 target,
193 caller_cwd: current_working_directory(),
194 stdin,
195 }))
196 }
197}
198
199fn current_working_directory() -> Option<PathBuf> {
200 std::env::current_dir().ok()
201}
202
203#[cfg(all(test, unix))]
204mod tests {
205 use std::io::{self, Read};
206 use std::os::unix::net::UnixStream;
207
208 use rmux_proto::{OptionName, RmuxError, ScopeSelector, SessionName, SetOptionMode};
209
210 use super::Connection;
211 use crate::ClientError;
212
213 #[test]
214 fn set_option_rejects_invalid_requests_before_writing_to_the_socket() {
215 let (client_stream, mut server_stream) = UnixStream::pair().expect("create stream pair");
216 server_stream
217 .set_nonblocking(true)
218 .expect("set read end nonblocking");
219 let mut connection = Connection::new(client_stream).expect("connection");
220
221 let error = connection
222 .set_option(
223 ScopeSelector::Session(SessionName::new("alpha").expect("valid session")),
224 OptionName::DefaultTerminal,
225 "tmux-256color".to_owned(),
226 SetOptionMode::Replace,
227 )
228 .expect_err("invalid request should fail");
229
230 assert!(matches!(
231 error,
232 ClientError::Protocol(RmuxError::InvalidSetOption(message))
233 if message == "default-terminal is only supported at global scope"
234 ));
235
236 let mut buffer = [0_u8; 1];
237 let read_error = server_stream
238 .read(&mut buffer)
239 .expect_err("validation should happen before any bytes are written");
240 assert_eq!(read_error.kind(), io::ErrorKind::WouldBlock);
241 }
242}