use std::process::Command;
use crate::api::PreviewCommand;
use super::builtin::run_builtin_preview;
use super::cache::{PreviewGeometry, PreviewPayload};
#[cfg(feature = "image")]
use super::image::{preview_image_from_output, preview_image_from_path_text};
pub(crate) fn run_preview_command(
command: &PreviewCommand,
shell: Option<&str>,
item: &str,
geometry: PreviewGeometry,
) -> PreviewPayload {
#[cfg(feature = "image")]
if let Some(image) = preview_image_from_path_text(item) {
return PreviewPayload::Image(image);
}
match command {
PreviewCommand::Shell(template) => {
run_shell_preview_command(template, shell, item, geometry)
}
PreviewCommand::Builtin { text_extensions } => run_builtin_preview(item, text_extensions),
}
}
fn run_shell_preview_command(
template: &str,
shell: Option<&str>,
item: &str,
geometry: PreviewGeometry,
) -> PreviewPayload {
let command = expand_preview_template(template, item);
let output = preview_shell_command(&command, shell, geometry).output();
match output {
Ok(output) => {
if !output.stdout.is_empty() {
#[cfg(feature = "image")]
if let Some(image) = preview_image_from_output(&output.stdout) {
return PreviewPayload::Image(image);
}
#[cfg(feature = "image")]
if output.status.success() {
if let Some(image) = preview_image_from_path_text(item) {
return PreviewPayload::Image(image);
}
}
let stdout = String::from_utf8_lossy(&output.stdout);
return PreviewPayload::Text(stdout.into_owned());
}
let stderr = String::from_utf8_lossy(&output.stderr);
if !stderr.is_empty() {
return PreviewPayload::Text(stderr.into_owned());
}
if output.status.success() {
#[cfg(feature = "image")]
if let Some(image) = preview_image_from_path_text(item) {
return PreviewPayload::Image(image);
}
PreviewPayload::Text(String::new())
} else {
PreviewPayload::Text(format!("preview exited with {}", output.status))
}
}
Err(error) => PreviewPayload::Text(format!("preview failed: {error}")),
}
}
fn expand_preview_template(template: &str, item: &str) -> String {
if template.contains("{}") {
template.replace("{}", &shell_quote(item))
} else {
template.to_string()
}
}
#[cfg(not(windows))]
fn shell_quote(value: &str) -> String {
format!("'{}'", value.replace('\'', "'\"'\"'"))
}
#[cfg(windows)]
fn shell_quote(value: &str) -> String {
format!("'{}'", value.replace('\'', "''"))
}
#[cfg(not(windows))]
fn preview_shell_command(command: &str, shell: Option<&str>, geometry: PreviewGeometry) -> Command {
let shell = shell
.map(str::to_string)
.or_else(|| std::env::var("SHELL").ok())
.unwrap_or_else(|| "sh".to_string());
let mut parts = shell.split_whitespace();
let program = parts.next().unwrap_or("sh");
let mut process = Command::new(program);
let shell_args: Vec<_> = parts.collect();
if shell_args.is_empty() {
process.arg("-c");
} else {
process.args(shell_args);
}
process.arg(command);
apply_preview_env(&mut process, geometry);
process
}
#[cfg(windows)]
fn preview_shell_command(command: &str, shell: Option<&str>, geometry: PreviewGeometry) -> Command {
let shell = shell
.map(str::to_string)
.or_else(|| std::env::var("YURU_WINDOWS_SHELL").ok())
.unwrap_or_else(|| "powershell.exe".to_string());
let mut parts = shell.split_whitespace();
let program = parts.next().unwrap_or("powershell.exe");
let mut process = Command::new(program);
let shell_args: Vec<_> = parts.collect();
if shell_args.is_empty() {
process.args(["-NoLogo", "-NoProfile", "-Command"]);
} else {
process.args(shell_args);
}
process.arg(command);
apply_preview_env(&mut process, geometry);
process
}
fn apply_preview_env(process: &mut Command, geometry: PreviewGeometry) {
process
.env("FZF_PREVIEW_COLUMNS", geometry.columns.to_string())
.env("FZF_PREVIEW_LINES", geometry.lines.to_string())
.env("FZF_PREVIEW_LEFT", geometry.left.to_string())
.env("FZF_PREVIEW_TOP", geometry.top.to_string());
}