use std::ffi::OsStr;
use std::path::Path;
use std::process::Command;
use crate::app::state::FileOpeners;
const TEXT_EXTENSIONS: &[&str] = &[
"rs", "py", "js", "ts", "jsx", "tsx", "go", "c", "cpp", "h", "hpp",
"java", "kt", "swift", "rb", "php", "cs", "fs", "scala", "clj",
"hs", "ml", "elm", "ex", "exs", "erl", "lua", "r", "jl", "nim",
"zig", "v", "d", "pas", "pl", "pm", "tcl", "awk", "sed",
"html", "htm", "css", "scss", "sass", "less", "vue", "svelte",
"json", "yaml", "yml", "toml", "xml", "ini", "conf", "cfg",
"sh", "bash", "zsh", "fish", "ps1", "bat", "cmd",
"txt", "rst", "adoc", "org", "tex", "bib",
"sql", "graphql", "proto", "dockerfile", "makefile", "cmake",
"gitignore", "gitattributes", "editorconfig",
];
pub enum OpenResult {
Opened,
NeedsSuspend(Command),
#[allow(dead_code)]
NotSupported(String),
Error(String),
}
pub fn open_file(
path: &Path,
openers: &FileOpeners,
editor_config: &opensesame::EditorConfig,
) -> OpenResult {
let extension = path
.extension()
.and_then(OsStr::to_str)
.map(|s| s.to_lowercase());
match extension.as_deref() {
Some("md" | "markdown") => open_markdown(path, openers),
Some(ext) if is_text_extension(ext) => open_with_editor(path, editor_config),
_ => open_with_system(path, openers),
}
}
fn is_text_extension(ext: &str) -> bool {
TEXT_EXTENSIONS.contains(&ext)
}
fn open_markdown(path: &Path, openers: &FileOpeners) -> OpenResult {
let opener = &openers.md;
#[cfg(feature = "treemd")]
{
if opener == "treemd" {
let mut cmd = Command::new("treemd");
cmd.arg(path);
return OpenResult::NeedsSuspend(cmd);
}
}
if opener == "treemd" {
if which_exists("treemd") {
let mut cmd = Command::new("treemd");
cmd.arg(path);
return OpenResult::NeedsSuspend(cmd);
} else {
return open_with_system(path, openers);
}
}
if opener == "editor" {
return open_with_editor(path, &opensesame::EditorConfig::default());
}
if opener == "open" || opener == "system" {
return open_with_system(path, openers);
}
let mut cmd = Command::new(opener);
cmd.arg(path);
OpenResult::NeedsSuspend(cmd)
}
fn open_with_editor(path: &Path, config: &opensesame::EditorConfig) -> OpenResult {
let editor = config
.editor
.clone()
.or_else(|| std::env::var("VISUAL").ok())
.or_else(|| std::env::var("EDITOR").ok())
.unwrap_or_else(|| "vim".to_string());
let mut cmd = Command::new(&editor);
cmd.arg(path);
cmd.stdin(std::process::Stdio::inherit())
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::inherit());
OpenResult::NeedsSuspend(cmd)
}
fn open_with_system(path: &Path, openers: &FileOpeners) -> OpenResult {
let opener = &openers.default;
if opener == "open" || opener == "system" {
match open::that(path) {
Ok(()) => OpenResult::Opened,
Err(e) => OpenResult::Error(format!("Failed to open with system: {e}")),
}
} else {
let mut cmd = Command::new(opener);
cmd.arg(path);
OpenResult::NeedsSuspend(cmd)
}
}
fn which_exists(cmd: &str) -> bool {
std::process::Command::new("which")
.arg(cmd)
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}