Skip to content

Commit

Permalink
zsh support
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonmadigan committed May 7, 2024
1 parent b351eca commit 579ddfc
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 15 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.vscode/launch.json

# Cargo build dir
/target

Expand Down
110 changes: 106 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ serde_json = "1.0.97"
serde_yaml = "0.9.22"
thiserror = "1.0.40"
which = "4.4"
ctrlc = "3.4.4"

# The profile that 'cargo dist' will build with
[profile.dist]
Expand Down
6 changes: 5 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ enum Shell {
#[default]
Bash,
Python,
Zsh,
#[value(skip)]
Custom {
program: String,
Expand All @@ -315,6 +316,7 @@ impl Display for Shell {
match self {
Self::Bash => f.write_str("bash"),
Self::Python => f.write_str("python"),
Self::Zsh => f.write_str("zsh"),
Self::Custom { program, args, .. } => f.write_str(
&iter::once(program)
.chain(args)
Expand Down Expand Up @@ -342,7 +344,7 @@ impl Merge for Shell {
impl Shell {
fn line_split(&self) -> &str {
match self {
Self::Bash | Self::Python => " \\",
Self::Bash | Self::Python | Self::Zsh => " \\",
Self::Custom { line_split, .. } => line_split,
}
}
Expand All @@ -351,6 +353,7 @@ impl Shell {
match self {
Self::Bash => "bash",
Self::Python => "python",
Self::Zsh => "zsh",
Self::Custom { program, .. } => program,
}
}
Expand All @@ -370,6 +373,7 @@ impl Shell {
match self {
Self::Bash => spawn::bash(timeout, environment, width, height),
Self::Python => spawn::python(timeout, environment, width, height),
Self::Zsh => spawn::zsh(timeout, environment, width, height),
Self::Custom {
program,
args,
Expand Down
4 changes: 4 additions & 0 deletions src/config/de/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::config::Shell;
enum Variant {
Bash,
Python,
Zsh,
Custom,
}

Expand Down Expand Up @@ -41,6 +42,7 @@ impl<'de> de::Visitor<'de> for Visitor {
match v {
"bash" | "Bash" => Ok(Shell::Bash),
"python" | "Python" => Ok(Shell::Python),
"zsh" | "Zsh" => Ok(Shell::Zsh),
_ => Err(E::invalid_value(
de::Unexpected::Str(v),
&"supported shell (e.g. bash or python) or a custom shell",
Expand All @@ -57,6 +59,7 @@ impl<'de> de::Visitor<'de> for Visitor {
match tag {
Variant::Bash => variant.unit_variant().map(|_| Shell::Bash),
Variant::Python => variant.unit_variant().map(|_| Shell::Python),
Variant::Zsh => variant.unit_variant().map(|_| Shell::Zsh),
Variant::Custom => variant.struct_variant(CUSTOM_FIELDS, CustomVisitor),
}
}
Expand Down Expand Up @@ -109,6 +112,7 @@ mod tests {
fn visit_str() -> serde_yaml::Result<()> {
assert_eq!(serde_yaml::from_str::<Shell>("bash")?, Shell::Bash);
assert_eq!(serde_yaml::from_str::<Shell>("python")?, Shell::Python);
assert_eq!(serde_yaml::from_str::<Shell>("zsh")?, Shell::Zsh);
assert!(serde_yaml::from_str::<Shell>("custom").is_err());
Ok(())
}
Expand Down
57 changes: 47 additions & 10 deletions src/config/spawn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,42 @@ use expectrl::{
};
use os_str_bytes::OsStrBytes;



use crate::asciicast::Event;

pub(super) fn zsh<I, K, V>(
timeout: Duration,
environment: I,
width: u16,
height: u16,
) -> color_eyre::Result<ShellSession>
where
I: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
println!("Spawning zsh with timeout: {timeout:?}");
const PROMPT: &str = "AUTOCAST_PROMPT";
const PROMPT_COMMAND: &str = "PS1=AUTOCAST_PROMPT; unset PROMPT_COMMAND; bindkey -v";

let mut command = Command::new("zsh");
command.arg("--no-rcs");
command
.envs(environment)
.env("PS1", PROMPT)
.env("PROMPT_COMMAND", PROMPT_COMMAND);

ShellSession::spawn(
command,
width,
height,
String::from(PROMPT),
Some(String::from("exit")),
timeout,
)
}

pub(super) fn bash<I, K, V>(
timeout: Duration,
environment: I,
Expand Down Expand Up @@ -78,6 +112,7 @@ pub struct ShellSession<P = OsProcess, S = OsProcessStream> {
prompt: String,
quit_command: Option<String>,
timeout: Duration,
#[allow(dead_code)]
process: P,
stream: Stream<S>,
last_event: Instant,
Expand Down Expand Up @@ -174,6 +209,7 @@ impl<P, S: Write> ShellSession<P, S> {
const LINE_ENDING: &str = "\r\n";

self.send(line)?;

let line_ending: &OsStr = LINE_ENDING.as_ref();
self.send(line_ending.to_raw_bytes())
}
Expand Down Expand Up @@ -208,19 +244,20 @@ where
}
}

impl<P: Process + Wait, S: Write> ShellSession<P, S> {
/// Sends the quit command to the shell.
/// Blocks until the shell process has exited.
impl<P: Process + Wait, S: Write + Read + NonBlocking> ShellSession<P, S> {
/// Sends the quit command to the shell and attempts to terminate the shell process immediately.
pub fn quit(&mut self) -> color_eyre::Result<()> {
if let Some(quit_command) = &self.quit_command {
let quit_command = quit_command.clone();
self.send_line(quit_command)
.wrap_err("error sending quit command to shell")?;
println!("Attempting to quit shell session...");

if let Some(quit_command) = self.quit_command.clone() {
println!("Sending quit command to shell: {quit_command}");
self.send_line(&quit_command)?;
}

self.process
.wait(self.timeout)
.wrap_err("error waiting for shell to stop")?;
ctrlc::set_handler(move || {
std::process::exit(0);
})
.expect("Error setting Ctrl-C handler");

Ok(())
}
Expand Down

0 comments on commit 579ddfc

Please sign in to comment.