1use std::future::Future;
2use std::io;
3use std::path::PathBuf;
4use std::pin::Pin;
5
6use distant_net::client::Channel;
7use distant_net::common::Request;
8
9use crate::client::{
10 RemoteCommand, RemoteLspCommand, RemoteLspProcess, RemoteOutput, RemoteProcess, Searcher,
11 Watcher,
12};
13use crate::protocol::{
14 self, ChangeKindSet, DirEntry, Environment, Error as Failure, Metadata, Permissions, PtySize,
15 SearchId, SearchQuery, SetPermissionsOptions, SystemInfo, Version,
16};
17
18pub type AsyncReturn<'a, T, E = io::Error> =
19 Pin<Box<dyn Future<Output = Result<T, E>> + Send + 'a>>;
20
21fn mismatched_response() -> io::Error {
22 io::Error::new(io::ErrorKind::Other, "Mismatched response")
23}
24
25pub trait DistantChannelExt {
27 fn append_file(
29 &mut self,
30 path: impl Into<PathBuf>,
31 data: impl Into<Vec<u8>>,
32 ) -> AsyncReturn<'_, ()>;
33
34 fn append_file_text(
36 &mut self,
37 path: impl Into<PathBuf>,
38 data: impl Into<String>,
39 ) -> AsyncReturn<'_, ()>;
40
41 fn copy(&mut self, src: impl Into<PathBuf>, dst: impl Into<PathBuf>) -> AsyncReturn<'_, ()>;
43
44 fn create_dir(&mut self, path: impl Into<PathBuf>, all: bool) -> AsyncReturn<'_, ()>;
46
47 fn exists(&mut self, path: impl Into<PathBuf>) -> AsyncReturn<'_, bool>;
49
50 fn is_compatible(&mut self) -> AsyncReturn<'_, bool>;
52
53 fn metadata(
55 &mut self,
56 path: impl Into<PathBuf>,
57 canonicalize: bool,
58 resolve_file_type: bool,
59 ) -> AsyncReturn<'_, Metadata>;
60
61 fn set_permissions(
63 &mut self,
64 path: impl Into<PathBuf>,
65 permissions: Permissions,
66 options: SetPermissionsOptions,
67 ) -> AsyncReturn<'_, ()>;
68
69 fn search(&mut self, query: impl Into<SearchQuery>) -> AsyncReturn<'_, Searcher>;
71
72 fn cancel_search(&mut self, id: SearchId) -> AsyncReturn<'_, ()>;
74
75 fn read_dir(
77 &mut self,
78 path: impl Into<PathBuf>,
79 depth: usize,
80 absolute: bool,
81 canonicalize: bool,
82 include_root: bool,
83 ) -> AsyncReturn<'_, (Vec<DirEntry>, Vec<Failure>)>;
84
85 fn read_file(&mut self, path: impl Into<PathBuf>) -> AsyncReturn<'_, Vec<u8>>;
87
88 fn read_file_text(&mut self, path: impl Into<PathBuf>) -> AsyncReturn<'_, String>;
90
91 fn remove(&mut self, path: impl Into<PathBuf>, force: bool) -> AsyncReturn<'_, ()>;
94
95 fn rename(&mut self, src: impl Into<PathBuf>, dst: impl Into<PathBuf>) -> AsyncReturn<'_, ()>;
97
98 fn watch(
100 &mut self,
101 path: impl Into<PathBuf>,
102 recursive: bool,
103 only: impl Into<ChangeKindSet>,
104 except: impl Into<ChangeKindSet>,
105 ) -> AsyncReturn<'_, Watcher>;
106
107 fn unwatch(&mut self, path: impl Into<PathBuf>) -> AsyncReturn<'_, ()>;
109
110 fn spawn(
112 &mut self,
113 cmd: impl Into<String>,
114 environment: Environment,
115 current_dir: Option<PathBuf>,
116 pty: Option<PtySize>,
117 ) -> AsyncReturn<'_, RemoteProcess>;
118
119 fn spawn_lsp(
121 &mut self,
122 cmd: impl Into<String>,
123 environment: Environment,
124 current_dir: Option<PathBuf>,
125 pty: Option<PtySize>,
126 ) -> AsyncReturn<'_, RemoteLspProcess>;
127
128 fn output(
130 &mut self,
131 cmd: impl Into<String>,
132 environment: Environment,
133 current_dir: Option<PathBuf>,
134 pty: Option<PtySize>,
135 ) -> AsyncReturn<'_, RemoteOutput>;
136
137 fn system_info(&mut self) -> AsyncReturn<'_, SystemInfo>;
139
140 fn version(&mut self) -> AsyncReturn<'_, Version>;
142
143 fn protocol_version(&self) -> protocol::semver::Version;
145
146 fn write_file(
148 &mut self,
149 path: impl Into<PathBuf>,
150 data: impl Into<Vec<u8>>,
151 ) -> AsyncReturn<'_, ()>;
152
153 fn write_file_text(
155 &mut self,
156 path: impl Into<PathBuf>,
157 data: impl Into<String>,
158 ) -> AsyncReturn<'_, ()>;
159}
160
161macro_rules! make_body {
162 ($self:expr, $data:expr, @ok) => {
163 make_body!($self, $data, |data| {
164 match data {
165 protocol::Response::Ok => Ok(()),
166 protocol::Response::Error(x) => Err(io::Error::from(x)),
167 _ => Err(mismatched_response()),
168 }
169 })
170 };
171
172 ($self:expr, $data:expr, $and_then:expr) => {{
173 let req = Request::new(protocol::Msg::Single($data));
174 Box::pin(async move {
175 $self
176 .send(req)
177 .await
178 .and_then(|res| match res.payload {
179 protocol::Msg::Single(x) => Ok(x),
180 _ => Err(mismatched_response()),
181 })
182 .and_then($and_then)
183 })
184 }};
185}
186
187impl DistantChannelExt
188 for Channel<protocol::Msg<protocol::Request>, protocol::Msg<protocol::Response>>
189{
190 fn append_file(
191 &mut self,
192 path: impl Into<PathBuf>,
193 data: impl Into<Vec<u8>>,
194 ) -> AsyncReturn<'_, ()> {
195 make_body!(
196 self,
197 protocol::Request::FileAppend { path: path.into(), data: data.into() },
198 @ok
199 )
200 }
201
202 fn append_file_text(
203 &mut self,
204 path: impl Into<PathBuf>,
205 data: impl Into<String>,
206 ) -> AsyncReturn<'_, ()> {
207 make_body!(
208 self,
209 protocol::Request::FileAppendText { path: path.into(), text: data.into() },
210 @ok
211 )
212 }
213
214 fn copy(&mut self, src: impl Into<PathBuf>, dst: impl Into<PathBuf>) -> AsyncReturn<'_, ()> {
215 make_body!(
216 self,
217 protocol::Request::Copy { src: src.into(), dst: dst.into() },
218 @ok
219 )
220 }
221
222 fn create_dir(&mut self, path: impl Into<PathBuf>, all: bool) -> AsyncReturn<'_, ()> {
223 make_body!(
224 self,
225 protocol::Request::DirCreate { path: path.into(), all },
226 @ok
227 )
228 }
229
230 fn exists(&mut self, path: impl Into<PathBuf>) -> AsyncReturn<'_, bool> {
231 make_body!(
232 self,
233 protocol::Request::Exists { path: path.into() },
234 |data| match data {
235 protocol::Response::Exists { value } => Ok(value),
236 protocol::Response::Error(x) => Err(io::Error::from(x)),
237 _ => Err(mismatched_response()),
238 }
239 )
240 }
241
242 fn is_compatible(&mut self) -> AsyncReturn<'_, bool> {
243 make_body!(self, protocol::Request::Version {}, |data| match data {
244 protocol::Response::Version(version) =>
245 Ok(protocol::is_compatible_with(&version.protocol_version)),
246 protocol::Response::Error(x) => Err(io::Error::from(x)),
247 _ => Err(mismatched_response()),
248 })
249 }
250
251 fn metadata(
252 &mut self,
253 path: impl Into<PathBuf>,
254 canonicalize: bool,
255 resolve_file_type: bool,
256 ) -> AsyncReturn<'_, Metadata> {
257 make_body!(
258 self,
259 protocol::Request::Metadata {
260 path: path.into(),
261 canonicalize,
262 resolve_file_type
263 },
264 |data| match data {
265 protocol::Response::Metadata(x) => Ok(x),
266 protocol::Response::Error(x) => Err(io::Error::from(x)),
267 _ => Err(mismatched_response()),
268 }
269 )
270 }
271
272 fn set_permissions(
273 &mut self,
274 path: impl Into<PathBuf>,
275 permissions: Permissions,
276 options: SetPermissionsOptions,
277 ) -> AsyncReturn<'_, ()> {
278 make_body!(
279 self,
280 protocol::Request::SetPermissions {
281 path: path.into(),
282 permissions,
283 options,
284 },
285 @ok
286 )
287 }
288
289 fn search(&mut self, query: impl Into<SearchQuery>) -> AsyncReturn<'_, Searcher> {
290 let query = query.into();
291 Box::pin(async move { Searcher::search(self.clone(), query).await })
292 }
293
294 fn cancel_search(&mut self, id: SearchId) -> AsyncReturn<'_, ()> {
295 make_body!(
296 self,
297 protocol::Request::CancelSearch { id },
298 @ok
299 )
300 }
301
302 fn read_dir(
303 &mut self,
304 path: impl Into<PathBuf>,
305 depth: usize,
306 absolute: bool,
307 canonicalize: bool,
308 include_root: bool,
309 ) -> AsyncReturn<'_, (Vec<DirEntry>, Vec<Failure>)> {
310 make_body!(
311 self,
312 protocol::Request::DirRead {
313 path: path.into(),
314 depth,
315 absolute,
316 canonicalize,
317 include_root
318 },
319 |data| match data {
320 protocol::Response::DirEntries { entries, errors } => Ok((entries, errors)),
321 protocol::Response::Error(x) => Err(io::Error::from(x)),
322 _ => Err(mismatched_response()),
323 }
324 )
325 }
326
327 fn read_file(&mut self, path: impl Into<PathBuf>) -> AsyncReturn<'_, Vec<u8>> {
328 make_body!(
329 self,
330 protocol::Request::FileRead { path: path.into() },
331 |data| match data {
332 protocol::Response::Blob { data } => Ok(data),
333 protocol::Response::Error(x) => Err(io::Error::from(x)),
334 _ => Err(mismatched_response()),
335 }
336 )
337 }
338
339 fn read_file_text(&mut self, path: impl Into<PathBuf>) -> AsyncReturn<'_, String> {
340 make_body!(
341 self,
342 protocol::Request::FileReadText { path: path.into() },
343 |data| match data {
344 protocol::Response::Text { data } => Ok(data),
345 protocol::Response::Error(x) => Err(io::Error::from(x)),
346 _ => Err(mismatched_response()),
347 }
348 )
349 }
350
351 fn remove(&mut self, path: impl Into<PathBuf>, force: bool) -> AsyncReturn<'_, ()> {
352 make_body!(
353 self,
354 protocol::Request::Remove { path: path.into(), force },
355 @ok
356 )
357 }
358
359 fn rename(&mut self, src: impl Into<PathBuf>, dst: impl Into<PathBuf>) -> AsyncReturn<'_, ()> {
360 make_body!(
361 self,
362 protocol::Request::Rename { src: src.into(), dst: dst.into() },
363 @ok
364 )
365 }
366
367 fn watch(
368 &mut self,
369 path: impl Into<PathBuf>,
370 recursive: bool,
371 only: impl Into<ChangeKindSet>,
372 except: impl Into<ChangeKindSet>,
373 ) -> AsyncReturn<'_, Watcher> {
374 let path = path.into();
375 let only = only.into();
376 let except = except.into();
377 Box::pin(async move { Watcher::watch(self.clone(), path, recursive, only, except).await })
378 }
379
380 fn unwatch(&mut self, path: impl Into<PathBuf>) -> AsyncReturn<'_, ()> {
381 fn inner_unwatch(
382 channel: &mut Channel<
383 protocol::Msg<protocol::Request>,
384 protocol::Msg<protocol::Response>,
385 >,
386 path: impl Into<PathBuf>,
387 ) -> AsyncReturn<'_, ()> {
388 make_body!(
389 channel,
390 protocol::Request::Unwatch { path: path.into() },
391 @ok
392 )
393 }
394
395 let path = path.into();
396
397 Box::pin(async move { inner_unwatch(self, path).await })
398 }
399
400 fn spawn(
401 &mut self,
402 cmd: impl Into<String>,
403 environment: Environment,
404 current_dir: Option<PathBuf>,
405 pty: Option<PtySize>,
406 ) -> AsyncReturn<'_, RemoteProcess> {
407 let cmd = cmd.into();
408 Box::pin(async move {
409 RemoteCommand::new()
410 .environment(environment)
411 .current_dir(current_dir)
412 .pty(pty)
413 .spawn(self.clone(), cmd)
414 .await
415 })
416 }
417
418 fn spawn_lsp(
419 &mut self,
420 cmd: impl Into<String>,
421 environment: Environment,
422 current_dir: Option<PathBuf>,
423 pty: Option<PtySize>,
424 ) -> AsyncReturn<'_, RemoteLspProcess> {
425 let cmd = cmd.into();
426 Box::pin(async move {
427 RemoteLspCommand::new()
428 .environment(environment)
429 .current_dir(current_dir)
430 .pty(pty)
431 .spawn(self.clone(), cmd)
432 .await
433 })
434 }
435
436 fn output(
437 &mut self,
438 cmd: impl Into<String>,
439 environment: Environment,
440 current_dir: Option<PathBuf>,
441 pty: Option<PtySize>,
442 ) -> AsyncReturn<'_, RemoteOutput> {
443 let cmd = cmd.into();
444 Box::pin(async move {
445 RemoteCommand::new()
446 .environment(environment)
447 .current_dir(current_dir)
448 .pty(pty)
449 .spawn(self.clone(), cmd)
450 .await?
451 .output()
452 .await
453 })
454 }
455
456 fn system_info(&mut self) -> AsyncReturn<'_, SystemInfo> {
457 make_body!(self, protocol::Request::SystemInfo {}, |data| match data {
458 protocol::Response::SystemInfo(x) => Ok(x),
459 protocol::Response::Error(x) => Err(io::Error::from(x)),
460 _ => Err(mismatched_response()),
461 })
462 }
463
464 fn version(&mut self) -> AsyncReturn<'_, Version> {
465 make_body!(self, protocol::Request::Version {}, |data| match data {
466 protocol::Response::Version(x) => Ok(x),
467 protocol::Response::Error(x) => Err(io::Error::from(x)),
468 _ => Err(mismatched_response()),
469 })
470 }
471
472 fn protocol_version(&self) -> protocol::semver::Version {
473 protocol::PROTOCOL_VERSION
474 }
475
476 fn write_file(
477 &mut self,
478 path: impl Into<PathBuf>,
479 data: impl Into<Vec<u8>>,
480 ) -> AsyncReturn<'_, ()> {
481 make_body!(
482 self,
483 protocol::Request::FileWrite { path: path.into(), data: data.into() },
484 @ok
485 )
486 }
487
488 fn write_file_text(
489 &mut self,
490 path: impl Into<PathBuf>,
491 data: impl Into<String>,
492 ) -> AsyncReturn<'_, ()> {
493 make_body!(
494 self,
495 protocol::Request::FileWriteText { path: path.into(), text: data.into() },
496 @ok
497 )
498 }
499}