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#[non_exhaustive]
61pub enum NvimIPCAction<'a, P>
62where
63    P: AsRef<std::path::Path>,
64{
65    OPEN(&'a P),
66    DELETE(&'a P),
67}
68
69pub fn nvim_inform_ipc<P>(output_socket: &str, action: NvimIPCAction<P>) -> Result<()>
70where
71    P: AsRef<std::path::Path>,
72{
73    crate::log_info!("Using argument socket file {output_socket}");
74    let mut stream = UnixStream::connect(output_socket)?;
75
76    match action {
77        NvimIPCAction::OPEN(filepath) => {
78            writeln!(
79                stream,
80                "OPEN {filepath}",
81                filepath = filepath.as_ref().display()
82            )?;
83            crate::log_info!(
84                "Wrote to socket {output_socket}: OPEN {filepath}",
85                filepath = filepath.as_ref().display()
86            );
87        }
88        NvimIPCAction::DELETE(filepath) => {
89            writeln!(
90                stream,
91                "DELETE {filepath}",
92                filepath = filepath.as_ref().display()
93            )?;
94            crate::log_info!(
95                "Wrote to socket {output_socket}: DELETE {filepath}",
96                filepath = filepath.as_ref().display()
97            );
98        }
99    }
100    Ok(())
101}