diff options
| author | denis <denismaximov98@gmail.com> | 2021-05-01 13:45:01 +0300 |
|---|---|---|
| committer | denis <denismaximov98@gmail.com> | 2021-05-01 13:45:01 +0300 |
| commit | eabecde90b1617ce4c29be9d5af3f52ccb67e45f (patch) | |
| tree | 0d0dafa594269045f084383a9719f2a4995fe7ea /src/common | |
| parent | wip: more cleaning up (diff) | |
| parent | docs(changelog): performance improvements (diff) | |
| download | sl0ppij-eabecde90b1617ce4c29be9d5af3f52ccb67e45f.tar.gz sl0ppij-eabecde90b1617ce4c29be9d5af3f52ccb67e45f.tar.xz sl0ppij-eabecde90b1617ce4c29be9d5af3f52ccb67e45f.zip | |
chore: painless merge this time
Diffstat (limited to 'src/common')
| -rw-r--r-- | src/common/errors.rs | 2 | ||||
| -rw-r--r-- | src/common/input/actions.rs | 2 | ||||
| -rw-r--r-- | src/common/input/config.rs | 58 | ||||
| -rw-r--r-- | src/common/input/handler.rs | 16 | ||||
| -rw-r--r-- | src/common/input/keybinds.rs | 286 | ||||
| -rw-r--r-- | src/common/install.rs | 46 | ||||
| -rw-r--r-- | src/common/mod.rs | 40 | ||||
| -rw-r--r-- | src/common/screen.rs | 18 | ||||
| -rw-r--r-- | src/common/utils/consts.rs | 6 | ||||
| -rw-r--r-- | src/common/wasm_vm.rs | 106 |
10 files changed, 230 insertions, 350 deletions
diff --git a/src/common/errors.rs b/src/common/errors.rs index dd675738..c1c6753f 100644 --- a/src/common/errors.rs +++ b/src/common/errors.rs @@ -200,6 +200,7 @@ pub enum ScreenContext { PageScrollDown, ClearScroll, CloseFocusedPane, + ToggleActiveSyncPanes, ToggleActiveTerminalFullscreen, SetSelectable, SetInvisibleBorders, @@ -260,6 +261,7 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::UpdateTabName(_) => ScreenContext::UpdateTabName, ScreenInstruction::TerminalResize => ScreenContext::TerminalResize, ScreenInstruction::ChangeMode(_) => ScreenContext::ChangeMode, + ScreenInstruction::ToggleActiveSyncPanes => ScreenContext::ToggleActiveSyncPanes, } } } diff --git a/src/common/input/actions.rs b/src/common/input/actions.rs index 3bcadf15..46a8e1d6 100644 --- a/src/common/input/actions.rs +++ b/src/common/input/actions.rs @@ -39,6 +39,8 @@ pub enum Action { PageScrollDown, /// Toggle between fullscreen focus pane and normal layout. ToggleFocusFullscreen, + /// Toggle between sending text commands to all panes and normal mode. + ToggleActiveSyncPanes, /// Open a new pane in the specified direction (relative to focus). /// If no direction is specified, will try to use the biggest available space. NewPane(Option<Direction>), diff --git a/src/common/input/config.rs b/src/common/input/config.rs index 542116d9..62ec3dfe 100644 --- a/src/common/input/config.rs +++ b/src/common/input/config.rs @@ -7,8 +7,8 @@ use std::path::{Path, PathBuf}; use super::keybinds::{Keybinds, KeybindsFromYaml}; use crate::cli::ConfigCli; +use crate::common::install; -use directories_next::ProjectDirs; use serde::Deserialize; type ConfigResult = Result<Config, ConfigError>; @@ -33,6 +33,8 @@ pub enum ConfigError { Io(io::Error), // Io error with path context IoPath(io::Error, PathBuf), + // Internal Deserialisation Error + FromUtf8(std::string::FromUtf8Error), } impl Default for Config { @@ -63,18 +65,11 @@ impl Config { } } - /// Deserializes the config from a default platform specific path, - /// merges the default configuration - options take precedence. - fn from_default_path() -> ConfigResult { - let project_dirs = ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap(); - let mut config_path: PathBuf = project_dirs.config_dir().to_owned(); - config_path.push("config.yaml"); - - match Config::new(&config_path) { - Ok(config) => Ok(config), - Err(ConfigError::IoPath(_, _)) => Ok(Config::default()), - Err(e) => Err(e), - } + /// Gets default configuration from assets + // TODO Deserialize the Configuration from bytes &[u8], + // once serde-yaml supports zero-copy + pub fn from_default_assets() -> ConfigResult { + Self::from_yaml(String::from_utf8(install::DEFAULT_CONFIG.to_vec())?.as_str()) } /// Entry point of the configuration @@ -82,20 +77,37 @@ impl Config { pub fn from_cli_config( location: Option<PathBuf>, cli_config: Option<ConfigCli>, + config_dir: Option<PathBuf>, ) -> ConfigResult { if let Some(path) = location { return Config::new(&path); } - match cli_config { - Some(ConfigCli::Config { clean, .. }) if clean => Ok(Config::default()), - Some(_) | None => Ok(Config::from_default_path()?), + if let Some(ConfigCli::Config { clean, .. }) = cli_config { + if clean { + return Config::from_default_assets(); + } + } + + if let Some(config) = config_dir { + let path = config.join("config.yaml"); + if path.exists() { + Config::new(&path) + } else { + Config::from_default_assets() + } + } else { + Config::from_default_assets() } } /// In order not to mess up tests from changing configurations #[cfg(test)] - pub fn from_cli_config(_: Option<PathBuf>, _: Option<ConfigCli>) -> ConfigResult { + pub fn from_cli_config( + _: Option<PathBuf>, + _: Option<ConfigCli>, + _: Option<PathBuf>, + ) -> ConfigResult { Ok(Config::default()) } } @@ -108,6 +120,7 @@ impl Display for ConfigError { write!(formatter, "IoError: {}, File: {}", err, path.display(),) } ConfigError::Serde(ref err) => write!(formatter, "Deserialisation error: {}", err), + ConfigError::FromUtf8(ref err) => write!(formatter, "FromUtf8Error: {}", err), } } } @@ -118,6 +131,7 @@ impl std::error::Error for ConfigError { ConfigError::Io(ref err) => Some(err), ConfigError::IoPath(ref err, _) => Some(err), ConfigError::Serde(ref err) => Some(err), + ConfigError::FromUtf8(ref err) => Some(err), } } } @@ -134,6 +148,12 @@ impl From<serde_yaml::Error> for ConfigError { } } +impl From<std::string::FromUtf8Error> for ConfigError { + fn from(err: std::string::FromUtf8Error) -> ConfigError { + ConfigError::FromUtf8(err) + } +} + // The unit test location. #[cfg(test)] mod config_test { @@ -142,14 +162,14 @@ mod config_test { #[test] fn clean_option_equals_default_config() { let cli_config = ConfigCli::Config { clean: true }; - let config = Config::from_cli_config(None, Some(cli_config)).unwrap(); + let config = Config::from_cli_config(None, Some(cli_config), None).unwrap(); let default = Config::default(); assert_eq!(config, default); } #[test] fn no_config_option_file_equals_default_config() { - let config = Config::from_cli_config(None, None).unwrap(); + let config = Config::from_cli_config(None, None, None).unwrap(); let default = Config::default(); assert_eq!(config, default); } diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs index bbb594a3..eda71eea 100644 --- a/src/common/input/handler.rs +++ b/src/common/input/handler.rs @@ -59,7 +59,6 @@ impl InputHandler { fn handle_input(&mut self) { let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow()); err_ctx.add_call(ContextType::StdinHandler); - let keybinds = self.config.keybinds.clone(); let alt_left_bracket = vec![27, 91]; loop { if self.should_exit { @@ -71,14 +70,14 @@ impl InputHandler { Ok((event, raw_bytes)) => match event { termion::event::Event::Key(key) => { let key = cast_termion_key(key); - self.handle_key(&key, raw_bytes, &keybinds); + self.handle_key(&key, raw_bytes); } termion::event::Event::Unsupported(unsupported_key) => { // we have to do this because of a bug in termion // this should be a key event and not an unsupported event if unsupported_key == alt_left_bracket { let key = Key::Alt('['); - self.handle_key(&key, raw_bytes, &keybinds); + self.handle_key(&key, raw_bytes); } } termion::event::Event::Mouse(_) => { @@ -91,8 +90,9 @@ impl InputHandler { } } } - fn handle_key(&mut self, key: &Key, raw_bytes: Vec<u8>, keybinds: &Keybinds) { - for action in Keybinds::key_to_actions(&key, raw_bytes, &self.mode, &keybinds) { + fn handle_key(&mut self, key: &Key, raw_bytes: Vec<u8>) { + let keybinds = &self.config.keybinds; + for action in Keybinds::key_to_actions(&key, raw_bytes, &self.mode, keybinds) { let should_exit = self.dispatch_action(action); if should_exit { self.should_exit = true; @@ -245,6 +245,11 @@ impl InputHandler { .send(ScreenInstruction::SwitchTabPrev) .unwrap(); } + Action::ToggleActiveSyncPanes => { + self.send_screen_instructions + .send(ScreenInstruction::ToggleActiveSyncPanes) + .unwrap(); + } Action::CloseTab => { self.command_is_executing.closing_pane(); self.send_screen_instructions @@ -295,6 +300,7 @@ pub fn get_mode_info(mode: InputMode) -> ModeInfo { keybinds.push(("d".to_string(), "Down split".to_string())); keybinds.push(("r".to_string(), "Right split".to_string())); keybinds.push(("x".to_string(), "Close".to_string())); + keybinds.push(("s".to_string(), "Sync".to_string())); keybinds.push(("f".to_string(), "Fullscreen".to_string())); } InputMode::Tab => { diff --git a/src/common/input/keybinds.rs b/src/common/input/keybinds.rs index ee039f83..9842fbe2 100644 --- a/src/common/input/keybinds.rs +++ b/src/common/input/keybinds.rs @@ -1,7 +1,8 @@ //! Mapping of inputs to sequences of actions. use std::collections::HashMap; -use super::actions::{Action, Direction}; +use super::actions::Action; +use super::config; use serde::Deserialize; use strum::IntoEnumIterator; @@ -57,14 +58,9 @@ enum Unbind { impl Default for Keybinds { fn default() -> Keybinds { - let mut defaults = Keybinds::new(); - - for mode in InputMode::iter() { - defaults - .0 - .insert(mode, Keybinds::get_defaults_for_mode(&mode)); - } - defaults + config::Config::from_default_assets() + .expect("Keybinds from default assets Error") + .keybinds } } @@ -109,278 +105,6 @@ impl Keybinds { keybinds } - /// Returns the default keybinds for a given [`InputMode`]. - fn get_defaults_for_mode(mode: &InputMode) -> ModeKeybinds { - let mut defaults = HashMap::new(); - - match *mode { - InputMode::Normal => { - defaults.insert( - Key::Ctrl('g'), - vec![Action::SwitchToMode(InputMode::Locked)], - ); - defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]); - defaults.insert( - Key::Ctrl('r'), - vec![Action::SwitchToMode(InputMode::Resize)], - ); - defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]); - defaults.insert( - Key::Ctrl('s'), - vec![Action::SwitchToMode(InputMode::Scroll)], - ); - defaults.insert(Key::Ctrl('q'), vec![Action::Quit]); - - defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]); - defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]); - defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]); - defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]); - defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]); - defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]); - defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]); - } - InputMode::Locked => { - defaults.insert( - Key::Ctrl('g'), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - } - InputMode::Resize => { - defaults.insert( - Key::Ctrl('g'), - vec![Action::SwitchToMode(InputMode::Locked)], - ); - defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]); - defaults.insert( - Key::Ctrl('r'), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]); - defaults.insert( - Key::Ctrl('s'), - vec![Action::SwitchToMode(InputMode::Scroll)], - ); - defaults.insert(Key::Ctrl('q'), vec![Action::Quit]); - defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]); - defaults.insert( - Key::Char('\n'), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - defaults.insert( - Key::Char(' '), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - - defaults.insert(Key::Char('h'), vec![Action::Resize(Direction::Left)]); - defaults.insert(Key::Char('j'), vec![Action::Resize(Direction::Down)]); - defaults.insert(Key::Char('k'), vec![Action::Resize(Direction::Up)]); - defaults.insert(Key::Char('l'), vec![Action::Resize(Direction::Right)]); - - defaults.insert(Key::Left, vec![Action::Resize(Direction::Left)]); - defaults.insert(Key::Down, vec![Action::Resize(Direction::Down)]); - defaults.insert(Key::Up, vec![Action::Resize(Direction::Up)]); - defaults.insert(Key::Right, vec![Action::Resize(Direction::Right)]); - - defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]); - defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]); - defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]); - defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]); - defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]); - defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]); - defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]); - } - InputMode::Pane => { - defaults.insert( - Key::Ctrl('g'), - vec![Action::SwitchToMode(InputMode::Locked)], - ); - defaults.insert( - Key::Ctrl('p'), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - defaults.insert( - Key::Ctrl('r'), - vec![Action::SwitchToMode(InputMode::Resize)], - ); - defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]); - defaults.insert( - Key::Ctrl('s'), - vec![Action::SwitchToMode(InputMode::Scroll)], - ); - defaults.insert(Key::Ctrl('q'), vec![Action::Quit]); - defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]); - defaults.insert( - Key::Char('\n'), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - defaults.insert( - Key::Char(' '), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - - defaults.insert(Key::Char('h'), vec![Action::MoveFocus(Direction::Left)]); - defaults.insert(Key::Char('j'), vec![Action::MoveFocus(Direction::Down)]); - defaults.insert(Key::Char('k'), vec![Action::MoveFocus(Direction::Up)]); - defaults.insert(Key::Char('l'), vec![Action::MoveFocus(Direction::Right)]); - - defaults.insert(Key::Left, vec![Action::MoveFocus(Direction::Left)]); - defaults.insert(Key::Down, vec![Action::MoveFocus(Direction::Down)]); - defaults.insert(Key::Up, vec![Action::MoveFocus(Direction::Up)]); - defaults.insert(Key::Right, vec![Action::MoveFocus(Direction::Right)]); - - defaults.insert(Key::Char('p'), vec![Action::SwitchFocus]); - defaults.insert(Key::Char('n'), vec![Action::NewPane(None)]); - defaults.insert(Key::Char('d'), vec![Action::NewPane(Some(Direction::Down))]); - defaults.insert( - Key::Char('r'), - vec![Action::NewPane(Some(Direction::Right))], - ); - defaults.insert(Key::Char('x'), vec![Action::CloseFocus]); - defaults.insert(Key::Char('f'), vec![Action::ToggleFocusFullscreen]); - - defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]); - defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]); - defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]); - defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]); - defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]); - defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]); - defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]); - } - InputMode::Tab => { - defaults.insert( - Key::Ctrl('g'), - vec![Action::SwitchToMode(InputMode::Locked)], - ); - defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]); - defaults.insert( - Key::Ctrl('r'), - vec![Action::SwitchToMode(InputMode::Resize)], - ); - defaults.insert( - Key::Ctrl('t'), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - defaults.insert( - Key::Ctrl('s'), - vec![Action::SwitchToMode(InputMode::Scroll)], - ); - defaults.insert(Key::Ctrl('q'), vec![Action::Quit]); - defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]); - defaults.insert( - Key::Char('\n'), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - defaults.insert( - Key::Char(' '), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - - defaults.insert(Key::Char('h'), vec![Action::GoToPreviousTab]); - defaults.insert(Key::Char('j'), vec![Action::GoToNextTab]); - defaults.insert(Key::Char('k'), vec![Action::GoToPreviousTab]); - defaults.insert(Key::Char('l'), vec![Action::GoToNextTab]); - - defaults.insert(Key::Left, vec![Action::GoToPreviousTab]); - defaults.insert(Key::Down, vec![Action::GoToNextTab]); - defaults.insert(Key::Up, vec![Action::GoToPreviousTab]); - defaults.insert(Key::Right, vec![Action::GoToNextTab]); - - defaults.insert(Key::Char('n'), vec![Action::NewTab]); - defaults.insert(Key::Char('x'), vec![Action::CloseTab]); - - defaults.insert( - Key::Char('r'), - vec![ - Action::SwitchToMode(InputMode::RenameTab), - Action::TabNameInput(vec![0]), - ], - ); - defaults.insert( - Key::Ctrl('g'), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - for i in '1'..='9' { - defaults.insert(Key::Char(i), vec![Action::GoToTab(i.to_digit(10).unwrap())]); - } - defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]); - defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]); - defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]); - defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]); - defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]); - defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]); - defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]); - } - InputMode::Scroll => { - defaults.insert( - Key::Ctrl('g'), - vec![Action::SwitchToMode(InputMode::Locked)], - ); - defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]); - defaults.insert( - Key::Ctrl('r'), - vec![Action::SwitchToMode(InputMode::Resize)], - ); - defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]); - defaults.insert( - Key::Ctrl('s'), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - defaults.insert(Key::Ctrl('q'), vec![Action::Quit]); - defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]); - defaults.insert( - Key::Char('\n'), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - defaults.insert( - Key::Char(' '), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - - defaults.insert(Key::Char('j'), vec![Action::ScrollDown]); - defaults.insert(Key::Char('k'), vec![Action::ScrollUp]); - - defaults.insert(Key::Ctrl('f'), vec![Action::PageScrollDown]); - defaults.insert(Key::Ctrl('b'), vec![Action::PageScrollUp]); - defaults.insert(Key::PageDown, vec![Action::PageScrollDown]); - defaults.insert(Key::PageUp, vec![Action::PageScrollUp]); - - defaults.insert(Key::Down, vec![Action::ScrollDown]); - defaults.insert(Key::Up, vec![Action::ScrollUp]); - - defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]); - defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]); - defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]); - defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]); - defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]); - defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]); - defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]); - } - InputMode::RenameTab => { - defaults.insert(Key::Char('\n'), vec![Action::SwitchToMode(InputMode::Tab)]); - defaults.insert( - Key::Ctrl('g'), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - defaults.insert( - Key::Esc, - vec![ - Action::TabNameInput(vec![0x1b]), - Action::SwitchToMode(InputMode::Tab), - ], - ); - - defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]); - defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]); - defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]); - defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]); - defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]); - defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]); - defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]); - } - } - ModeKeybinds(defaults) - } - /// Converts a [`Key`] terminal event to a sequence of [`Action`]s according to the current /// [`InputMode`] and [`Keybinds`]. pub fn key_to_actions( diff --git a/src/common/install.rs b/src/common/install.rs index 86fdd269..c7f4d4be 100644 --- a/src/common/install.rs +++ b/src/common/install.rs @@ -1,4 +1,7 @@ -use std::{fs, path::Path}; +use crate::common::utils::consts::SYSTEM_DEFAULT_CONFIG_DIR; +use directories_next::{BaseDirs, ProjectDirs}; +use std::io::Write; +use std::{fs, path::Path, path::PathBuf}; const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -39,3 +42,44 @@ pub fn populate_data_dir(data_dir: &Path) { } } } + +pub fn default_config_dir() -> Option<PathBuf> { + vec![ + Some(xdg_config_dir()), + home_config_dir(), + Some(Path::new(SYSTEM_DEFAULT_CONFIG_DIR).to_path_buf()), + ] + .into_iter() + .filter(|p| p.is_some()) + .find(|p| p.clone().unwrap().exists()) + .flatten() +} + +pub fn xdg_config_dir() -> PathBuf { + let project_dirs = ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap(); + project_dirs.config_dir().to_owned() +} + +pub fn home_config_dir() -> Option<PathBuf> { + if let Some(user_dirs) = BaseDirs::new() { + let config_dir = user_dirs.home_dir().join("/.config/zellij"); + Some(config_dir) + } else { + None + } +} + +pub fn dump_asset(asset: &[u8]) -> std::io::Result<()> { + std::io::stdout().write_all(&asset)?; + Ok(()) +} + +pub const DEFAULT_CONFIG: &[u8] = include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/", + "assets/config/default.yaml" +)); + +pub fn dump_default_config() -> std::io::Result<()> { + dump_asset(DEFAULT_CONFIG) +} diff --git a/src/common/mod.rs b/src/common/mod.rs index 0f896d1e..71824e26 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -40,8 +40,7 @@ use pty_bus::{PtyBus, PtyInstruction}; use screen::{Screen, ScreenInstruction}; use serde::{Deserialize, Serialize}; use utils::{consts::ZELLIJ_IPC_PIPE, shared::load_palette}; -use wasm_vm::PluginEnv; -use wasm_vm::{wasi_stdout, wasi_write_string, zellij_imports, PluginInstruction}; +use wasm_vm::{wasi_read_string, wasi_write_object, zellij_exports, PluginEnv, PluginInstruction}; use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value}; use wasmer_wasi::{Pipe, WasiState}; use zellij_tile::data::{EventType, InputMode, ModeInfo}; @@ -131,7 +130,9 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) { env::set_var(&"ZELLIJ", "0"); - let config = Config::from_cli_config(opts.config, opts.option) + let config_dir = opts.config_dir.or_else(install::default_config_dir); + + let config = Config::from_cli_config(opts.config, opts.option, config_dir) .map_err(|e| { eprintln!("There was an error in the config file:\n{}", e); std::process::exit(1); @@ -185,8 +186,8 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) { let default_layout = None; let maybe_layout = opts .layout - .or(default_layout) - .map(|p| Layout::new(&p, &data_dir)); + .map(|p| Layout::new(&p, &data_dir)) + .or_else(|| default_layout.map(|p| Layout::from_defaults(&p, &data_dir))); #[cfg(not(test))] std::panic::set_hook({ @@ -325,10 +326,11 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) { command_is_executing.done_opening_new_pane(); } ScreenInstruction::WriteCharacter(bytes) => { - screen - .get_active_tab_mut() - .unwrap() - .write_to_active_terminal(bytes); + let active_tab = screen.get_active_tab_mut().unwrap(); + match active_tab.is_sync_panes_active() { + true => active_tab.write_to_terminals_on_current_tab(bytes), + false => active_tab.write_to_active_terminal(bytes), + } } ScreenInstruction::ResizeLeft => { screen.get_active_tab_mut().unwrap().resize_left(); @@ -449,6 +451,13 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) { ScreenInstruction::ChangeMode(mode_info) => { screen.change_mode(mode_info); } + ScreenInstruction::ToggleActiveSyncPanes => { + screen + .get_active_tab_mut() + .unwrap() + .toggle_sync_panes_is_active(); + screen.update_tabs(); + } ScreenInstruction::Quit => { break; } @@ -464,6 +473,7 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) { let send_pty_instructions = send_pty_instructions.clone(); let send_screen_instructions = send_screen_instructions.clone(); let send_app_instructions = send_app_instructions.clone(); + let send_plugin_instructions = send_plugin_instructions.clone(); let store = Store::default(); let mut plugin_id = 0; @@ -508,16 +518,17 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) { send_pty_instructions: send_pty_instructions.clone(), send_screen_instructions: send_screen_instructions.clone(), send_app_instructions: send_app_instructions.clone(), + send_plugin_instructions: send_plugin_instructions.clone(), wasi_env, subscriptions: Arc::new(Mutex::new(HashSet::new())), }; - let zellij = zellij_imports(&store, &plugin_env); + let zellij = zellij_exports(&store, &plugin_env); let instance = Instance::new(&module, &zellij.chain_back(wasi)).unwrap(); let start = instance.exports.get_function("_start").unwrap(); - // This eventually calls the `.init()` method + // This eventually calls the `.load()` method start.call(&[]).unwrap(); plugin_map.insert(plugin_id, (instance, plugin_env)); @@ -531,10 +542,7 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) { let event_type = EventType::from_str(&event.to_string()).unwrap(); if (pid.is_none() || pid == Some(i)) && subs.contains(&event_type) { let update = instance.exports.get_function("update").unwrap(); - wasi_write_string( - &plugin_env.wasi_env, - &serde_json::to_string(&event).unwrap(), - ); + wasi_write_object(&plugin_env.wasi_env, &event); update.call(&[]).unwrap(); } } @@ -549,7 +557,7 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) { .call(&[Value::I32(rows as i32), Value::I32(cols as i32)]) .unwrap(); - buf_tx.send(wasi_stdout(&plugin_env.wasi_env)).unwrap(); + buf_tx.send(wasi_read_string(&plugin_env.wasi_env)).unwrap(); } PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)), PluginInstruction::Quit => break, diff --git a/src/common/screen.rs b/src/common/screen.rs index 03cfa0bf..e6cf73f5 100644 --- a/src/common/screen.rs +++ b/src/common/screen.rs @@ -51,6 +51,7 @@ pub enum ScreenInstruction { NewTab(RawFd), SwitchTabNext, SwitchTabPrev, + ToggleActiveSyncPanes, CloseTab, GoToTab(u32), UpdateTabName(Vec<u8>), @@ -158,8 +159,9 @@ impl Screen { let active_tab_pos = self.get_active_tab().unwrap().position; let new_tab_pos = (active_tab_pos + 1) % self.tabs.len(); - for tab in self.tabs.values() { + for tab in self.tabs.values_mut() { if tab.position == new_tab_pos { + tab.set_force_render(); self.active_tab_index = Some(tab.index); break; } @@ -176,8 +178,9 @@ impl Screen { } else { active_tab_pos - 1 }; - for tab in self.tabs.values() { + for tab in self.tabs.values_mut() { if tab.position == new_tab_pos { + tab.set_force_render(); self.active_tab_index = Some(tab.index); break; } @@ -188,9 +191,10 @@ impl Screen { pub fn go_to_tab(&mut self, mut tab_index: usize) { tab_index -= 1; - let active_tab = self.get_active_tab().unwrap(); - if let Some(t) = self.tabs.values().find(|t| t.position == tab_index) { - if t.index != active_tab.index { + let active_tab_index = self.get_active_tab().unwrap().index; + if let Some(t) = self.tabs.values_mut().find(|t| t.position == tab_index) { + if t.index != active_tab_index { + t.set_force_render(); self.active_tab_index = Some(t.index); self.update_tabs(); self.render(); @@ -234,6 +238,7 @@ impl Screen { for (_, tab) in self.tabs.iter_mut() { tab.resize_whole_tab(new_screen_size); } + let _ = self.get_active_tab_mut().map(|t| t.set_force_render()); self.render(); } @@ -295,7 +300,7 @@ impl Screen { self.update_tabs(); } - fn update_tabs(&self) { + pub fn update_tabs(&self) { let mut tab_data = vec![]; let active_tab_index = self.active_tab_index.unwrap(); for tab in self.tabs.values() { @@ -303,6 +308,7 @@ impl Screen { position: tab.position, name: tab.name.clone(), active: active_tab_index == tab.index, + is_sync_panes_active: tab.is_sync_panes_active(), }); } self.send_plugin_instructions diff --git a/src/common/utils/consts.rs b/src/common/utils/consts.rs index 663a545d..4bd1b5b1 100644 --- a/src/common/utils/consts.rs +++ b/src/common/utils/consts.rs @@ -4,3 +4,9 @@ pub const ZELLIJ_TMP_DIR: &str = "/tmp/zellij"; pub const ZELLIJ_TMP_LOG_DIR: &str = "/tmp/zellij/zellij-log"; pub const ZELLIJ_TMP_LOG_FILE: &str = "/tmp/zellij/zellij-log/log.txt"; pub const ZELLIJ_IPC_PIPE: &str = "/tmp/zellij/ipc"; + +pub const ZELLIJ_CONFIG_FILE_ENV: &str = "ZELLIJ_CONFIG_FILE"; +pub const ZELLIJ_CONFIG_DIR_ENV: &str = "ZELLIJ_CONFIG_DIR"; + +// TODO: ${PREFIX} argument in makefile +pub const SYSTEM_DEFAULT_CONFIG_DIR: &str = "/etc/zellij"; diff --git a/src/common/wasm_vm.rs b/src/common/wasm_vm.rs index 29b110ae..bd98a85b 100644 --- a/src/common/wasm_vm.rs +++ b/src/common/wasm_vm.rs @@ -1,11 +1,15 @@ +use serde::{de::DeserializeOwned, Serialize}; use std::{ collections::HashSet, path::PathBuf, + process, sync::{mpsc::Sender, Arc, Mutex}, + thread, + time::{Duration, Instant}, }; use wasmer::{imports, Function, ImportObject, Store, WasmerEnv}; use wasmer_wasi::WasiEnv; -use zellij_tile::data::{Event, EventType}; +use zellij_tile::data::{Event, EventType, PluginIds}; use super::{ pty_bus::PtyInstruction, screen::ScreenInstruction, AppInstruction, PaneId, SenderWithContext, @@ -23,48 +27,53 @@ pub enum PluginInstruction { #[derive(WasmerEnv, Clone)] pub struct PluginEnv { pub plugin_id: u32, + // FIXME: This should be a big bundle of all of the channels pub send_screen_instructions: SenderWithContext<ScreenInstruction>, pub send_app_instructions: SenderWithContext<AppInstruction>, - pub send_pty_instructions: SenderWithContext<PtyInstruction>, // FIXME: This should be a big bundle of all of the channels + pub send_pty_instructions: SenderWithContext<PtyInstruction>, + pub send_plugin_instructions: SenderWithContext<PluginInstruction>, pub wasi_env: WasiEnv, pub subscriptions: Arc<Mutex<HashSet<EventType>>>, } // Plugin API --------------------------------------------------------------------------------------------------------- -pub fn zellij_imports(store: &Store, plugin_env: &PluginEnv) -> ImportObject { - imports! { - "zellij" => { - "host_subscribe" => Function::new_native_with_env(store, plugin_env.clone(), host_subscribe), - "host_unsubscribe" => Function::new_native_with_env(store, plugin_env.clone(), host_unsubscribe), - "host_open_file" => Function::new_native_with_env(store, plugin_env.clone(), host_open_file), - "host_set_invisible_borders" => Function::new_native_with_env(store, plugin_env.clone(), host_set_invisible_borders), - "host_set_max_height" => Function::new_native_with_env(store, plugin_env.clone(), host_set_max_height), - "host_set_selectable" => Function::new_native_with_env(store, plugin_env.clone(), host_set_selectable), +pub fn zellij_exports(store: &Store, plugin_env: &PluginEnv) -> ImportObject { + macro_rules! zellij_export { + ($($host_function:ident),+ $(,)?) => { + imports! { + "zellij" => { + $(stringify!($host_function) => + Function::new_native_with_env(store, plugin_env.clone(), $host_function),)+ + } + } } } + + zellij_export! { + host_subscribe, + host_unsubscribe, + host_set_invisible_borders, + host_set_max_height, + host_set_selectable, + host_get_plugin_ids, + host_open_file, + host_set_timeout, + } } fn host_subscribe(plugin_env: &PluginEnv) { let mut subscriptions = plugin_env.subscriptions.lock().unwrap(); - let new: HashSet<EventType> = serde_json::from_str(&wasi_stdout(&plugin_env.wasi_env)).unwrap(); + let new: HashSet<EventType> = wasi_read_object(&plugin_env.wasi_env); subscriptions.extend(new); } fn host_unsubscribe(plugin_env: &PluginEnv) { let mut subscriptions = plugin_env.subscriptions.lock().unwrap(); - let old: HashSet<EventType> = serde_json::from_str(&wasi_stdout(&plugin_env.wasi_env)).unwrap(); + let old: HashSet<EventType> = wasi_read_object(&plugin_env.wasi_env); subscriptions.retain(|k| !old.contains(k)); } -fn host_open_file(plugin_env: &PluginEnv) { - let path = PathBuf::from(wasi_stdout(&plugin_env.wasi_env).lines().next().unwrap()); - plugin_env - .send_pty_instructions - .send(PtyInstruction::SpawnTerminal(Some(path))) - .unwrap(); -} - fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) { let selectable = selectable != 0; plugin_env @@ -98,10 +107,54 @@ fn host_set_invisible_borders(plugin_env: &PluginEnv, invisible_borders: i32) { .unwrap() } +fn host_get_plugin_ids(plugin_env: &PluginEnv) { + let ids = PluginIds { + plugin_id: plugin_env.plugin_id, + zellij_pid: process::id(), + }; + wasi_write_object(&plugin_env.wasi_env, &ids); +} + +fn host_open_file(plugin_env: &PluginEnv) { + let path: PathBuf = wasi_read_object(&plugin_env.wasi_env); + plugin_env + .send_pty_instructions + .send(PtyInstruction::SpawnTerminal(Some(path))) + .unwrap(); +} + +fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) { + // There is a fancy, high-performance way to do this with zero additional threads: + // If the plugin thread keeps a BinaryHeap of timer structs, it can manage multiple and easily `.peek()` at the + // next time to trigger in O(1) time. Once the wake-up time is known, the `wasm` thread can use `recv_timeout()` + // to wait for an event with the timeout set to be the time of the next wake up. If events come in in the meantime, + // they are handled, but if the timeout triggers, we replace the event from `recv()` with an + // `Update(pid, TimerEvent)` and pop the timer from the Heap (or reschedule it). No additional threads for as many + // timers as we'd like. + // + // But that's a lot of code, and this is a few lines: + let send_plugin_instructions = plugin_env.send_plugin_instructions.clone(); + let update_target = Some(plugin_env.plugin_id); + thread::spawn(move || { + let start_time = Instant::now(); + thread::sleep(Duration::from_secs_f64(secs)); + // FIXME: The way that elapsed time is being calculated here is not exact; it doesn't take into account the + // time it takes an event to actually reach the plugin after it's sent to the `wasm` thread. + let elapsed_time = Instant::now().duration_since(start_time).as_secs_f64(); + + send_plugin_instructions + .send(PluginInstruction::Update( + update_target, + Event::Timer(elapsed_time), + )) + .unwrap(); + }); +} + // Helper Functions --------------------------------------------------------------------------------------------------- // FIXME: Unwrap city -pub fn wasi_stdout(wasi_env: &WasiEnv) -> String { +pub fn wasi_read_string(wasi_env: &WasiEnv) -> String { let mut state = wasi_env.state(); let wasi_file = state.fs.stdout_mut().unwrap().as_mut().unwrap(); let mut buf = String::new(); @@ -114,3 +167,12 @@ pub fn wasi_write_string(wasi_env: &WasiEnv, buf: &str) { let wasi_file = state.fs.stdin_mut().unwrap().as_mut().unwrap(); writeln!(wasi_file, "{}\r", buf).unwrap(); } + +pub fn wasi_write_object(wasi_env: &WasiEnv, object: &impl Serialize) { + wasi_write_string(wasi_env, &serde_json::to_string(&object).unwrap()); +} + +pub fn wasi_read_object<T: DeserializeOwned>(wasi_env: &WasiEnv) -> T { + let json = wasi_read_string(wasi_env); + serde_json::from_str(&json).unwrap() +} |
