diff --git a/src/errors.rs b/src/errors.rs index bbe85ec8..cb483bbc 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -266,6 +266,8 @@ impl From<&PluginInstruction> for PluginContext { #[derive(Debug, Clone, Copy, PartialEq)] pub enum AppContext { + GetState, + SetState, Exit, Error, } @@ -273,6 +275,8 @@ pub enum AppContext { impl From<&AppInstruction> for AppContext { fn from(app_instruction: &AppInstruction) -> Self { match *app_instruction { + AppInstruction::GetState(_) => AppContext::GetState, + AppInstruction::SetState(_) => AppContext::SetState, AppInstruction::Exit => AppContext::Exit, AppInstruction::Error(_) => AppContext::Error, } diff --git a/src/input.rs b/src/input.rs index c947b7fb..40967f57 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,9 +1,8 @@ -/// Module for handling input -use crate::os_input_output::OsApi; use crate::pty_bus::PtyInstruction; use crate::screen::ScreenInstruction; use crate::CommandIsExecuting; use crate::{errors::ContextType, wasm_vm::PluginInstruction}; +use crate::{os_input_output::OsApi, update_state, AppState}; use crate::{AppInstruction, SenderWithContext, OPENCALLS}; struct InputHandler { @@ -45,6 +44,9 @@ impl InputHandler { self.send_app_instructions.update(err_ctx); self.send_screen_instructions.update(err_ctx); loop { + update_state(&self.send_app_instructions, |_| AppState { + input_mode: self.mode, + }); match self.mode { InputMode::Normal => self.read_normal_mode(), InputMode::Command => self.read_command_mode(false), @@ -112,12 +114,10 @@ impl InputHandler { // multiple commands. If we're already in persistent mode, it'll return us to normal mode. match self.mode { InputMode::Command => self.mode = InputMode::CommandPersistent, - InputMode::CommandPersistent => { - self.mode = InputMode::Normal; - return; - } + InputMode::CommandPersistent => self.mode = InputMode::Normal, _ => panic!(), } + return; } [27] => { // Esc @@ -262,7 +262,7 @@ impl InputHandler { self.command_is_executing.wait_until_pane_is_closed(); } //@@@khs26 Write this to the powerbar? - _ => {} + _ => continue, } if self.mode == InputMode::Command { @@ -299,7 +299,7 @@ impl InputHandler { /// normal mode /// - Exiting means that we should start the shutdown process for mosaic or the given /// input handler -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone, Copy)] pub enum InputMode { Normal, Command, @@ -307,6 +307,36 @@ pub enum InputMode { Exiting, } +// FIXME: This should be auto-generated from the soon-to-be-added `get_default_keybinds` +pub fn get_help(mode: &InputMode) -> Vec { + let command_help = vec![ + " Split".into(), + " Resize".into(), + "

Focus Next".into(), + " Close Pane".into(), + " Quit".into(), + " Scroll".into(), + "<1> New Tab".into(), + "<2> Next Tab".into(), + "<3> Last Tab".into(), + ]; + match mode { + InputMode::Normal => vec![" Command Mode".into()], + InputMode::Command => [ + vec![ + " Persistent Mode".into(), + " Normal Mode".into(), + ], + command_help, + ] + .concat(), + InputMode::CommandPersistent => { + [vec![" Normal Mode".into()], command_help].concat() + } + InputMode::Exiting => vec!["Bye from Mosaic!".into()], + } +} + /// Entry point to the module that instantiates a new InputHandler and calls its /// reading loop pub fn input_loop( diff --git a/src/main.rs b/src/main.rs index c449f89a..bf39d7f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,14 +16,15 @@ mod utils; mod wasm_vm; -use std::cell::RefCell; use std::collections::HashMap; use std::io::Write; use std::os::unix::net::UnixStream; use std::path::PathBuf; use std::sync::mpsc::{channel, sync_channel, Receiver, SendError, Sender, SyncSender}; use std::thread; +use std::{cell::RefCell, sync::mpsc::TrySendError}; +use input::InputMode; use panes::PaneId; use serde::{Deserialize, Serialize}; use structopt::StructOpt; @@ -85,6 +86,14 @@ impl SenderWithContext { } } + pub fn try_send(&self, event: T) -> Result<(), TrySendError<(T, ErrorContext)>> { + if let SenderType::SyncSender(ref s) = self.sender { + s.try_send((event, self.err_ctx)) + } else { + panic!("try_send can only be called on SyncSenders!") + } + } + pub fn update(&mut self, new_ctx: ErrorContext) { self.err_ctx = new_ctx; } @@ -125,13 +134,44 @@ pub fn main() { } } +// FIXME: It would be good to add some more things to this over time +#[derive(Debug, Clone)] +pub struct AppState { + pub input_mode: InputMode, +} + +impl Default for AppState { + fn default() -> Self { + Self { + input_mode: InputMode::Normal, + } + } +} + +// FIXME: Make this a method on the big `Communication` struct, so that app_tx can be extracted +// from self instead of being explicitly passed here +pub fn update_state( + app_tx: &SenderWithContext, + update_fn: impl FnOnce(AppState) -> AppState, +) { + let (state_tx, state_rx) = channel(); + + drop(app_tx.send(AppInstruction::GetState(state_tx))); + let state = state_rx.recv().unwrap(); + + drop(app_tx.send(AppInstruction::SetState(update_fn(state)))) +} + #[derive(Clone)] pub enum AppInstruction { + GetState(Sender), + SetState(AppState), Exit, Error(String), } pub fn start(mut os_input: Box, opts: CliArgs) { + let mut app_state = AppState::default(); let mut active_threads = vec![]; let command_is_executing = CommandIsExecuting::new(); @@ -399,6 +439,7 @@ pub fn start(mut os_input: Box, opts: CliArgs) { .spawn({ let mut send_pty_instructions = send_pty_instructions.clone(); let mut send_screen_instructions = send_screen_instructions.clone(); + let mut send_app_instructions = send_app_instructions.clone(); move || { let store = Store::default(); @@ -413,6 +454,7 @@ pub fn start(mut os_input: Box, opts: CliArgs) { err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event))); send_screen_instructions.update(err_ctx); send_pty_instructions.update(err_ctx); + send_app_instructions.update(err_ctx); match event { PluginInstruction::Load(pid_tx, path) => { // FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that @@ -441,6 +483,7 @@ pub fn start(mut os_input: Box, opts: CliArgs) { plugin_id, send_pty_instructions: send_pty_instructions.clone(), send_screen_instructions: send_screen_instructions.clone(), + send_app_instructions: send_app_instructions.clone(), wasi_env, }; @@ -602,6 +645,8 @@ pub fn start(mut os_input: Box, opts: CliArgs) { send_screen_instructions.update(err_ctx); send_pty_instructions.update(err_ctx); match app_instruction { + AppInstruction::GetState(state_tx) => drop(state_tx.send(app_state.clone())), + AppInstruction::SetState(state) => app_state = state, AppInstruction::Exit => { let _ = send_screen_instructions.send(ScreenInstruction::Quit); let _ = send_pty_instructions.send(PtyInstruction::Quit); diff --git a/src/wasm_vm.rs b/src/wasm_vm.rs index acb9e013..97a1a587 100644 --- a/src/wasm_vm.rs +++ b/src/wasm_vm.rs @@ -1,8 +1,14 @@ -use std::{path::PathBuf, sync::mpsc::Sender}; +use std::{ + path::PathBuf, + sync::mpsc::{channel, Sender}, +}; use wasmer::{imports, Function, ImportObject, Store, WasmerEnv}; use wasmer_wasi::WasiEnv; -use crate::{panes::PaneId, pty_bus::PtyInstruction, screen::ScreenInstruction, SenderWithContext}; +use crate::{ + input::get_help, panes::PaneId, pty_bus::PtyInstruction, screen::ScreenInstruction, + AppInstruction, SenderWithContext, +}; #[derive(Clone, Debug)] pub enum PluginInstruction { @@ -18,6 +24,7 @@ pub enum PluginInstruction { pub struct PluginEnv { pub plugin_id: u32, pub send_screen_instructions: SenderWithContext, + pub send_app_instructions: SenderWithContext, pub send_pty_instructions: SenderWithContext, // FIXME: This should be a big bundle of all of the channels pub wasi_env: WasiEnv, } @@ -29,6 +36,7 @@ pub fn mosaic_imports(store: &Store, plugin_env: &PluginEnv) -> ImportObject { "mosaic" => { "host_open_file" => Function::new_native_with_env(store, plugin_env.clone(), host_open_file), "host_set_selectable" => Function::new_native_with_env(store, plugin_env.clone(), host_set_selectable), + "host_get_help" => Function::new_native_with_env(store, plugin_env.clone(), host_get_help), } } } @@ -54,6 +62,21 @@ fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) { .unwrap() } +fn host_get_help(plugin_env: &PluginEnv) { + let (state_tx, state_rx) = channel(); + // FIXME: If I changed the application so that threads were sent the termination + // signal and joined one at a time, there would be an order to shutdown, so I + // could get rid of this .is_ok() check and the .try_send() + if plugin_env + .send_app_instructions + .try_send(AppInstruction::GetState(state_tx)) + .is_ok() + { + let help = get_help(&state_rx.recv().unwrap().input_mode); + wasi_write_string(&plugin_env.wasi_env, &serde_json::to_string(&help).unwrap()); + } +} + // Helper Functions --------------------------------------------------------------------------------------------------- // FIXME: Unwrap city diff --git a/status-bar.wasm b/status-bar.wasm old mode 100755 new mode 100644 index 39e081ff..07f2adf7 Binary files a/status-bar.wasm and b/status-bar.wasm differ