summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock3
-rw-r--r--Cargo.toml4
-rw-r--r--src/parse.rs96
-rw-r--r--src/shell.pest18
-rw-r--r--src/state/history.rs239
-rw-r--r--src/state/mod.rs6
6 files changed, 183 insertions, 183 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 047deb5..fbde278 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -95,8 +95,7 @@ dependencies = [
[[package]]
name = "async-process"
version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83137067e3a2a6a06d67168e49e68a0957d215410473a740cea95a2425c0b7c6"
+source = "git+https://github.com/doy/async-process?branch=status-drop#4438e61dac21d58c29ab4cc7d8152c829977a62d"
dependencies = [
"async-io",
"blocking",
diff --git a/Cargo.toml b/Cargo.toml
index b805342..18b07ed 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -24,4 +24,6 @@ unicode-width = "0.1.9"
users = "0.11.0"
vt100 = "0.15.0"
-[features]
+[patch.crates-io]
+# https://github.com/smol-rs/async-process/pull/19
+async-process = { git = "https://github.com/doy/async-process", branch = "status-drop" }
diff --git a/src/parse.rs b/src/parse.rs
index abeefb0..4ddffba 100644
--- a/src/parse.rs
+++ b/src/parse.rs
@@ -30,21 +30,15 @@ impl Word {
pub struct Exe {
exe: Word,
args: Vec<Word>,
- original: String,
}
impl Exe {
- fn parse(pair: pest::iterators::Pair<Rule>) -> Self {
+ fn build_ast(pair: pest::iterators::Pair<Rule>) -> Self {
assert!(matches!(pair.as_rule(), Rule::exe));
- let original = pair.as_str().to_string();
let mut iter = pair.into_inner();
let exe = Word::new(iter.next().unwrap().as_str());
let args = iter.map(|word| Word::new(word.as_str())).collect();
- Self {
- exe,
- args,
- original,
- }
+ Self { exe, args }
}
pub fn exe(&self) -> &str {
@@ -54,72 +48,62 @@ impl Exe {
pub fn args(&self) -> impl Iterator<Item = &str> {
self.args.iter().map(|arg| arg.word.as_ref())
}
+}
+
+#[derive(Debug, Clone)]
+pub struct Pipeline {
+ exes: Vec<Exe>,
+}
- pub fn input_string(&self) -> String {
- self.original.clone()
+impl Pipeline {
+ pub fn exes(&self) -> &[Exe] {
+ &self.exes
+ }
+
+ fn build_ast(pipeline: pest::iterators::Pair<Rule>) -> Self {
+ assert!(matches!(pipeline.as_rule(), Rule::pipeline));
+ Self {
+ exes: pipeline.into_inner().map(Exe::build_ast).collect(),
+ }
}
}
#[derive(Debug, Clone)]
-pub enum Command {
- Exe(Exe),
- And(Exe, Box<Command>),
- Or(Exe, Box<Command>),
- Both(Exe, Box<Command>),
- Pipe(Exe, Box<Command>),
+pub struct Commands {
+ pipelines: Vec<Pipeline>,
+ input_string: String,
}
-impl Command {
+impl Commands {
pub fn parse(full_cmd: &str) -> Self {
Self::build_ast(
Shell::parse(Rule::line, full_cmd)
.unwrap()
.next()
.unwrap()
- .into_inner(),
+ .into_inner()
+ .next()
+ .unwrap(),
)
}
- pub fn input_string(&self) -> String {
- match self {
- Self::Exe(exe) => exe.input_string(),
- Self::And(exe, command) => format!(
- "{} && {}",
- exe.input_string(),
- command.input_string()
- ),
- Self::Or(exe, command) => format!(
- "{} || {}",
- exe.input_string(),
- command.input_string()
- ),
- Self::Both(exe, command) => {
- format!("{}; {}", exe.input_string(), command.input_string())
- }
- Self::Pipe(exe, command) => {
- format!("{} | {}", exe.input_string(), command.input_string())
- }
- }
+ pub fn pipelines(&self) -> &[Pipeline] {
+ &self.pipelines
}
- fn build_ast(mut pairs: pest::iterators::Pairs<Rule>) -> Self {
- let command = pairs.next().unwrap();
- assert!(matches!(command.as_rule(), Rule::command));
- let mut inner = command.into_inner();
- let exe = inner.next().unwrap();
- let exe = Exe::parse(exe);
- if let Some(rest) = inner.next() {
- let rule = rest.as_rule();
- let ast = Self::build_ast(rest.into_inner());
- match rule {
- Rule::and => Self::And(exe, Box::new(ast)),
- Rule::or => Self::Or(exe, Box::new(ast)),
- Rule::both => Self::Both(exe, Box::new(ast)),
- Rule::pipe => Self::Pipe(exe, Box::new(ast)),
- _ => unreachable!(),
- }
- } else {
- Self::Exe(exe)
+ pub fn input_string(&self) -> &str {
+ &self.input_string
+ }
+
+ fn build_ast(commands: pest::iterators::Pair<Rule>) -> Self {
+ assert!(matches!(commands.as_rule(), Rule::commands));
+ let input_string = commands.as_str().to_string();
+ Self {
+ pipelines: commands
+ .into_inner()
+ .map(Pipeline::build_ast)
+ .collect(),
+ input_string,
}
}
}
diff --git a/src/shell.pest b/src/shell.pest
index f1f39ab..49a84b4 100644
--- a/src/shell.pest
+++ b/src/shell.pest
@@ -1,17 +1,11 @@
-char = { ASCII_ALPHANUMERIC }
-
+char = @{ !("|" | ";" | WHITESPACE) ~ ANY }
word = @{ char+ }
-exe = { word+ }
-
-and = { "&&" ~ command }
-or = { "||" ~ command }
-both = { ";" ~ command }
-pipe = { "|" ~ command }
-
-command = { exe ~ (and | or | both | pipe)? }
+exe = { word+ }
+pipeline = { exe ~ ("|" ~ exe)* }
+commands = { pipeline ~ (";" ~ pipeline)* }
-line = { SOI ~ command ~ EOI }
+line = { SOI ~ commands ~ EOI }
-WHITESPACE = _{ " " }
+WHITESPACE = _{ (" " | "\t" | "\n") }
COMMENT = _{ "#" ~ ANY* }
diff --git a/src/state/history.rs b/src/state/history.rs
index 685650a..b98bf60 100644
--- a/src/state/history.rs
+++ b/src/state/history.rs
@@ -85,49 +85,24 @@ impl History {
pub async fn run(
&mut self,
- cmd: &crate::parse::Command,
+ commands: &crate::parse::Commands,
event_w: async_std::channel::Sender<crate::event::Event>,
) -> anyhow::Result<usize> {
let (input_w, input_r) = async_std::channel::unbounded();
let (resize_w, resize_r) = async_std::channel::unbounded();
let entry = async_std::sync::Arc::new(async_std::sync::Mutex::new(
- Entry::new(cmd.clone(), self.size, input_w, resize_w),
+ Entry::new(commands.clone(), self.size, input_w, resize_w),
));
- // for now
- let cmd = match cmd {
- crate::parse::Command::Exe(exe) => exe,
- _ => todo!(),
- };
-
- if crate::builtins::is(cmd.exe()) {
- let code: i32 =
- crate::builtins::run(cmd.exe(), cmd.args()).into();
- entry.lock_arc().await.exit_info = Some(ExitInfo::new(
- async_std::process::ExitStatus::from_raw(code << 8),
- ));
- event_w
- .send(crate::event::Event::ProcessExit)
- .await
- .unwrap();
- } else {
- let mut process = async_std::process::Command::new(cmd.exe());
- process.args(cmd.args());
- let child = process
- .spawn_pty(Some(&pty_process::Size::new(
- self.size.0,
- self.size.1,
- )))
- .unwrap();
- run_process(
- child,
- async_std::sync::Arc::clone(&entry),
- input_r,
- resize_r,
- event_w,
- );
- }
+ run_commands(
+ commands.clone(),
+ async_std::sync::Arc::clone(&entry),
+ input_r,
+ resize_r,
+ event_w,
+ );
+
self.entries.push(entry);
Ok(self.entries.len() - 1)
}
@@ -248,7 +223,7 @@ impl std::iter::DoubleEndedIterator for VisibleEntries {
}
pub struct Entry {
- cmd: crate::parse::Command,
+ commands: crate::parse::Commands,
vt: vt100::Parser,
audible_bell_state: usize,
visual_bell_state: usize,
@@ -262,13 +237,13 @@ pub struct Entry {
impl Entry {
fn new(
- cmd: crate::parse::Command,
+ commands: crate::parse::Commands,
size: (u16, u16),
input: async_std::channel::Sender<Vec<u8>>,
resize: async_std::channel::Sender<(u16, u16)>,
) -> Self {
Self {
- cmd,
+ commands,
vt: vt100::Parser::new(size.0, size.1, 0),
audible_bell_state: 0,
visual_bell_state: 0,
@@ -320,7 +295,7 @@ impl Entry {
if self.running() {
out.set_bgcolor(textmode::Color::Rgb(16, 64, 16));
}
- out.write_str(&self.cmd.input_string());
+ out.write_str(self.commands.input_string());
out.reset_attributes();
set_bgcolor(out, focused);
@@ -429,8 +404,8 @@ impl Entry {
}
}
- pub fn cmd(&self) -> String {
- self.cmd.input_string()
+ pub fn cmd(&self) -> &str {
+ self.commands.input_string()
}
pub fn toggle_fullscreen(&mut self) {
@@ -500,87 +475,133 @@ impl ExitInfo {
}
}
-fn run_process(
- mut child: pty_process::async_std::Child,
+fn run_commands(
+ commands: crate::parse::Commands,
entry: async_std::sync::Arc<async_std::sync::Mutex<Entry>>,
input_r: async_std::channel::Receiver<Vec<u8>>,
resize_r: async_std::channel::Receiver<(u16, u16)>,
event_w: async_std::channel::Sender<crate::event::Event>,
) {
async_std::task::spawn(async move {
- loop {
- enum Res {
- Read(Result<usize, std::io::Error>),
- Write(Result<Vec<u8>, async_std::channel::RecvError>),
- Resize(Result<(u16, u16), async_std::channel::RecvError>),
+ for pipeline in commands.pipelines() {
+ assert_eq!(pipeline.exes().len(), 1);
+ for exe in pipeline.exes() {
+ run_exe(
+ exe,
+ async_std::sync::Arc::clone(&entry),
+ input_r.clone(),
+ resize_r.clone(),
+ event_w.clone(),
+ )
+ .await;
}
- let mut buf = [0_u8; 4096];
- let mut pty = child.pty();
- let read = async { Res::Read(pty.read(&mut buf).await) };
- let write = async { Res::Write(input_r.recv().await) };
- let resize = async { Res::Resize(resize_r.recv().await) };
- match read.race(write).race(resize).await {
- Res::Read(res) => match res {
- Ok(bytes) => {
- let mut entry = entry.lock_arc().await;
- let pre_alternate_screen =
- entry.vt.screen().alternate_screen();
- entry.vt.process(&buf[..bytes]);
- let post_alternate_screen =
- entry.vt.screen().alternate_screen();
- if entry.fullscreen.is_none()
- && pre_alternate_screen != post_alternate_screen
- {
- event_w
- .send(crate::event::Event::ProcessAlternateScreen)
- .await
- .unwrap();
- }
- event_w
- .send(crate::event::Event::ProcessOutput)
- .await
- .unwrap();
- }
- Err(e) => {
- if e.raw_os_error() != Some(libc::EIO) {
- eprintln!("pty read failed: {:?}", e);
- }
- // XXX not sure if this is safe - are we sure
- // the child exited?
- entry.lock_arc().await.exit_info = Some(
- ExitInfo::new(child.status().await.unwrap()),
- );
+ }
+ });
+}
+
+async fn run_exe(
+ exe: &crate::parse::Exe,
+ entry: async_std::sync::Arc<async_std::sync::Mutex<Entry>>,
+ input_r: async_std::channel::Receiver<Vec<u8>>,
+ resize_r: async_std::channel::Receiver<(u16, u16)>,
+ event_w: async_std::channel::Sender<crate::event::Event>,
+) {
+ if crate::builtins::is(exe.exe()) {
+ let code: i32 = crate::builtins::run(exe.exe(), exe.args()).into();
+ entry.lock_arc().await.exit_info = Some(ExitInfo::new(
+ async_std::process::ExitStatus::from_raw(code << 8),
+ ));
+ event_w
+ .send(crate::event::Event::ProcessExit)
+ .await
+ .unwrap();
+ return;
+ }
+
+ let mut process = async_std::process::Command::new(exe.exe());
+ process.args(exe.args());
+ let size = entry.lock_arc().await.vt.screen().size();
+ let child = process
+ .spawn_pty(Some(&pty_process::Size::new(size.0, size.1)))
+ .unwrap();
+ loop {
+ enum Res {
+ Read(Result<usize, std::io::Error>),
+ Write(Result<Vec<u8>, async_std::channel::RecvError>),
+ Resize(Result<(u16, u16), async_std::channel::RecvError>),
+ Exit(
+ Result<async_std::process::ExitStatus, async_std::io::Error>,
+ ),
+ }
+ let mut buf = [0_u8; 4096];
+ let mut pty = child.pty();
+ let read = async { Res::Read(pty.read(&mut buf).await) };
+ let write = async { Res::Write(input_r.recv().await) };
+ let resize = async { Res::Resize(resize_r.recv().await) };
+ let exit = async { Res::Exit(child.status_no_drop().await) };
+ match read.race(write).race(resize).or(exit).await {
+ Res::Read(res) => match res {
+ Ok(bytes) => {
+ let mut entry = entry.lock_arc().await;
+ let pre_alternate_screen =
+ entry.vt.screen().alternate_screen();
+ entry.vt.process(&buf[..bytes]);
+ let post_alternate_screen =
+ entry.vt.screen().alternate_screen();
+ if entry.fullscreen.is_none()
+ && pre_alternate_screen != post_alternate_screen
+ {
event_w
- .send(crate::event::Event::ProcessExit)
+ .send(crate::event::Event::ProcessAlternateScreen)
.await
.unwrap();
- break;
- }
- },
- Res::Write(res) => match res {
- Ok(bytes) => {
- pty.write(&bytes).await.unwrap();
- }
- Err(e) => {
- panic!("failed to read from input channel: {}", e);
- }
- },
- Res::Resize(res) => match res {
- Ok(size) => {
- child
- .resize_pty(&pty_process::Size::new(
- size.0, size.1,
- ))
- .unwrap();
- entry.lock_arc().await.vt.set_size(size.0, size.1);
}
- Err(e) => {
- panic!("failed to read from resize channel: {}", e);
+ event_w
+ .send(crate::event::Event::ProcessOutput)
+ .await
+ .unwrap();
+ }
+ Err(e) => {
+ if e.raw_os_error() != Some(libc::EIO) {
+ panic!("pty read failed: {:?}", e);
}
- },
- }
+ }
+ },
+ Res::Write(res) => match res {
+ Ok(bytes) => {
+ pty.write(&bytes).await.unwrap();
+ }
+ Err(e) => {
+ panic!("failed to read from input channel: {}", e);
+ }
+ },
+ Res::Resize(res) => match res {
+ Ok(size) => {
+ child
+ .resize_pty(&pty_process::Size::new(size.0, size.1))
+ .unwrap();
+ entry.lock_arc().await.vt.set_size(size.0, size.1);
+ }
+ Err(e) => {
+ panic!("failed to read from resize channel: {}", e);
+ }
+ },
+ Res::Exit(res) => match res {
+ Ok(status) => {
+ entry.lock_arc().await.exit_info =
+ Some(ExitInfo::new(status));
+ event_w
+ .send(crate::event::Event::ProcessExit)
+ .await
+ .unwrap();
+ break;
+ }
+ Err(e) => {
+ panic!("failed to get exit status: {}", e);
+ }
+ },
}
- });
+ }
}
fn set_bgcolor(out: &mut impl textmode::Textmode, focus: bool) {
diff --git a/src/state/mod.rs b/src/state/mod.rs
index 8547fc1..5ecfa6e 100644
--- a/src/state/mod.rs
+++ b/src/state/mod.rs
@@ -237,7 +237,7 @@ impl State {
textmode::Key::Char(' ') => {
if let Some(idx) = self.focus_idx() {
let entry = self.history.entry(idx).await;
- self.readline.set_input(&entry.cmd());
+ self.readline.set_input(entry.cmd());
self.set_focus(Focus::Readline, Some(entry)).await;
}
}
@@ -440,8 +440,8 @@ impl State {
self.focus_idx().map_or(Focus::Readline, Focus::History)
}
- fn parse(&self, cmd: &str) -> crate::parse::Command {
- let cmd = crate::parse::Command::parse(cmd);
+ fn parse(&self, cmd: &str) -> crate::parse::Commands {
+ let cmd = crate::parse::Commands::parse(cmd);
// todo: interpolate
cmd
}