Add child forking functionality

master
Manos Pitsidianakis 2018-07-21 11:20:13 +03:00
parent ac334b09b1
commit b35407bc7f
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
7 changed files with 199 additions and 39 deletions

View File

@ -10,6 +10,7 @@ path = "src/bin.rs"
[dependencies] [dependencies]
chan = "0.1.21" chan = "0.1.21"
chan-signal = "0.3.1" chan-signal = "0.3.1"
nix = "*"
melib = { path = "melib", version = "*" } melib = { path = "melib", version = "*" }
ui = { path = "ui", version = "*" } ui = { path = "ui", version = "*" }

View File

@ -31,7 +31,7 @@ use ui::*;
pub use melib::*; pub use melib::*;
use std::thread; use std::thread;
use std::io::{stdout, stdin, }; use std::io::{stdout,};
#[macro_use] #[macro_use]
extern crate chan; extern crate chan;
@ -39,13 +39,24 @@ extern crate chan_signal;
use chan_signal::Signal; use chan_signal::Signal;
fn make_input_thread(sx: chan::Sender<ThreadEvent>, rx: chan::Receiver<bool>) -> () {
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() { fn main() {
/* Lock all stdios */ /* Lock all stdio outs */
let _stdout = stdout(); let _stdout = stdout();
let mut _stdout = _stdout.lock(); let mut _stdout = _stdout.lock();
let stdin = stdin();
let stdin = stdin;
/* /*
let _stderr = stderr(); let _stderr = stderr();
let mut _stderr = _stderr.lock(); let mut _stderr = _stderr.lock();
@ -58,15 +69,17 @@ fn main() {
* */ * */
let (sender, receiver) = chan::sync(::std::mem::size_of::<ThreadEvent>()); let (sender, receiver) = chan::sync(::std::mem::size_of::<ThreadEvent>());
{
let sender = sender.clone(); /*
thread::Builder::new().name("input-thread".to_string()).spawn(move || { * Create async channel to block the input-thread if we need to fork and stop it from reading
get_events(stdin, move | k| { sender.send(ThreadEvent::Input(k)); * stdin, see get_events() for details
})}).unwrap(); * */
} 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 */ /* 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 */ /* Register some reasonably useful interfaces */
let menu = Entity {component: Box::new(AccountMenu::new(&state.context.accounts)) }; let menu = Entity {component: Box::new(AccountMenu::new(&state.context.accounts)) };
@ -81,23 +94,27 @@ fn main() {
state.register_entity(xdg_notifications); state.register_entity(xdg_notifications);
/* Keep track of the input mode. See ui::UIMode for details */ /* Keep track of the input mode. See ui::UIMode for details */
let mut mode: UIMode = UIMode::Normal;
'main: loop { 'main: loop {
state.render(); state.render();
eprintln!("entered main loop");
'inner: loop { 'inner: loop {
eprintln!("entered inner loop");
/* Check if any entities have sent reply events to State. */ /* Check if any entities have sent reply events to State. */
let events: Vec<UIEvent> = state.context.replies(); let events: Vec<UIEvent> = state.context.replies();
for e in events { for e in events {
state.rcv_event(e); state.rcv_event(e);
} }
state.redraw(); 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! { chan_select! {
receiver.recv() -> r => { receiver.recv() -> r => {
eprintln!("received {:?}", r);
match r.unwrap() { match r.unwrap() {
ThreadEvent::Input(k) => { ThreadEvent::Input(k) => {
match mode { eprintln!(" match input");
match state.mode {
UIMode::Normal => { UIMode::Normal => {
match k { match k {
Key::Char('q') | Key::Char('Q') => { Key::Char('q') | Key::Char('Q') => {
@ -105,8 +122,8 @@ fn main() {
break 'main; break 'main;
}, },
Key::Char(';') => { Key::Char(';') => {
mode = UIMode::Execute; state.mode = UIMode::Execute;
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::ChangeMode(mode)}); state.rcv_event(UIEvent { id: 0, event_type: UIEventType::ChangeMode(UIMode::Execute)});
state.redraw(); state.redraw();
} }
key => { key => {
@ -118,8 +135,8 @@ fn main() {
UIMode::Execute => { UIMode::Execute => {
match k { match k {
Key::Char('\n') | Key::Esc => { Key::Char('\n') | Key::Esc => {
mode = UIMode::Normal; state.mode = UIMode::Normal;
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::ChangeMode(mode)}); state.rcv_event(UIEvent { id: 0, event_type: UIEventType::ChangeMode(UIMode::Normal)});
state.redraw(); state.redraw();
}, },
k @ Key::Char(_) => { 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 } => { ThreadEvent::RefreshMailbox { name : n } => {
@ -137,20 +158,50 @@ fn main() {
/* Don't handle this yet. */ /* Don't handle this yet. */
eprintln!("Refresh mailbox {}", n); eprintln!("Refresh mailbox {}", n);
}, },
ThreadEvent::UIEventType(UIEventType::ChangeMode(f)) => {
state.mode = f;
break 'inner; // `goto` 'reap loop, and wait on child.
}
ThreadEvent::UIEventType(e) => { ThreadEvent::UIEventType(e) => {
eprintln!(" match event");
state.rcv_event(UIEvent { id: 0, event_type: e}); state.rcv_event(UIEvent { id: 0, event_type: e});
state.render(); state.render();
}, },
} }
}, },
signal.recv() -> signal => { signal.recv() -> signal => {
if state.mode != UIMode::Fork {
if let Some(Signal::WINCH) = signal { if let Some(Signal::WINCH) = signal {
eprintln!("resize, mode is {:?}", state.mode);
state.update_size(); state.update_size();
state.render(); state.render();
state.redraw(); 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;},
}
} }
} }
} }

View File

@ -11,3 +11,4 @@ chan = "0.1.21"
notify = "4.0.1" notify = "4.0.1"
notify-rust = "^3" notify-rust = "^3"
nom = "3.2.0" nom = "3.2.0"
chan-signal = "0.3.1"

View File

@ -476,7 +476,42 @@ impl Component for MailListing {
UIEventType::Input(Key::Char('\n')) if self.unfocused == false => { UIEventType::Input(Key::Char('\n')) if self.unfocused == false => {
self.unfocused = true; self.unfocused = true;
self.dirty = 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 => { UIEventType::Input(Key::Esc) | UIEventType::Input(Key::Char('i')) if self.unfocused == true => {
self.unfocused = false; self.unfocused = false;

View File

@ -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)), (set_y(upper_left, get_y(bottom_right) - height + 1), set_y(bottom_right, get_y(bottom_right) - height+1)),
context); context);
}, },
_ => {},
} }
} }
fn process_event(&mut self, event: &UIEvent, context: &mut Context) { fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
self.container.rcv_event(event, context); self.container.rcv_event(event, context);
match event.event_type { match &event.event_type {
UIEventType::RefreshMailbox((idx_a, idx_f)) => { UIEventType::RefreshMailbox((ref idx_a, ref idx_f)) => {
let m = &context.accounts[idx_a][idx_f].as_ref().unwrap().as_ref().unwrap(); 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.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; self.dirty = true;
@ -344,7 +345,7 @@ impl Component for StatusBar {
let offset = self.status.find('|').unwrap_or(self.status.len()); let offset = self.status.find('|').unwrap_or(self.status.len());
self.status.replace_range(..offset, &format!("{} ", m)); self.status.replace_range(..offset, &format!("{} ", m));
self.dirty = true; self.dirty = true;
self.mode = m; self.mode = m.clone();
match m { match m {
UIMode::Normal => { UIMode::Normal => {
self.height = 1; self.height = 1;
@ -354,11 +355,12 @@ impl Component for StatusBar {
UIMode::Execute => { UIMode::Execute => {
self.height = 2; self.height = 2;
}, },
_ => {},
}; };
}, },
UIEventType::ExInput(Key::Char(c)) => { UIEventType::ExInput(Key::Char(c)) => {
self.dirty = true; self.dirty = true;
self.ex_buffer.push(c); self.ex_buffer.push(*c);
}, },
UIEventType::Resize => { UIEventType::Resize => {
self.dirty = true; self.dirty = true;

View File

@ -1,7 +1,7 @@
/*! A parser module for user commands passed through the Ex mode. /*! A parser module for user commands passed through the Ex mode.
*/ */
use std; use std;
use nom::digit; use nom::{digit, alpha};
named!(usize_c<usize>, named!(usize_c<usize>,
@ -11,3 +11,8 @@ named!(pub goto<usize>,
preceded!(tag!("b "), preceded!(tag!("b "),
call!(usize_c)) call!(usize_c))
); );
named!(pub sort<&str>,
preceded!(tag!("sort "),
map_res!(call!(alpha), std::str::from_utf8))
);

View File

@ -42,6 +42,9 @@ pub use self::components::*;
extern crate melib; extern crate melib;
extern crate notify_rust; extern crate notify_rust;
#[macro_use]
extern crate chan;
extern crate chan_signal;
use melib::*; use melib::*;
use std::io::{Write, }; use std::io::{Write, };
@ -54,13 +57,13 @@ use termion::event::{Key as TermionKey, };
use termion::input::TermRead; use termion::input::TermRead;
use termion::screen::AlternateScreen; use termion::screen::AlternateScreen;
extern crate chan;
#[macro_use] #[macro_use]
extern crate nom; extern crate nom;
use chan::Sender; use chan::Sender;
/// `ThreadEvent` encapsulates all of the possible values we need to transfer between our threads /// `ThreadEvent` encapsulates all of the possible values we need to transfer between our threads
/// to the main process. /// to the main process.
#[derive(Debug)]
pub enum ThreadEvent { pub enum ThreadEvent {
/// User input. /// User input.
Input(Key), Input(Key),
@ -84,6 +87,8 @@ pub enum UIEventType {
RefreshMailbox((usize,usize)), RefreshMailbox((usize,usize)),
//Quit? //Quit?
Resize, Resize,
/// Force redraw.
Fork(std::process::Child),
ChangeMailbox(usize), ChangeMailbox(usize),
ChangeMode(UIMode), ChangeMode(UIMode),
Command(String), Command(String),
@ -98,10 +103,11 @@ pub struct UIEvent {
pub event_type: UIEventType, pub event_type: UIEventType,
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, PartialEq, Copy, Clone)]
pub enum UIMode { pub enum UIMode {
Normal, Normal,
Execute, Execute,
Fork,
} }
impl fmt::Display for UIMode { impl fmt::Display for UIMode {
@ -109,6 +115,7 @@ impl fmt::Display for UIMode {
write!(f, "{}", match *self { write!(f, "{}", match *self {
UIMode::Normal => { "NORMAL" }, UIMode::Normal => { "NORMAL" },
UIMode::Execute => { "EX" }, UIMode::Execute => { "EX" },
UIMode::Fork => { "FORK" },
}) })
} }
} }
@ -131,12 +138,17 @@ pub struct Context {
/// Events queue that components send back to the state /// Events queue that components send back to the state
replies: VecDeque<UIEvent>, replies: VecDeque<UIEvent>,
backends: Backends, backends: Backends,
input_thread: chan::Sender<bool>,
} }
impl Context { impl Context {
pub fn replies(&mut self) -> Vec<UIEvent> { pub fn replies(&mut self) -> Vec<UIEvent> {
self.replies.drain(0..).collect() self.replies.drain(0..).collect()
} }
pub fn input_thread(&mut self) -> &mut chan::Sender<bool> {
&mut self.input_thread
}
} }
@ -148,9 +160,12 @@ pub struct State<W: Write> {
grid: CellBuffer, grid: CellBuffer,
stdout: termion::screen::AlternateScreen<termion::raw::RawTerminal<W>>, stdout: termion::screen::AlternateScreen<termion::raw::RawTerminal<W>>,
child: Option<std::process::Child>,
pub mode: UIMode,
sender: Sender<ThreadEvent>, sender: Sender<ThreadEvent>,
entities: Vec<Entity>, entities: Vec<Entity>,
pub context: Context, pub context: Context,
} }
impl<W: Write> Drop for State<W> { impl<W: Write> Drop for State<W> {
@ -162,7 +177,7 @@ impl<W: Write> Drop for State<W> {
} }
impl<W: Write> State<W> { impl<W: Write> State<W> {
pub fn new(stdout: W, sender: Sender<ThreadEvent>) -> Self { pub fn new(stdout: W, sender: Sender<ThreadEvent>, input_thread: chan::Sender<bool>) -> Self {
let settings = Settings::new(); let settings = Settings::new();
let backends = Backends::new(); let backends = Backends::new();
let stdout = AlternateScreen::from(stdout.into_raw_mode().unwrap()); let stdout = AlternateScreen::from(stdout.into_raw_mode().unwrap());
@ -179,15 +194,20 @@ impl<W: Write> State<W> {
rows: rows, rows: rows,
grid: CellBuffer::new(cols, rows, Cell::with_char(' ')), grid: CellBuffer::new(cols, rows, Cell::with_char(' ')),
stdout: stdout, stdout: stdout,
child: None,
mode: UIMode::Normal,
sender: sender, sender: sender,
entities: Vec::with_capacity(1), entities: Vec::with_capacity(1),
context: Context { context: Context {
accounts: accounts, accounts: accounts,
backends: backends, backends: backends,
settings: settings, settings: settings,
dirty_areas: VecDeque::with_capacity(5), dirty_areas: VecDeque::with_capacity(5),
replies: 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(); write!(s.stdout, "{}{}{}", cursor::Hide, clear::All, cursor::Goto(1,1)).unwrap();
@ -298,6 +318,12 @@ impl<W: Write> State<W> {
self.parse_command(cmd); self.parse_command(cmd);
return; return;
}, },
UIEventType::Fork(child) => {
self.mode = UIMode::Fork;
self.child = Some(child);
self.stdout.flush().unwrap();
return;
},
_ => {}, _ => {},
} }
/* inform each entity */ /* inform each entity */
@ -320,6 +346,25 @@ impl<W: Write> State<W> {
self.rcv_event(UIEvent { id: 0, event_type: UIEventType::RefreshMailbox((account_idx, folder_idx)) }); self.rcv_event(UIEvent { id: 0, event_type: UIEventType::RefreshMailbox((account_idx, folder_idx)) });
} }
} }
pub fn try_wait_on_child(&mut self) -> Option<bool> {
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,10 +436,30 @@ impl From<TermionKey> for Key {
} }
} }
pub fn get_events(stdin: std::io::Stdin, mut closure: impl FnMut(Key)) -> (){
let stdin = stdin.lock(); /*
* 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<bool>) -> (){
for c in stdin.keys() { for c in stdin.keys() {
chan_select! {
default => {},
rx.recv() -> val => {
if let Some(true) = val {
exit();
return;
}
}
};
if let Ok(k) = c { if let Ok(k) = c {
eprintln!("rcvd {:?}", k);
closure(Key::from(k)); closure(Key::from(k));
} }
} }