navi/src/actor.rs
2021-08-08 17:58:42 -03:00

240 lines
7.2 KiB
Rust

use crate::clipboard;
use crate::config::Action;
use crate::config::CONFIG;
use crate::env_var;
use crate::extractor;
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
use crate::finder::Finder;
use crate::fs;
use crate::shell;
use crate::shell::ShellSpawnError;
use crate::structures::cheat::{Suggestion, VariableMap};
use crate::writer;
use anyhow::Context;
use anyhow::Result;
use shell::EOF;
use std::io::Write;
use std::path::Path;
use std::process::Stdio;
fn prompt_finder(
variable_name: &str,
suggestion: Option<&Suggestion>,
variable_count: usize,
) -> Result<String> {
env_var::remove(env_var::PREVIEW_COLUMN);
env_var::remove(env_var::PREVIEW_DELIMITER);
env_var::remove(env_var::PREVIEW_MAP);
let mut extra_preview: Option<String> = None;
let (suggestions, initial_opts) = if let Some(s) = suggestion {
let (suggestion_command, suggestion_opts) = s;
if let Some(sopts) = suggestion_opts {
if let Some(c) = &sopts.column {
env_var::set(env_var::PREVIEW_COLUMN, c.to_string());
}
if let Some(d) = &sopts.delimiter {
env_var::set(env_var::PREVIEW_DELIMITER, d);
}
if let Some(m) = &sopts.map {
env_var::set(env_var::PREVIEW_MAP, m);
}
if let Some(p) = &sopts.preview {
extra_preview = Some(p.into());
}
}
let child = shell::out()
.stdout(Stdio::piped())
.arg(&suggestion_command)
.spawn()
.map_err(|e| ShellSpawnError::new(suggestion_command, e))?;
let text = String::from_utf8(
child
.wait_with_output()
.context("Failed to wait and collect output from bash")?
.stdout,
)
.context("Suggestions are invalid utf8")?;
(text, suggestion_opts)
} else {
('\n'.to_string(), &None)
};
let exe = fs::exe_string();
let preview = if cfg!(target_os = "windows") {
format!(
r#"(@echo.{{+}}{eof}{{q}}{eof}{name}{eof}{extra}) | {exe} preview-var-stdin"#,
exe = exe,
name = variable_name,
extra = extra_preview.clone().unwrap_or_default(),
eof = EOF,
)
} else if CONFIG.shell().contains("fish") {
format!(
r#"{exe} preview-var "{{+}}" "{{q}}" "{name}"; {extra}"#,
exe = exe,
name = variable_name,
extra = extra_preview
.clone()
.map(|e| format!(" echo; {}", e))
.unwrap_or_default(),
)
} else {
format!(
r#"{exe} preview-var "$(cat <<{eof}
{{+}}
{eof}
)" "$(cat <<{eof}
{{q}}
{eof}
)" "{name}"; {extra}"#,
exe = exe,
name = variable_name,
extra = extra_preview
.clone()
.map(|e| format!(" echo; {}", e))
.unwrap_or_default(),
eof = EOF,
)
};
let mut opts = FinderOpts {
preview: Some(preview),
..initial_opts.clone().unwrap_or_else(FinderOpts::var_default)
};
opts.query = env_var::get(format!("{}__query", variable_name)).ok();
if let Ok(f) = env_var::get(format!("{}__best", variable_name)) {
opts.filter = Some(f);
opts.suggestion_type = SuggestionType::SingleSelection;
}
if opts.preview_window.is_none() {
opts.preview_window = Some(if extra_preview.is_none() {
format!("up:{}", variable_count + 3)
} else {
"right:50%".to_string()
});
}
if suggestion.is_none() {
opts.suggestion_type = SuggestionType::Disabled;
};
let (output, _, _) = CONFIG
.finder()
.call(opts, |stdin, _| {
stdin
.write_all(suggestions.as_bytes())
.context("Could not write to finder's stdin")?;
Ok(None)
})
.context("finder was unable to prompt with suggestions")?;
Ok(output)
}
fn unique_result_count(results: &[&str]) -> usize {
let mut vars = results.to_owned();
vars.sort_unstable();
vars.dedup();
vars.len()
}
fn replace_variables_from_snippet(snippet: &str, tags: &str, variables: VariableMap) -> Result<String> {
let mut interpolated_snippet = String::from(snippet);
let variables_found: Vec<&str> = writer::VAR_REGEX.find_iter(snippet).map(|m| m.as_str()).collect();
let variable_count = unique_result_count(&variables_found);
for bracketed_variable_name in variables_found {
let variable_name = &bracketed_variable_name[1..bracketed_variable_name.len() - 1];
let env_variable_name = env_var::escape(variable_name);
let env_value = env_var::get(&env_variable_name);
let value = if let Ok(e) = env_value {
e
} else if let Some(suggestion) = variables.get_suggestion(tags, variable_name) {
let mut new_suggestion = suggestion.clone();
new_suggestion.0 = replace_variables_from_snippet(&new_suggestion.0, tags, variables.clone())?;
prompt_finder(variable_name, Some(&new_suggestion), variable_count)?
} else {
prompt_finder(variable_name, None, variable_count)?
};
env_var::set(env_variable_name, &value);
interpolated_snippet = if value.as_str() == "\n" {
interpolated_snippet.replacen(bracketed_variable_name, "", 1)
} else {
interpolated_snippet.replacen(bracketed_variable_name, value.as_str(), 1)
};
}
Ok(interpolated_snippet)
}
pub fn with_absolute_path(snippet: String) -> String {
if let Some(s) = snippet.strip_prefix("navi ") {
return format!("{} {}", fs::exe_string(), s);
}
snippet
}
pub fn act(
extractions: Result<extractor::Output>,
files: Vec<String>,
variables: Option<VariableMap>,
) -> Result<()> {
let (key, tags, comment, snippet, file_index) = extractions.unwrap();
if key == "ctrl-o" {
edit::edit_file(Path::new(&files[file_index.expect("No files found")]))
.expect("Could not open file in external editor");
return Ok(());
}
env_var::set(env_var::PREVIEW_INITIAL_SNIPPET, &snippet);
env_var::set(env_var::PREVIEW_TAGS, &tags);
env_var::set(env_var::PREVIEW_COMMENT, &comment);
let interpolated_snippet = {
let mut s = replace_variables_from_snippet(
snippet,
tags,
variables.expect("No variables received from finder"),
)
.context("Failed to replace variables from snippet")?;
s = with_absolute_path(s);
s = writer::with_new_lines(s);
s
};
match CONFIG.action() {
Action::Print => {
println!("{}", interpolated_snippet);
}
Action::Execute => match key {
"ctrl-y" => {
clipboard::copy(interpolated_snippet)?;
}
_ => {
shell::out()
.arg(&interpolated_snippet[..])
.spawn()
.map_err(|e| ShellSpawnError::new(&interpolated_snippet[..], e))?
.wait()
.context("bash was not running")?;
}
},
};
Ok(())
}