use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum ShellType {
Bash,
Zsh,
Fish,
#[default]
Unknown,
}
impl ShellType {
fn from_path(path: &str) -> Self {
if path.contains("zsh") {
Self::Zsh
} else if path.contains("bash") {
Self::Bash
} else if path.contains("fish") {
Self::Fish
} else {
Self::Unknown
}
}
pub fn detect() -> Self {
if let Ok(shell) = std::env::var("SHELL") {
let t = Self::from_path(&shell);
if t != Self::Unknown {
return t;
}
}
#[cfg(target_os = "macos")]
{
if let Some(t) = Self::detect_via_dscl() {
return t;
}
}
#[cfg(unix)]
{
if let Some(t) = Self::detect_from_passwd() {
return t;
}
}
Self::Unknown
}
#[cfg(target_os = "macos")]
fn detect_via_dscl() -> Option<Self> {
let user = std::env::var("USER")
.or_else(|_| std::env::var("LOGNAME"))
.ok()?;
let output = std::process::Command::new("dscl")
.args([".", "-read", &format!("/Users/{}", user), "UserShell"])
.output()
.ok()?;
let text = String::from_utf8_lossy(&output.stdout);
let shell_path = text.split_whitespace().last()?;
let t = Self::from_path(shell_path);
if t != Self::Unknown { Some(t) } else { None }
}
#[cfg(unix)]
fn detect_from_passwd() -> Option<Self> {
let user = std::env::var("USER")
.or_else(|_| std::env::var("LOGNAME"))
.ok()?;
let contents = std::fs::read_to_string("/etc/passwd").ok()?;
for line in contents.lines() {
let parts: Vec<&str> = line.splitn(7, ':').collect();
if parts.len() == 7 && parts[0] == user {
let t = Self::from_path(parts[6]);
if t != Self::Unknown {
return Some(t);
}
}
}
None
}
pub fn display_name(&self) -> &'static str {
match self {
Self::Bash => "Bash",
Self::Zsh => "Zsh",
Self::Fish => "Fish",
Self::Unknown => "Unknown",
}
}
pub fn extension(&self) -> &'static str {
match self {
Self::Bash => "bash",
Self::Zsh => "zsh",
Self::Fish => "fish",
Self::Unknown => "sh",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum ShellExitAction {
#[default]
Close,
Keep,
RestartImmediately,
RestartWithPrompt,
RestartAfterDelay,
}
impl ShellExitAction {
pub fn display_name(&self) -> &'static str {
match self {
Self::Close => "Close tab/pane",
Self::Keep => "Keep open",
Self::RestartImmediately => "Restart immediately",
Self::RestartWithPrompt => "Restart with prompt",
Self::RestartAfterDelay => "Restart after 1s delay",
}
}
pub fn all() -> &'static [ShellExitAction] {
&[
ShellExitAction::Close,
ShellExitAction::Keep,
ShellExitAction::RestartImmediately,
ShellExitAction::RestartWithPrompt,
ShellExitAction::RestartAfterDelay,
]
}
pub fn is_restart(&self) -> bool {
matches!(
self,
Self::RestartImmediately | Self::RestartWithPrompt | Self::RestartAfterDelay
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum StartupDirectoryMode {
#[default]
Home,
Previous,
Custom,
}
impl StartupDirectoryMode {
pub fn display_name(&self) -> &'static str {
match self {
Self::Home => "Home Directory",
Self::Previous => "Previous Session",
Self::Custom => "Custom Directory",
}
}
pub fn all() -> &'static [StartupDirectoryMode] {
&[
StartupDirectoryMode::Home,
StartupDirectoryMode::Previous,
StartupDirectoryMode::Custom,
]
}
}