nvim_rs/create/
tokio.rs

1//! Functions to spawn a [`neovim`](crate::neovim::Neovim) session using
2//! [`tokio`](tokio)
3use 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
49/// Connect to a neovim instance via tcp
50pub 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
73/// Connect to a neovim instance via unix socket (Unix) or named pipe (Windows)
74pub 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      // From windows-sys so we don't have to depend on that for just this constant
98      // https://docs.rs/windows-sys/latest/windows_sys/Win32/Foundation/constant.ERROR_PIPE_BUSY.html
99      pub const ERROR_PIPE_BUSY: i32 = 231i32;
100
101      // Based on the example in the tokio docs, see explanation there
102      // https://docs.rs/tokio/latest/tokio/net/windows/named_pipe/struct.NamedPipeClient.html
103      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
127/// Connect to a neovim instance by spawning a new one
128pub 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
145/// Connect to a neovim instance by spawning a new one
146pub 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
160/// Connect to a neovim instance by spawning a new one
161///
162/// stdin/stdout will be rewritten to `Stdio::piped()`
163pub 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
192/// Connect to the neovim instance that spawned this process over stdin/stdout
193pub 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
217/// Connect to a neovim instance by spawning a new one and send a handshake
218/// message. Unlike `new_child_cmd`, this function is tolerant to extra
219/// data in the reader before the handshake response is received.
220///
221/// `message` should be a unique string that is normally not found in the
222/// stdout. Due to the way Neovim packs strings, the length has to be either
223/// less than 20 characters or more than 31 characters long.
224/// See https://github.com/neovim/neovim/issues/32784 for more information.
225pub 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}