use crate::utils;
use serde::de::DeserializeOwned;
use serde_json::Value;
use std::{fmt, io, path::Path, process::Command};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VimVar<Name: AsRef<str>> {
cmd: Cmd,
scope: Scope,
name: Name,
}
impl<Name: AsRef<str>> VimVar<Name> {
pub fn new(cmd: Cmd, scope: Scope, name: Name) -> Self {
Self { cmd, scope, name }
}
pub fn cmd(&self) -> Cmd {
self.cmd
}
pub fn scope(&self) -> Scope {
self.scope
}
pub fn name(&self) -> &str {
self.name.as_ref()
}
}
impl<Name: AsRef<str>> VimVar<Name> {
pub fn load_typed<T>(&self, allow_zero: bool) -> io::Result<Option<T>>
where
T: DeserializeOwned,
{
self.load(allow_zero)?
.map(|value| serde_json::from_value(value).map_err(Into::into))
.transpose()
}
pub fn load(&self, allow_zero: bool) -> io::Result<Option<Value>> {
let vimrc = utils::find_vimrc()
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "vimrc not found"))?;
self.load_with_config(vimrc, allow_zero)
}
pub fn load_typed_with_config<P: AsRef<Path>, T>(
&self,
config: P,
allow_zero: bool,
) -> io::Result<Option<T>>
where
T: DeserializeOwned,
{
self.load_with_config(config, allow_zero)?
.map(|value| serde_json::from_value(value).map_err(Into::into))
.transpose()
}
pub fn load_with_config<P: AsRef<Path>>(
&self,
config: P,
allow_zero: bool,
) -> io::Result<Option<Value>> {
let cmd = self.cmd;
let scope = self.scope.as_str();
let var = self.name.as_ref();
let full_cmd = {
if config.as_ref().as_os_str().is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"path to vimrc is required for neovim/vim",
));
}
format!(
r#"{} -Es -i NONE -u "{}" '+set nonumber' '+redir => m | echon json_encode(get({}, "{}")) | redir END | put=m' '+%p' '+qa!'"#,
cmd,
config.as_ref().to_string_lossy(),
scope,
var,
)
};
let output = Command::new("sh").arg("-c").arg(full_cmd).output()?;
if !output.status.success() && (output.status.code() != Some(1)) {
let code = output
.status
.code()
.as_ref()
.map(ToString::to_string)
.unwrap_or_else(|| String::from("--"));
return Err(io::Error::new(
io::ErrorKind::Other,
format!(
"[Exit code {}]: {}",
code,
String::from_utf8_lossy(&output.stderr).trim()
)
.as_str(),
));
}
let output_string = String::from_utf8_lossy(&output.stdout);
if output_string.trim().is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Result from {} was empty", self.cmd),
));
}
let value: Value = serde_json::from_str(output_string.trim()).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("Failed to parse as JSON: \"{}\"", output_string.trim()),
)
})?;
if !allow_zero && value == serde_json::json!(0) {
Ok(None)
} else {
Ok(Some(value))
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Cmd {
Neovim,
Vim,
}
impl Cmd {
pub fn as_str(&self) -> &'static str {
match self {
Self::Vim => "vim",
Self::Neovim => "nvim",
}
}
}
impl fmt::Display for Cmd {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Scope {
Nothing,
Buffer,
Window,
Tabpage,
Global,
Local,
Script,
FunctionArg,
Vim,
}
impl Default for Scope {
fn default() -> Self {
Self::Global
}
}
impl Scope {
pub fn as_str(&self) -> &'static str {
match self {
Self::Nothing => "",
Self::Buffer => "b:",
Self::Window => "w:",
Self::Tabpage => "t:",
Self::Global => "g:",
Self::Local => "l:",
Self::Script => "s:",
Self::FunctionArg => "a:",
Self::Vim => "v:",
}
}
}
impl fmt::Display for Scope {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}