diff --git a/Cargo.toml b/Cargo.toml index 8c5814d2d..ca31553e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ path = "src/bin.rs" [dependencies] chan = "0.1.21" chan-signal = "0.3.1" +nix = "*" melib = { path = "melib", version = "*" } ui = { path = "ui", version = "*" } diff --git a/src/bin.rs b/src/bin.rs index 2c9266880..224623147 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -31,7 +31,7 @@ use ui::*; pub use melib::*; use std::thread; -use std::io::{stdout, stdin, }; +use std::io::{stdout,}; #[macro_use] extern crate chan; @@ -39,13 +39,24 @@ extern crate chan_signal; use chan_signal::Signal; +fn make_input_thread(sx: chan::Sender, rx: chan::Receiver) -> () { + let stdin = std::io::stdin(); + thread::Builder::new().name("input-thread".to_string()).spawn(move || { + get_events(stdin, + |k| { + sx.send(ThreadEvent::Input(k)); + }, + || { + sx.send(ThreadEvent::UIEventType(UIEventType::ChangeMode(UIMode::Fork))); + }, rx)}).unwrap(); + + +} fn main() { - /* Lock all stdios */ + /* Lock all stdio outs */ let _stdout = stdout(); let mut _stdout = _stdout.lock(); - let stdin = stdin(); - let stdin = stdin; /* let _stderr = stderr(); let mut _stderr = _stderr.lock(); @@ -58,15 +69,17 @@ fn main() { * */ let (sender, receiver) = chan::sync(::std::mem::size_of::()); - { - let sender = sender.clone(); - thread::Builder::new().name("input-thread".to_string()).spawn(move || { - get_events(stdin, move | k| { sender.send(ThreadEvent::Input(k)); - })}).unwrap(); - } + + /* + * Create async channel to block the input-thread if we need to fork and stop it from reading + * stdin, see get_events() for details + * */ + let (tx, rx) = chan::async(); + /* Get input thread handle to kill it if we need to */ + make_input_thread(sender.clone(), rx.clone()); /* Create the application State. This is the 'System' part of an ECS architecture */ - let mut state = State::new(_stdout, sender); + let mut state = State::new(_stdout, sender.clone(), tx ); /* Register some reasonably useful interfaces */ let menu = Entity {component: Box::new(AccountMenu::new(&state.context.accounts)) }; @@ -81,23 +94,27 @@ fn main() { state.register_entity(xdg_notifications); /* Keep track of the input mode. See ui::UIMode for details */ - let mut mode: UIMode = UIMode::Normal; 'main: loop { state.render(); + eprintln!("entered main loop"); 'inner: loop { + eprintln!("entered inner loop"); /* Check if any entities have sent reply events to State. */ let events: Vec = state.context.replies(); for e in events { state.rcv_event(e); } + state.redraw(); - /* Poll on all channels. Currently we have the input channel for stdin, watching events and the signal watcher. */ + /* Poll on all channels. Currently we have the input channel for stdin, watching events and the signal watcher. */ chan_select! { receiver.recv() -> r => { + eprintln!("received {:?}", r); match r.unwrap() { ThreadEvent::Input(k) => { - match mode { + eprintln!(" match input"); + match state.mode { UIMode::Normal => { match k { Key::Char('q') | Key::Char('Q') => { @@ -105,8 +122,8 @@ fn main() { break 'main; }, Key::Char(';') => { - mode = UIMode::Execute; - state.rcv_event(UIEvent { id: 0, event_type: UIEventType::ChangeMode(mode)}); + state.mode = UIMode::Execute; + state.rcv_event(UIEvent { id: 0, event_type: UIEventType::ChangeMode(UIMode::Execute)}); state.redraw(); } key => { @@ -118,8 +135,8 @@ fn main() { UIMode::Execute => { match k { Key::Char('\n') | Key::Esc => { - mode = UIMode::Normal; - state.rcv_event(UIEvent { id: 0, event_type: UIEventType::ChangeMode(mode)}); + state.mode = UIMode::Normal; + state.rcv_event(UIEvent { id: 0, event_type: UIEventType::ChangeMode(UIMode::Normal)}); state.redraw(); }, k @ Key::Char(_) => { @@ -129,6 +146,10 @@ fn main() { _ => {}, } }, + UIMode::Fork => { + eprintln!("UIMODE FORK"); + break 'inner; // `goto` 'reap loop, and wait on child. + }, } }, ThreadEvent::RefreshMailbox { name : n } => { @@ -137,20 +158,50 @@ fn main() { /* Don't handle this yet. */ eprintln!("Refresh mailbox {}", n); }, + ThreadEvent::UIEventType(UIEventType::ChangeMode(f)) => { + state.mode = f; + break 'inner; // `goto` 'reap loop, and wait on child. + } ThreadEvent::UIEventType(e) => { + eprintln!(" match event"); state.rcv_event(UIEvent { id: 0, event_type: e}); state.render(); }, } }, signal.recv() -> signal => { - if let Some(Signal::WINCH) = signal { - state.update_size(); - state.render(); - state.redraw(); + if state.mode != UIMode::Fork { + if let Some(Signal::WINCH) = signal { + eprintln!("resize, mode is {:?}", state.mode); + state.update_size(); + state.render(); + state.redraw(); + } } }, } + } // end of 'inner + + 'reap: loop { + eprintln!("reached reap loop"); + match state.try_wait_on_child() { + Some(true) => { + make_input_thread(sender.clone(), rx.clone()); + state.mode = UIMode::Normal; + state.render(); + }, + Some(false) => { + use std::{thread, time}; + + let ten_millis = time::Duration::from_millis(500); + + thread::sleep(ten_millis); + continue 'reap; + }, + None => {break 'reap;}, + } + + } } } diff --git a/ui/Cargo.toml b/ui/Cargo.toml index 13bd15666..d9c862fa6 100644 --- a/ui/Cargo.toml +++ b/ui/Cargo.toml @@ -11,3 +11,4 @@ chan = "0.1.21" notify = "4.0.1" notify-rust = "^3" nom = "3.2.0" +chan-signal = "0.3.1" diff --git a/ui/src/components/mail/listing.rs b/ui/src/components/mail/listing.rs index 7d8a734b7..df3379776 100644 --- a/ui/src/components/mail/listing.rs +++ b/ui/src/components/mail/listing.rs @@ -476,7 +476,42 @@ impl Component for MailListing { UIEventType::Input(Key::Char('\n')) if self.unfocused == false => { self.unfocused = true; self.dirty = true; + }, + UIEventType::Input(Key::Char('m')) if self.unfocused == false => { + use std::process::{Command, Stdio}; + /* Kill input thread so that spawned command can be sole receiver of stdin */ + { + /* I tried thread::park() here but for some reason it never blocked and always + * returned. Spinlocks are also useless because you have to keep the mutex + * guard alive til the child process exits, which requires some effort. + * + * The only problem with this approach is tht the user has to send some input + * in order for the input-thread to wake up and realise it should kill itself. + * + * I tried writing to stdin/tty manually but for some reason rustty didn't + * acknowledge it. + */ + /* + * tx sends to input-thread and it kills itself. + */ + let tx = context.input_thread(); + tx.send(true); + } + + let mut output = Command::new("vim") + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .spawn() + .expect("failed to execute process") ; + + /* + * Main loop will wait on children and when they reap them the loop spawns a new + * input-thread + */ + context.replies.push_back(UIEvent { id: 0, event_type: UIEventType::Fork(output) }); + context.replies.push_back(UIEvent { id: 0, event_type: UIEventType::ChangeMode(UIMode::Fork) }); + return; }, UIEventType::Input(Key::Esc) | UIEventType::Input(Key::Char('i')) if self.unfocused == true => { self.unfocused = false; diff --git a/ui/src/components/utilities.rs b/ui/src/components/utilities.rs index 2ccede031..7f6315b80 100644 --- a/ui/src/components/utilities.rs +++ b/ui/src/components/utilities.rs @@ -328,14 +328,15 @@ impl Component for StatusBar { (set_y(upper_left, get_y(bottom_right) - height + 1), set_y(bottom_right, get_y(bottom_right) - height+1)), context); }, + _ => {}, } } fn process_event(&mut self, event: &UIEvent, context: &mut Context) { self.container.rcv_event(event, context); - match event.event_type { - UIEventType::RefreshMailbox((idx_a, idx_f)) => { - let m = &context.accounts[idx_a][idx_f].as_ref().unwrap().as_ref().unwrap(); + match &event.event_type { + UIEventType::RefreshMailbox((ref idx_a, ref idx_f)) => { + let m = &context.accounts[*idx_a][*idx_f].as_ref().unwrap().as_ref().unwrap(); self.status = format!("{} |Mailbox: {}, Messages: {}, New: {}", self.mode, m.folder.name(), m.collection.len(), m.collection.iter().filter(|e| !e.is_seen()).count()); self.dirty = true; @@ -344,7 +345,7 @@ impl Component for StatusBar { let offset = self.status.find('|').unwrap_or(self.status.len()); self.status.replace_range(..offset, &format!("{} ", m)); self.dirty = true; - self.mode = m; + self.mode = m.clone(); match m { UIMode::Normal => { self.height = 1; @@ -354,11 +355,12 @@ impl Component for StatusBar { UIMode::Execute => { self.height = 2; }, + _ => {}, }; }, UIEventType::ExInput(Key::Char(c)) => { self.dirty = true; - self.ex_buffer.push(c); + self.ex_buffer.push(*c); }, UIEventType::Resize => { self.dirty = true; diff --git a/ui/src/execute/mod.rs b/ui/src/execute/mod.rs index be949f4ac..9b481f432 100644 --- a/ui/src/execute/mod.rs +++ b/ui/src/execute/mod.rs @@ -1,13 +1,18 @@ /*! A parser module for user commands passed through the Ex mode. */ use std; -use nom::digit; +use nom::{digit, alpha}; named!(usize_c, map_res!(map_res!(ws!(digit), std::str::from_utf8), std::str::FromStr::from_str)); named!(pub goto, - preceded!(tag!("b "), - call!(usize_c)) + preceded!(tag!("b "), + call!(usize_c)) + ); + +named!(pub sort<&str>, + preceded!(tag!("sort "), + map_res!(call!(alpha), std::str::from_utf8)) ); diff --git a/ui/src/lib.rs b/ui/src/lib.rs index bc7121862..c4cdcd462 100644 --- a/ui/src/lib.rs +++ b/ui/src/lib.rs @@ -42,6 +42,9 @@ pub use self::components::*; extern crate melib; extern crate notify_rust; +#[macro_use] +extern crate chan; +extern crate chan_signal; use melib::*; use std::io::{Write, }; @@ -54,13 +57,13 @@ use termion::event::{Key as TermionKey, }; use termion::input::TermRead; use termion::screen::AlternateScreen; -extern crate chan; #[macro_use] extern crate nom; use chan::Sender; /// `ThreadEvent` encapsulates all of the possible values we need to transfer between our threads /// to the main process. +#[derive(Debug)] pub enum ThreadEvent { /// User input. Input(Key), @@ -84,6 +87,8 @@ pub enum UIEventType { RefreshMailbox((usize,usize)), //Quit? Resize, + /// Force redraw. + Fork(std::process::Child), ChangeMailbox(usize), ChangeMode(UIMode), Command(String), @@ -98,10 +103,11 @@ pub struct UIEvent { pub event_type: UIEventType, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, PartialEq, Copy, Clone)] pub enum UIMode { Normal, Execute, + Fork, } impl fmt::Display for UIMode { @@ -109,6 +115,7 @@ impl fmt::Display for UIMode { write!(f, "{}", match *self { UIMode::Normal => { "NORMAL" }, UIMode::Execute => { "EX" }, + UIMode::Fork => { "FORK" }, }) } } @@ -131,12 +138,17 @@ pub struct Context { /// Events queue that components send back to the state replies: VecDeque, backends: Backends, + + input_thread: chan::Sender, } impl Context { pub fn replies(&mut self) -> Vec { self.replies.drain(0..).collect() } + pub fn input_thread(&mut self) -> &mut chan::Sender { + &mut self.input_thread + } } @@ -148,9 +160,12 @@ pub struct State { grid: CellBuffer, stdout: termion::screen::AlternateScreen>, + child: Option, + pub mode: UIMode, sender: Sender, entities: Vec, pub context: Context, + } impl Drop for State { @@ -162,7 +177,7 @@ impl Drop for State { } impl State { - pub fn new(stdout: W, sender: Sender) -> Self { + pub fn new(stdout: W, sender: Sender, input_thread: chan::Sender) -> Self { let settings = Settings::new(); let backends = Backends::new(); let stdout = AlternateScreen::from(stdout.into_raw_mode().unwrap()); @@ -179,15 +194,20 @@ impl State { rows: rows, grid: CellBuffer::new(cols, rows, Cell::with_char(' ')), stdout: stdout, + child: None, + mode: UIMode::Normal, sender: sender, entities: Vec::with_capacity(1), + context: Context { accounts: accounts, backends: backends, settings: settings, dirty_areas: VecDeque::with_capacity(5), replies: VecDeque::with_capacity(5), + + input_thread: input_thread, }, }; write!(s.stdout, "{}{}{}", cursor::Hide, clear::All, cursor::Goto(1,1)).unwrap(); @@ -298,6 +318,12 @@ impl State { self.parse_command(cmd); return; }, + UIEventType::Fork(child) => { + self.mode = UIMode::Fork; + self.child = Some(child); + self.stdout.flush().unwrap(); + return; + }, _ => {}, } /* inform each entity */ @@ -320,6 +346,25 @@ impl State { self.rcv_event(UIEvent { id: 0, event_type: UIEventType::RefreshMailbox((account_idx, folder_idx)) }); } } + pub fn try_wait_on_child(&mut self) -> Option { + if { + if let Some(ref mut c) = self.child { + let mut w = c.try_wait(); + match w { + Ok(Some(_)) => { true }, + Ok(None) => { false }, + Err(_) => { return None; }, + } + } else { + return None; + } + } + { + self.child = None; + return Some(true); + } + Some(false) + } } @@ -391,11 +436,31 @@ impl From for Key { } } -pub fn get_events(stdin: std::io::Stdin, mut closure: impl FnMut(Key)) -> (){ - let stdin = stdin.lock(); - for c in stdin.keys() { - if let Ok(k) = c { - closure(Key::from(k)); + +/* + * If we fork (for example start $EDITOR) we want the input-thread to stop reading from stdin. The + * best way I came up with right now is to send a signal to the thread that is read in the first + * input in stdin after the fork, and then the thread kills itself. The parent process spawns a new + * input-thread when the child returns. + * + * The main loop uses try_wait_on_child() to check if child has exited. + */ +pub fn get_events(stdin: std::io::Stdin, mut closure: impl FnMut(Key), mut exit: impl FnMut(), rx: chan::Receiver) -> (){ + for c in stdin.keys() { + chan_select! { + default => {}, + rx.recv() -> val => { + if let Some(true) = val { + exit(); + return; + } + } + + + }; + if let Ok(k) = c { + eprintln!("rcvd {:?}", k); + closure(Key::from(k)); + } } - } }