Skip to main content

fm/modes/menu/
nvim.rs

1use std::{
2    io::Write,
3    os::unix::net::UnixStream,
4    process::{Command, Stdio},
5};
6
7use anyhow::{bail, Result};
8use clap::Parser;
9
10use crate::io::Args;
11
12/// Find a way to open the file in neovim.
13/// Either, the process is ran from neovim itself, surelly from the plugin [fm-picker.nvim](https://github.com/qkzk/fm-picker.nvim)
14/// or it's ran externally in another terminal window.
15/// In the first case, an output socket should be provided through command line arguments and we use it.
16/// The plugin listen on it and open the file in a new buffer.
17/// In the second case, we send a a remote command to neovim.
18/// Use `nvim --server $server_address --remote $filepath` to open the file in the neovim session.
19///
20/// I tried my best to avoid writing a plugin just for that but couldn't find another way since there's many ways to
21/// open a terminal in neovim and they don't react to the same keybinds.
22/// The problem I faced was to avoid opening the file _in the same window as the terminal_ since it may be floating...
23pub fn nvim_open(server_address: &str, filepath: &std::path::Path) -> Result<()> {
24    let args = Args::parse();
25    if let Some(output_socket) = args.output_socket {
26        nvim_inform_ipc(&output_socket, NvimIPCAction::OPEN(&filepath))
27    } else {
28        nvim_remote_send_open(server_address, filepath)
29    }
30}
31
32fn nvim_remote_send_open(server_address: &str, filepath: &std::path::Path) -> Result<()> {
33    if !std::path::Path::new(server_address).exists() {
34        bail!("Neovim server {server_address} doesn't exists.");
35    }
36    let args = [
37        "--server",
38        server_address,
39        "--remote",
40        &filepath.to_string_lossy(),
41    ];
42    let output = Command::new("nvim")
43        .args(args)
44        .stdin(Stdio::null())
45        .stdout(Stdio::piped())
46        .stderr(Stdio::piped())
47        .output()?;
48
49    if !output.stdout.is_empty() || !output.stderr.is_empty() {
50        crate::log_info!(
51            "nvim {args:?}\nstdout: {stdout}\nstderr: {stderr}",
52            stdout = String::from_utf8_lossy(&output.stdout),
53            stderr = String::from_utf8_lossy(&output.stderr),
54        );
55    }
56
57    Ok(())
58}
59
60/// What kind of actions can we send to nvim ?
61/// Currently only opening a buffer or closing it.
62#[non_exhaustive]
63pub enum NvimIPCAction<'a, P>
64where
65    P: AsRef<std::path::Path>,
66{
67    OPEN(&'a P),
68    DELETE(&'a P),
69}
70
71/// Called when something is done on a buffer which may infer with nvim.
72/// It will send an event to nvim which is read by fm-picker.
73pub fn nvim_inform_ipc<P>(output_socket: &str, action: NvimIPCAction<P>) -> Result<()>
74where
75    P: AsRef<std::path::Path>,
76{
77    crate::log_info!("Using argument socket file {output_socket}");
78    let mut stream = UnixStream::connect(output_socket)?;
79
80    match action {
81        NvimIPCAction::OPEN(filepath) => {
82            writeln!(
83                stream,
84                "OPEN {filepath}",
85                filepath = filepath.as_ref().display()
86            )?;
87            crate::log_info!(
88                "Wrote to socket {output_socket}: OPEN {filepath}",
89                filepath = filepath.as_ref().display()
90            );
91        }
92        NvimIPCAction::DELETE(filepath) => {
93            writeln!(
94                stream,
95                "DELETE {filepath}",
96                filepath = filepath.as_ref().display()
97            )?;
98            crate::log_info!(
99                "Wrote to socket {output_socket}: DELETE {filepath}",
100                filepath = filepath.as_ref().display()
101            );
102        }
103    }
104    Ok(())
105}