From 6cd4319fcf540ef70f74cc2f10d0d4297ee7b788 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Thu, 11 Apr 2024 16:59:01 +0100 Subject: feat(gui): add base structure (#1935) * initial * ui things * cargo * update, add history refresh button * history page a bit better, add initial dotfiles page * re-org layout * bye squigglies * add dotfiles ui, show aliases * add default shell detection * put stats in a little drawer, alias import changes * use new table for aliases, add alias deleting * support adding aliases * close drawer when added, no alias autocomplete * clippy, format * attempt to ensure gdk is installed ok * sudo * no linux things on mac ffs * I forgot we build for windows too... end of day * remove tauri backend from workspace --- atuin-common/src/shell.rs | 107 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 94 insertions(+), 13 deletions(-) (limited to 'atuin-common/src/shell.rs') 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 { + 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(&self, args: I) -> Result + where + I: IntoIterator, + S: AsRef, + { + 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 { -- cgit v1.3.1