1use std::{
4 future::Future,
5 io::{self, Error, ErrorKind},
6 path::Path,
7 process::Stdio,
8};
9
10use tokio::{
11 fs::File as TokioFile,
12 io::{split, stdin, WriteHalf},
13 net::{TcpStream, ToSocketAddrs},
14 process::{Child, ChildStdin, Command},
15 spawn,
16 task::JoinHandle,
17};
18
19#[cfg(unix)]
20type Connection = tokio::net::UnixStream;
21#[cfg(windows)]
22type Connection = tokio::net::windows::named_pipe::NamedPipeClient;
23
24use tokio_util::compat::{
25 Compat, TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt,
26};
27
28use crate::{
29 create::{unbuffered_stdout, Spawner},
30 error::{HandshakeError, LoopError},
31 neovim::Neovim,
32 Handler,
33};
34
35impl<H> Spawner for H
36where
37 H: Handler,
38{
39 type Handle = JoinHandle<()>;
40
41 fn spawn<Fut>(&self, future: Fut) -> Self::Handle
42 where
43 Fut: Future<Output = ()> + Send + 'static,
44 {
45 spawn(future)
46 }
47}
48
49pub async fn new_tcp<A, H>(
51 addr: A,
52 handler: H,
53) -> io::Result<(
54 Neovim<Compat<WriteHalf<TcpStream>>>,
55 JoinHandle<Result<(), Box<LoopError>>>,
56)>
57where
58 H: Handler<Writer = Compat<WriteHalf<TcpStream>>>,
59 A: ToSocketAddrs,
60{
61 let stream = TcpStream::connect(addr).await?;
62 let (reader, writer) = split(stream);
63 let (neovim, io) = Neovim::<Compat<WriteHalf<TcpStream>>>::new(
64 reader.compat(),
65 writer.compat_write(),
66 handler,
67 );
68 let io_handle = spawn(io);
69
70 Ok((neovim, io_handle))
71}
72
73pub async fn new_path<H, P: AsRef<Path> + Clone>(
75 path: P,
76 handler: H,
77) -> io::Result<(
78 Neovim<Compat<WriteHalf<Connection>>>,
79 JoinHandle<Result<(), Box<LoopError>>>,
80)>
81where
82 H: Handler<Writer = Compat<WriteHalf<Connection>>> + Send + 'static,
83{
84 let stream = {
85 #[cfg(unix)]
86 {
87 use tokio::net::UnixStream;
88
89 UnixStream::connect(path).await?
90 }
91 #[cfg(windows)]
92 {
93 use std::time::Duration;
94 use tokio::net::windows::named_pipe::ClientOptions;
95 use tokio::time;
96
97 pub const ERROR_PIPE_BUSY: i32 = 231i32;
100
101 let client = loop {
104 match ClientOptions::new().open(path.as_ref()) {
105 Ok(client) => break client,
106 Err(e) if e.raw_os_error() == Some(ERROR_PIPE_BUSY) => (),
107 Err(e) => return Err(e),
108 }
109
110 time::sleep(Duration::from_millis(50)).await;
111 };
112
113 client
114 }
115 };
116 let (reader, writer) = split(stream);
117 let (neovim, io) = Neovim::<Compat<WriteHalf<Connection>>>::new(
118 reader.compat(),
119 writer.compat_write(),
120 handler,
121 );
122 let io_handle = spawn(io);
123
124 Ok((neovim, io_handle))
125}
126
127pub async fn new_child<H>(
129 handler: H,
130) -> io::Result<(
131 Neovim<Compat<ChildStdin>>,
132 JoinHandle<Result<(), Box<LoopError>>>,
133 Child,
134)>
135where
136 H: Handler<Writer = Compat<ChildStdin>> + Send + 'static,
137{
138 if cfg!(target_os = "windows") {
139 new_child_path("nvim.exe", handler).await
140 } else {
141 new_child_path("nvim", handler).await
142 }
143}
144
145pub async fn new_child_path<H, S: AsRef<Path>>(
147 program: S,
148 handler: H,
149) -> io::Result<(
150 Neovim<Compat<ChildStdin>>,
151 JoinHandle<Result<(), Box<LoopError>>>,
152 Child,
153)>
154where
155 H: Handler<Writer = Compat<ChildStdin>> + Send + 'static,
156{
157 new_child_cmd(Command::new(program.as_ref()).arg("--embed"), handler).await
158}
159
160pub async fn new_child_cmd<H>(
164 cmd: &mut Command,
165 handler: H,
166) -> io::Result<(
167 Neovim<Compat<ChildStdin>>,
168 JoinHandle<Result<(), Box<LoopError>>>,
169 Child,
170)>
171where
172 H: Handler<Writer = Compat<ChildStdin>> + Send + 'static,
173{
174 let mut child = cmd.stdin(Stdio::piped()).stdout(Stdio::piped()).spawn()?;
175 let stdout = child
176 .stdout
177 .take()
178 .ok_or_else(|| Error::new(ErrorKind::Other, "Can't open stdout"))?
179 .compat();
180 let stdin = child
181 .stdin
182 .take()
183 .ok_or_else(|| Error::new(ErrorKind::Other, "Can't open stdin"))?
184 .compat_write();
185
186 let (neovim, io) = Neovim::<Compat<ChildStdin>>::new(stdout, stdin, handler);
187 let io_handle = spawn(io);
188
189 Ok((neovim, io_handle, child))
190}
191
192pub async fn new_parent<H>(
194 handler: H,
195) -> Result<
196 (
197 Neovim<Compat<tokio::fs::File>>,
198 JoinHandle<Result<(), Box<LoopError>>>,
199 ),
200 Error,
201>
202where
203 H: Handler<Writer = Compat<tokio::fs::File>>,
204{
205 let sout = TokioFile::from_std(unbuffered_stdout()?);
206
207 let (neovim, io) = Neovim::<Compat<tokio::fs::File>>::new(
208 stdin().compat(),
209 sout.compat(),
210 handler,
211 );
212 let io_handle = spawn(io);
213
214 Ok((neovim, io_handle))
215}
216
217pub async fn new_child_handshake_cmd<H>(
226 cmd: &mut Command,
227 handler: H,
228 message: &str,
229) -> Result<
230 (
231 Neovim<Compat<ChildStdin>>,
232 JoinHandle<Result<(), Box<LoopError>>>,
233 Child,
234 ),
235 Box<HandshakeError>,
236>
237where
238 H: Handler<Writer = Compat<ChildStdin>> + Send + 'static,
239{
240 let mut child = cmd.stdin(Stdio::piped()).stdout(Stdio::piped()).spawn()?;
241 let stdout = child
242 .stdout
243 .take()
244 .ok_or_else(|| Error::new(ErrorKind::Other, "Can't open stdout"))?
245 .compat();
246 let stdin = child
247 .stdin
248 .take()
249 .ok_or_else(|| Error::new(ErrorKind::Other, "Can't open stdin"))?
250 .compat_write();
251
252 let (neovim, io) =
253 Neovim::<Compat<ChildStdin>>::handshake(stdout, stdin, handler, message)
254 .await?;
255 let io_handle = spawn(io);
256
257 Ok((neovim, io_handle, child))
258}