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