mirror of
https://github.com/denisidoro/navi
synced 2024-11-28 14:30:22 +00:00
0f95a29dfd
Fixes #584
240 lines
7.2 KiB
Rust
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(())
|
|
}
|