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 {
which::which(cmd).is_ok()
}