aboutsummaryrefslogtreecommitdiffstats
path: root/src/common
diff options
context:
space:
mode:
authordenis <denismaximov98@gmail.com>2021-05-01 13:45:01 +0300
committerdenis <denismaximov98@gmail.com>2021-05-01 13:45:01 +0300
commiteabecde90b1617ce4c29be9d5af3f52ccb67e45f (patch)
tree0d0dafa594269045f084383a9719f2a4995fe7ea /src/common
parentwip: more cleaning up (diff)
parentdocs(changelog): performance improvements (diff)
downloadsl0ppij-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.rs2
-rw-r--r--src/common/input/actions.rs2
-rw-r--r--src/common/input/config.rs58
-rw-r--r--src/common/input/handler.rs16
-rw-r--r--src/common/input/keybinds.rs286
-rw-r--r--src/common/install.rs46
-rw-r--r--src/common/mod.rs40
-rw-r--r--src/common/screen.rs18
-rw-r--r--src/common/utils/consts.rs6
-rw-r--r--src/common/wasm_vm.rs106
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()
+}