aboutsummaryrefslogtreecommitdiffstats
path: root/atuin-common
diff options
context:
space:
mode:
Diffstat (limited to 'atuin-common')
-rw-r--r--atuin-common/src/shell.rs107
1 files changed, 94 insertions, 13 deletions
diff --git a/atuin-common/src/shell.rs b/atuin-common/src/shell.rs
index 2c0f3534..42e32f72 100644
--- a/atuin-common/src/shell.rs
+++ b/atuin-common/src/shell.rs
@@ -1,3 +1,6 @@
+use std::{ffi::OsStr, path::Path, process::Command};
+
+use serde::Serialize;
use sysinfo::{get_current_pid, Process, System};
use thiserror::Error;
@@ -12,7 +15,24 @@ pub enum Shell {
Unknown,
}
-#[derive(Debug, Error)]
+impl std::fmt::Display for Shell {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let shell = match self {
+ Shell::Bash => "bash",
+ Shell::Fish => "fish",
+ Shell::Zsh => "zsh",
+ Shell::Nu => "nu",
+ Shell::Xonsh => "xonsh",
+ Shell::Sh => "sh",
+
+ Shell::Unknown => "unknown",
+ };
+
+ write!(f, "{}", shell)
+ }
+}
+
+#[derive(Debug, Error, Serialize)]
pub enum ShellError {
#[error("shell not supported")]
NotSupported,
@@ -21,28 +41,89 @@ pub enum ShellError {
ExecError(String),
}
-pub fn shell() -> Shell {
- let name = shell_name(None);
+impl Shell {
+ pub fn current() -> Shell {
+ let sys = System::new_all();
+
+ let process = sys
+ .process(get_current_pid().expect("Failed to get current PID"))
+ .expect("Process with current pid does not exist");
- match name.as_str() {
- "bash" => Shell::Bash,
- "fish" => Shell::Fish,
- "zsh" => Shell::Zsh,
- "xonsh" => Shell::Xonsh,
- "nu" => Shell::Nu,
- "sh" => Shell::Sh,
+ let parent = sys
+ .process(process.parent().expect("Atuin running with no parent!"))
+ .expect("Process with parent pid does not exist");
- _ => Shell::Unknown,
+ let shell = parent.name().trim().to_lowercase();
+ let shell = shell.strip_prefix('-').unwrap_or(&shell);
+
+ Shell::from_string(shell.to_string())
+ }
+
+ /// Best-effort attempt to determine the default shell
+ /// This implementation will be different across different platforms
+ /// Caller should ensure to handle Shell::Unknown correctly
+ pub fn default_shell() -> Result<Shell, ShellError> {
+ let sys = System::name().unwrap_or("".to_string()).to_lowercase();
+
+ // TODO: Support Linux
+ // I'm pretty sure we can use /etc/passwd there, though there will probably be some issues
+ if sys.contains("darwin") {
+ // This works in my testing so far
+ let path = Shell::Sh.run_interactive([
+ "dscl localhost -read \"/Local/Default/Users/$USER\" shell | awk '{print $2}'",
+ ])?;
+
+ let path = Path::new(path.trim());
+
+ let shell = path.file_name();
+
+ if shell.is_none() {
+ return Err(ShellError::NotSupported);
+ }
+
+ Ok(Shell::from_string(
+ shell.unwrap().to_string_lossy().to_string(),
+ ))
+ } else {
+ Err(ShellError::NotSupported)
+ }
+ }
+
+ pub fn from_string(name: String) -> Shell {
+ match name.as_str() {
+ "bash" => Shell::Bash,
+ "fish" => Shell::Fish,
+ "zsh" => Shell::Zsh,
+ "xonsh" => Shell::Xonsh,
+ "nu" => Shell::Nu,
+ "sh" => Shell::Sh,
+
+ _ => Shell::Unknown,
+ }
}
-}
-impl Shell {
/// Returns true if the shell is posix-like
/// Note that while fish is not posix compliant, it behaves well enough for our current
/// featureset that this does not matter.
pub fn is_posixish(&self) -> bool {
matches!(self, Shell::Bash | Shell::Fish | Shell::Zsh)
}
+
+ pub fn run_interactive<I, S>(&self, args: I) -> Result<String, ShellError>
+ where
+ I: IntoIterator<Item = S>,
+ S: AsRef<OsStr>,
+ {
+ let shell = self.to_string();
+
+ let output = Command::new(shell)
+ .arg("-ic")
+ .args(args)
+ .output()
+ .map_err(|e| ShellError::ExecError(e.to_string()))?;
+
+ Ok(String::from_utf8(output.stdout).unwrap())
+ }
}
pub fn shell_name(parent: Option<&Process>) -> String {