aboutsummaryrefslogtreecommitdiffstats
path: root/src/common
diff options
context:
space:
mode:
authora-kenji <aks.kenji@protonmail.com>2021-05-12 20:03:04 +0200
committera-kenji <aks.kenji@protonmail.com>2021-05-12 20:03:04 +0200
commit4134c952f65aa2951404991bcf525462e3482fe9 (patch)
treecf809552c3f676c5dee56873052a5a584e377623 /src/common
parent Add Complile Time System Directories (diff)
parentMerge pull request #497 from a-kenji/fix-default-keybind (diff)
downloadsl0ppij-4134c952f65aa2951404991bcf525462e3482fe9.tar.gz
sl0ppij-4134c952f65aa2951404991bcf525462e3482fe9.tar.xz
sl0ppij-4134c952f65aa2951404991bcf525462e3482fe9.zip
Merge branch 'main' of https://github.com/zellij-org/zellij into set-data-dir
Diffstat (limited to 'src/common')
-rw-r--r--src/common/errors.rs22
-rw-r--r--src/common/input/actions.rs7
-rw-r--r--src/common/input/config.rs21
-rw-r--r--src/common/input/handler.rs42
-rw-r--r--src/common/input/keybinds.rs138
-rw-r--r--src/common/input/mod.rs1
-rw-r--r--src/common/input/options.rs45
-rw-r--r--src/common/input/unit/keybinds_test.rs653
-rw-r--r--src/common/mod.rs65
-rw-r--r--src/common/os_input_output.rs6
-rw-r--r--src/common/pty.rs (renamed from src/common/pty_bus.rs)165
-rw-r--r--src/common/screen.rs341
-rw-r--r--src/common/setup.rs6
-rw-r--r--src/common/thread_bus.rs142
-rw-r--r--src/common/utils/consts.rs2
-rw-r--r--src/common/utils/logging.rs18
-rw-r--r--src/common/wasm_vm.rs138
17 files changed, 1565 insertions, 247 deletions
diff --git a/src/common/errors.rs b/src/common/errors.rs
index 411e0225..c2caf19d 100644
--- a/src/common/errors.rs
+++ b/src/common/errors.rs
@@ -1,20 +1,22 @@
//! Error context system based on a thread-local representation of the call stack, itself based on
//! the instructions that are sent between threads.
-use super::{ServerInstruction, ASYNCOPENCALLS, OPENCALLS};
-use crate::client::ClientInstruction;
-use crate::pty_bus::PtyInstruction;
-use crate::screen::ScreenInstruction;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Error, Formatter};
+use crate::client::ClientInstruction;
+use crate::common::thread_bus::{ASYNCOPENCALLS, OPENCALLS};
+use crate::pty::PtyInstruction;
+use crate::screen::ScreenInstruction;
+use crate::server::ServerInstruction;
+
/// The maximum amount of calls an [`ErrorContext`] will keep track
/// of in its stack representation. This is a per-thread maximum.
const MAX_THREAD_CALL_STACK: usize = 6;
#[cfg(not(test))]
-use super::SenderWithContext;
+use super::thread_bus::SenderWithContext;
#[cfg(not(test))]
use std::panic::PanicInfo;
/// Custom panic handler/hook. Prints the [`ErrorContext`].
@@ -190,9 +192,11 @@ pub enum ScreenContext {
FocusNextPane,
FocusPreviousPane,
MoveFocusLeft,
+ MoveFocusLeftOrPreviousTab,
MoveFocusDown,
MoveFocusUp,
MoveFocusRight,
+ MoveFocusRightOrNextTab,
Exit,
ScrollUp,
ScrollDown,
@@ -200,7 +204,7 @@ pub enum ScreenContext {
PageScrollDown,
ClearScroll,
CloseFocusedPane,
- ToggleActiveSyncPanes,
+ ToggleActiveSyncTab,
ToggleActiveTerminalFullscreen,
SetSelectable,
SetInvisibleBorders,
@@ -235,9 +239,13 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::FocusNextPane => ScreenContext::FocusNextPane,
ScreenInstruction::FocusPreviousPane => ScreenContext::FocusPreviousPane,
ScreenInstruction::MoveFocusLeft => ScreenContext::MoveFocusLeft,
+ ScreenInstruction::MoveFocusLeftOrPreviousTab => {
+ ScreenContext::MoveFocusLeftOrPreviousTab
+ }
ScreenInstruction::MoveFocusDown => ScreenContext::MoveFocusDown,
ScreenInstruction::MoveFocusUp => ScreenContext::MoveFocusUp,
ScreenInstruction::MoveFocusRight => ScreenContext::MoveFocusRight,
+ ScreenInstruction::MoveFocusRightOrNextTab => ScreenContext::MoveFocusRightOrNextTab,
ScreenInstruction::Exit => ScreenContext::Exit,
ScreenInstruction::ScrollUp => ScreenContext::ScrollUp,
ScreenInstruction::ScrollDown => ScreenContext::ScrollDown,
@@ -261,7 +269,7 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::UpdateTabName(_) => ScreenContext::UpdateTabName,
ScreenInstruction::TerminalResize(_) => ScreenContext::TerminalResize,
ScreenInstruction::ChangeMode(_) => ScreenContext::ChangeMode,
- ScreenInstruction::ToggleActiveSyncPanes => ScreenContext::ToggleActiveSyncPanes,
+ ScreenInstruction::ToggleActiveSyncTab => ScreenContext::ToggleActiveSyncTab,
}
}
}
diff --git a/src/common/input/actions.rs b/src/common/input/actions.rs
index 46a8e1d6..b806aa3f 100644
--- a/src/common/input/actions.rs
+++ b/src/common/input/actions.rs
@@ -29,6 +29,9 @@ pub enum Action {
/// Move the focus pane in specified direction.
SwitchFocus,
MoveFocus(Direction),
+ /// Tries to move the focus pane in specified direction.
+ /// If there is no pane in the direction, move to previous/next Tab.
+ MoveFocusOrTab(Direction),
/// Scroll up in focus pane.
ScrollUp,
/// Scroll down in focus pane.
@@ -39,8 +42,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,
+ /// Toggle between sending text commands to all panes on the current tab and normal mode.
+ ToggleActiveSyncTab,
/// 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 3263b571..5f2656f3 100644
--- a/src/common/input/config.rs
+++ b/src/common/input/config.rs
@@ -6,10 +6,11 @@ use std::io::{self, Read};
use std::path::{Path, PathBuf};
use super::keybinds::{Keybinds, KeybindsFromYaml};
+use super::options::Options;
use crate::cli::{CliArgs, ConfigCli};
use crate::common::setup;
-use serde::Deserialize;
+use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
const DEFAULT_CONFIG_FILE_NAME: &str = "config.yaml";
@@ -19,13 +20,16 @@ type ConfigResult = Result<Config, ConfigError>;
/// Intermediate deserialization config struct
#[derive(Debug, Deserialize)]
pub struct ConfigFromYaml {
+ #[serde(flatten)]
+ pub options: Option<Options>,
pub keybinds: Option<KeybindsFromYaml>,
}
/// Main configuration.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Config {
pub keybinds: Keybinds,
+ pub options: Options,
}
#[derive(Debug)]
@@ -43,7 +47,8 @@ pub enum ConfigError {
impl Default for Config {
fn default() -> Self {
let keybinds = Keybinds::default();
- Config { keybinds }
+ let options = Options::default();
+ Config { keybinds, options }
}
}
@@ -55,7 +60,7 @@ impl TryFrom<&CliArgs> for Config {
return Config::new(&path);
}
- if let Some(ConfigCli::Config { clean, .. }) = opts.option {
+ if let Some(ConfigCli::Setup { clean, .. }) = opts.option {
if clean {
return Config::from_default_assets();
}
@@ -84,7 +89,8 @@ impl Config {
pub fn from_yaml(yaml_config: &str) -> ConfigResult {
let config_from_yaml: ConfigFromYaml = serde_yaml::from_str(&yaml_config)?;
let keybinds = Keybinds::get_default_keybinds_with_config(config_from_yaml.keybinds);
- Ok(Config { keybinds })
+ let options = Options::from_yaml(config_from_yaml.options);
+ Ok(Config { keybinds, options })
}
/// Deserializes from given path.
@@ -172,7 +178,10 @@ mod config_test {
#[test]
fn try_from_cli_args_with_option_clean() {
let mut opts = CliArgs::default();
- opts.option = Some(ConfigCli::Config { clean: true });
+ opts.option = Some(ConfigCli::Setup {
+ clean: true,
+ dump_config: false,
+ });
let result = Config::try_from(&opts);
assert!(result.is_ok());
}
diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs
index 75f9f8b1..2dbec080 100644
--- a/src/common/input/handler.rs
+++ b/src/common/input/handler.rs
@@ -4,14 +4,14 @@ use super::actions::Action;
use super::keybinds::Keybinds;
use crate::client::ClientInstruction;
use crate::common::input::config::Config;
-use crate::common::{SenderWithContext, OPENCALLS};
+use crate::common::thread_bus::{SenderWithContext, OPENCALLS};
use crate::errors::ContextType;
use crate::os_input_output::ClientOsApi;
use crate::server::ServerInstruction;
use crate::CommandIsExecuting;
use termion::input::{TermRead, TermReadEventsAndRaw};
-use zellij_tile::data::{InputMode, Key, ModeInfo, Palette};
+use zellij_tile::data::{InputMode, Key, ModeInfo, Palette, PluginCapabilities};
/// Handles the dispatching of [`Action`]s according to the current
/// [`InputMode`], and keep tracks of the current [`InputMode`].
@@ -23,6 +23,7 @@ struct InputHandler {
command_is_executing: CommandIsExecuting,
send_client_instructions: SenderWithContext<ClientInstruction>,
should_exit: bool,
+ pasting: bool,
}
impl InputHandler {
@@ -40,6 +41,7 @@ impl InputHandler {
command_is_executing,
send_client_instructions,
should_exit: false,
+ pasting: false,
}
}
@@ -49,6 +51,8 @@ impl InputHandler {
let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
err_ctx.add_call(ContextType::StdinHandler);
let alt_left_bracket = vec![27, 91];
+ let bracketed_paste_start = vec![27, 91, 50, 48, 48, 126]; // \u{1b}[200~
+ let bracketed_paste_end = vec![27, 91, 50, 48, 49, 126]; // \u{1b}[201
loop {
if self.should_exit {
break;
@@ -67,6 +71,10 @@ impl InputHandler {
if unsupported_key == alt_left_bracket {
let key = Key::Alt('[');
self.handle_key(&key, raw_bytes);
+ } else if unsupported_key == bracketed_paste_start {
+ self.pasting = true;
+ } else if unsupported_key == bracketed_paste_end {
+ self.pasting = false;
}
}
termion::event::Event::Mouse(_) => {
@@ -81,10 +89,20 @@ impl InputHandler {
}
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;
+ if self.pasting {
+ // we're inside a paste block, if we're in a mode that allows sending text to the
+ // terminal, send all text directly without interpreting it
+ // otherwise, just discard the input
+ if self.mode == InputMode::Normal || self.mode == InputMode::Locked {
+ let action = Action::Write(raw_bytes);
+ self.dispatch_action(action);
+ }
+ } else {
+ 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;
+ }
}
}
}
@@ -119,7 +137,8 @@ impl InputHandler {
| Action::GoToNextTab
| Action::GoToPreviousTab
| Action::CloseTab
- | Action::GoToTab(_) => {
+ | Action::GoToTab(_)
+ | Action::MoveFocusOrTab(_) => {
self.command_is_executing.blocking_input_thread();
self.os_input
.send_to_server(ServerInstruction::Action(action));
@@ -146,7 +165,11 @@ impl InputHandler {
/// Creates a [`Help`] struct indicating the current [`InputMode`] and its keybinds
/// (as pairs of [`String`]s).
// TODO this should probably be automatically generated in some way
-pub fn get_mode_info(mode: InputMode, palette: Palette) -> ModeInfo {
+pub fn get_mode_info(
+ mode: InputMode,
+ palette: Palette,
+ capabilities: PluginCapabilities,
+) -> ModeInfo {
let mut keybinds: Vec<(String, String)> = vec![];
match mode {
InputMode::Normal | InputMode::Locked => {}
@@ -160,7 +183,6 @@ pub fn get_mode_info(mode: InputMode, palette: Palette) -> 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 => {
@@ -168,6 +190,7 @@ pub fn get_mode_info(mode: InputMode, palette: Palette) -> ModeInfo {
keybinds.push(("n".to_string(), "New".to_string()));
keybinds.push(("x".to_string(), "Close".to_string()));
keybinds.push(("r".to_string(), "Rename".to_string()));
+ keybinds.push(("s".to_string(), "Sync".to_string()));
}
InputMode::Scroll => {
keybinds.push(("↓↑".to_string(), "Scroll".to_string()));
@@ -181,6 +204,7 @@ pub fn get_mode_info(mode: InputMode, palette: Palette) -> ModeInfo {
mode,
keybinds,
palette,
+ capabilities,
}
}
diff --git a/src/common/input/keybinds.rs b/src/common/input/keybinds.rs
index 9842fbe2..8bef47fd 100644
--- a/src/common/input/keybinds.rs
+++ b/src/common/input/keybinds.rs
@@ -4,14 +4,14 @@ use std::collections::HashMap;
use super::actions::Action;
use super::config;
-use serde::Deserialize;
+use serde::{Deserialize, Serialize};
use strum::IntoEnumIterator;
use zellij_tile::data::*;
/// Used in the config struct
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct Keybinds(HashMap<InputMode, ModeKeybinds>);
-#[derive(Clone, Debug, Default, PartialEq)]
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct ModeKeybinds(HashMap<Key, Vec<Action>>);
/// Intermediate struct used for deserialisation
@@ -29,8 +29,14 @@ pub struct KeybindsFromYaml {
#[serde(untagged)]
enum KeyActionUnbind {
KeyAction(KeyActionFromYaml),
- // TODO: use the enum
- //Unbind(UnbindFromYaml),
+ Unbind(UnbindFromYaml),
+}
+
+/// Intermediate struct used for deserialisation
+#[derive(Clone, Debug, PartialEq, Deserialize)]
+struct KeyActionUnbindFromYaml {
+ keybinds: Vec<KeyActionFromYaml>,
+ unbind: Unbind,
}
/// Intermediate struct used for deserialisation
@@ -41,7 +47,7 @@ pub struct KeyActionFromYaml {
}
/// Intermediate struct used for deserialisation
-#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize)]
+#[derive(Clone, Debug, PartialEq, Deserialize)]
struct UnbindFromYaml {
unbind: Unbind,
}
@@ -51,16 +57,19 @@ struct UnbindFromYaml {
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize)]
#[serde(untagged)]
enum Unbind {
+ // This is the correct order, don't rearrange!
+ // Suspected Bug in the untagged macro.
+ // 1. Keys
+ Keys(Vec<Key>),
+ // 2. All
All(bool),
- // TODO@a-kenji: use the enum
- //Keys(Vec<Key>),
}
impl Default for Keybinds {
+ // Use once per codepath
+ // TODO investigate why
fn default() -> Keybinds {
- config::Config::from_default_assets()
- .expect("Keybinds from default assets Error")
- .keybinds
+ Self::from_default_assets()
}
}
@@ -69,11 +78,18 @@ impl Keybinds {
Keybinds(HashMap::<InputMode, ModeKeybinds>::new())
}
+ fn from_default_assets() -> Keybinds {
+ config::Config::from_default_assets()
+ .expect("Keybinds from default assets Error!")
+ .keybinds
+ }
+
+ /// Entrypoint from the config module
pub fn get_default_keybinds_with_config(from_yaml: Option<KeybindsFromYaml>) -> Keybinds {
let default_keybinds = match from_yaml.clone() {
Some(keybinds) => match keybinds.unbind {
Unbind::All(true) => Keybinds::new(),
- Unbind::All(false) => Keybinds::default(),
+ Unbind::All(false) | Unbind::Keys(_) => Keybinds::unbind(keybinds),
},
None => Keybinds::default(),
};
@@ -85,6 +101,71 @@ impl Keybinds {
}
}
+ /// Unbinds the default keybindings in relation to their mode
+ fn unbind(from_yaml: KeybindsFromYaml) -> Keybinds {
+ let mut keybind_config = Self::new();
+ let mut unbind_config: HashMap<InputMode, Unbind> = HashMap::new();
+ let keybinds_from_yaml = from_yaml.keybinds;
+
+ for mode in InputMode::iter() {
+ if let Some(keybinds) = keybinds_from_yaml.get(&mode) {
+ for keybind in keybinds.iter() {
+ match keybind {
+ KeyActionUnbind::Unbind(unbind) => {
+ unbind_config.insert(mode, unbind.unbind.clone());
+ }
+ KeyActionUnbind::KeyAction(key_action_from_yaml) => {
+ keybind_config
+ .0
+ .insert(mode, ModeKeybinds::from(key_action_from_yaml.clone()));
+ }
+ }
+ }
+ }
+ }
+
+ let mut default = Self::default().unbind_mode(unbind_config);
+
+ // Toplevel Unbinds
+ if let Unbind::Keys(_) = from_yaml.unbind {
+ let mut unbind_config: HashMap<InputMode, Unbind> = HashMap::new();
+ for mode in InputMode::iter() {
+ unbind_config.insert(mode, from_yaml.unbind.clone());
+ }
+ default = default.unbind_mode(unbind_config);
+ };
+
+ default.merge_keybinds(keybind_config)
+ }
+
+ /// Unbind [`Key`] bindings respective to their mode
+ fn unbind_mode(&self, unbind: HashMap<InputMode, Unbind>) -> Keybinds {
+ let mut keybinds = Keybinds::new();
+
+ for mode in InputMode::iter() {
+ if let Some(unbind) = unbind.get(&mode) {
+ match unbind {
+ Unbind::All(true) => {}
+ Unbind::Keys(keys) => {
+ if let Some(defaults) = self.0.get(&mode) {
+ keybinds
+ .0
+ .insert(mode, defaults.clone().unbind_keys(keys.to_vec()));
+ }
+ }
+ Unbind::All(false) => {
+ if let Some(defaults) = self.0.get(&mode) {
+ keybinds.0.insert(mode, defaults.clone());
+ }
+ }
+ }
+ } else if let Some(defaults) = self.0.get(&mode) {
+ keybinds.0.insert(mode, defaults.clone());
+ }
+ }
+ keybinds
+ }
+
/// Merges two Keybinds structs into one Keybinds struct
/// `other` overrides the ModeKeybinds of `self`.
fn merge_keybinds(&self, other: Keybinds) -> Keybinds {
@@ -142,6 +223,15 @@ impl ModeKeybinds {
merged.0.extend(other.0);
merged
}
+
+ /// Remove [`Key`]'s from [`ModeKeybinds`]
+ fn unbind_keys(self, unbind: Vec<Key>) -> Self {
+ let mut keymap = self;
+ for key in unbind {
+ keymap.0.remove(&key);
+ }
+ keymap
+ }
}
impl From<KeybindsFromYaml> for Keybinds {
@@ -161,27 +251,41 @@ impl From<KeybindsFromYaml> for Keybinds {
}
}
-/// For each `Key` assigned to `Action`s,
-/// map the `Action`s to the key
+/// For each [`Key`] assigned to [`Action`]s,
+/// map the [`Action`]s to the [`Key`]
impl From<KeyActionFromYaml> for ModeKeybinds {
fn from(key_action: KeyActionFromYaml) -> ModeKeybinds {
- let keys = key_action.key;
let actions = key_action.action;
ModeKeybinds(
- keys.into_iter()
+ key_action
+ .key
+ .into_iter()
.map(|k| (k, actions.clone()))
.collect::<HashMap<Key, Vec<Action>>>(),
)
}
}
-// Currently an enum for future use
impl From<KeyActionUnbind> for ModeKeybinds {
fn from(key_action_unbind: KeyActionUnbind) -> ModeKeybinds {
match key_action_unbind {
KeyActionUnbind::KeyAction(key_action) => ModeKeybinds::from(key_action),
+ KeyActionUnbind::Unbind(_) => ModeKeybinds::new(),
+ }
+ }
+}
+
+impl From<Vec<KeyActionFromYaml>> for ModeKeybinds {
+ fn from(key_action_from_yaml: Vec<KeyActionFromYaml>) -> ModeKeybinds {
+ let mut mode_keybinds = ModeKeybinds::new();
+
+ for keybind in key_action_from_yaml {
+ for key in keybind.key {
+ mode_keybinds.0.insert(key, keybind.action.clone());
+ }
}
+ mode_keybinds
}
}
diff --git a/src/common/input/mod.rs b/src/common/input/mod.rs
index 6589cac2..f30ccf23 100644
--- a/src/common/input/mod.rs
+++ b/src/common/input/mod.rs
@@ -4,3 +4,4 @@ pub mod actions;
pub mod config;
pub mod handler;
pub mod keybinds;
+pub mod options;
diff --git a/src/common/input/options.rs b/src/common/input/options.rs
new file mode 100644
index 00000000..33625b65
--- /dev/null
+++ b/src/common/input/options.rs
@@ -0,0 +1,45 @@
+//! Handles cli and configuration options
+use crate::cli::ConfigCli;
+use serde::{Deserialize, Serialize};
+use structopt::StructOpt;
+
+#[derive(Clone, Default, Debug, PartialEq, Deserialize, Serialize, StructOpt)]
+/// Options that can be set either through the config file,
+/// or cli flags
+pub struct Options {
+ /// Allow plugins to use a more simplified layout
+ /// that is compatible with more fonts
+ #[structopt(long)]
+ pub simplified_ui: bool,
+}
+
+impl Options {
+ pub fn from_yaml(from_yaml: Option<Options>) -> Options {
+ if let Some(opts) = from_yaml {
+ opts
+ } else {
+ Options::default()
+ }
+ }
+
+ /// Merges two [`Options`] structs, a `Some` in `other`
+ /// will supercede a `Some` in `self`
+ // TODO: Maybe a good candidate for a macro?
+ pub fn merge(&self, other: Options) -> Options {
+ let simplified_ui = if other.simplified_ui {
+ true
+ } else {
+ self.simplified_ui
+ };
+
+ Options { simplified_ui }
+ }
+
+ pub fn from_cli(&self, other: Option<ConfigCli>) -> Options {
+ if let Some(ConfigCli::Options(options)) = other {
+ Options::merge(&self, options)
+ } else {
+ self.to_owned()
+ }
+ }
+}
diff --git a/src/common/input/unit/keybinds_test.rs b/src/common/input/unit/keybinds_test.rs
index e0d78b62..4767fc15 100644
--- a/src/common/input/unit/keybinds_test.rs
+++ b/src/common/input/unit/keybinds_test.rs
@@ -136,6 +136,7 @@ fn toplevel_unbind_unbinds_all() {
assert_eq!(keybinds_from_yaml, Keybinds::new());
}
+#[test]
fn no_unbind_unbinds_none() {
let from_yaml = KeybindsFromYaml {
unbind: Unbind::All(false),
@@ -144,5 +145,657 @@ fn no_unbind_unbinds_none() {
let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+ assert_eq!(keybinds_from_yaml, Keybinds::default());
+}
+
+#[test]
+fn last_keybind_is_taken() {
+ let actions_1 = vec![Action::NoOp, Action::NewTab];
+ let keyaction_1 = KeyActionFromYaml {
+ action: actions_1.clone(),
+ key: vec![Key::F(1), Key::Backspace, Key::Char('t')],
+ };
+ let actions_2 = vec![Action::GoToTab(1)];
+ let keyaction_2 = KeyActionFromYaml {
+ action: actions_2.clone(),
+ key: vec![Key::F(1), Key::Backspace, Key::Char('t')],
+ };
+
+ let mut expected = ModeKeybinds::new();
+ expected.0.insert(Key::F(1), actions_2.clone());
+ expected.0.insert(Key::Backspace, actions_2.clone());
+ expected.0.insert(Key::Char('t'), actions_2);
+
+ assert_eq!(expected, ModeKeybinds::from(vec![keyaction_1, keyaction_2]));
+}
+
+#[test]
+fn last_keybind_overwrites() {
+ let actions_1 = vec![Action::NoOp, Action::NewTab];
+ let keyaction_1 = KeyActionFromYaml {
+ action: actions_1.clone(),
+ key: vec![Key::F(1), Key::Backspace, Key::Char('t')],
+ };
+ let actions_2 = vec![Action::GoToTab(1)];
+ let keyaction_2 = KeyActionFromYaml {
+ action: actions_2.clone(),
+ key: vec![Key::F(1), Key::Char('t')],
+ };
+
+ let mut expected = ModeKeybinds::new();
+ expected.0.insert(Key::F(1), actions_2.clone());
+ expected.0.insert(Key::Backspace, actions_1.clone());
+ expected.0.insert(Key::Char('t'), actions_2);
+
+ assert_eq!(expected, ModeKeybinds::from(vec![keyaction_1, keyaction_2]));
+}
+
+#[test]
+fn unbind_single_mode() {
+ let unbind = Unbind::All(true);
+ let unbind_from_yaml = UnbindFromYaml { unbind };
+ let key_action_unbinds = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
+
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbinds);
+
+ let keybinds_from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::All(false),
+ };
+
+ let keybinds = Keybinds::unbind(keybinds_from_yaml);
+ let result = keybinds.0.get(&InputMode::Normal);
+ assert!(result.is_none());
+}
+
+#[test]
+fn unbind_multiple_modes() {
+ let unbind = Unbind::All(true);
+ let unbind_from_yaml = UnbindFromYaml { unbind };
+ let key_action_unbinds = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
+
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbinds.clone());
+ keys.insert(InputMode::Pane, key_action_unbinds);
+
+ let keybinds_from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::All(false),
+ };
+
+ let keybinds = Keybinds::unbind(keybinds_from_yaml);
+ let normal = keybinds.0.get(&InputMode::Normal);
+ let pane = keybinds.0.get(&InputMode::Pane);
+ assert!(normal.is_none());
+ assert!(pane.is_none());
+}
+
+#[test]
+fn unbind_single_keybind_single_mode() {
+ let unbind = Unbind::Keys(vec![Key::Alt('n')]);
+ let unbind_from_yaml = UnbindFromYaml { unbind };
+ let key_action_unbinds = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
+
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbinds);
+
+ let keybinds_from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::All(false),
+ };
+
+ let keybinds = Keybinds::unbind(keybinds_from_yaml);
+ let mode_keybinds = keybinds.0.get(&InputMode::Normal);
+ let result = mode_keybinds
+ .expect("Mode shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ assert!(result.is_none());
+}
+
+#[test]
+fn unbind_single_keybind_multiple_modes() {
+ let unbind_n = Unbind::Keys(vec![Key::Alt('n')]);
+ let unbind_h = Unbind::Keys(vec![Key::Alt('h')]);
+ let unbind_from_yaml_n = UnbindFromYaml { unbind: unbind_n };
+ let unbind_from_yaml_h = UnbindFromYaml { unbind: unbind_h };
+ let key_action_unbinds_n = vec![KeyActionUnbind::Unbind(unbind_from_yaml_n)];
+ let key_action_unbinds_h = vec![KeyActionUnbind::Unbind(unbind_from_yaml_h)];
+
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbinds_n);
+ keys.insert(InputMode::Pane, key_action_unbinds_h);
+
+ let keybinds_from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::All(false),
+ };
+
+ let keybinds = Keybinds::unbind(keybinds_from_yaml);
+ let normal = keybinds.0.get(&InputMode::Normal);
+ let pane = keybinds.0.get(&InputMode::Pane);
+ let result_normal = normal
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_pane = pane.expect("Mode shouldn't be empty").0.get(&Key::Alt('h'));
+ assert!(result_normal.is_none());
+ assert!(result_pane.is_none());
+}
+
+#[test]
+fn unbind_multiple_keybinds_single_mode() {
+ let unbind = Unbind::Keys(vec![Key::Alt('n'), Key::Ctrl('p')]);
+ let unbind_from_yaml = UnbindFromYaml { unbind };
+ let key_action_unbinds = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
+
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbinds);
+
+ let keybinds_from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::All(false),
+ };
+
+ let keybinds = Keybinds::unbind(keybinds_from_yaml);
+ let mode_keybinds = keybinds.0.get(&InputMode::Normal);
+ let result_n = mode_keybinds
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_p = mode_keybinds
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Ctrl('p'));
+ assert!(result_n.is_none());
+ assert!(result_p.is_none());
+}
+
+#[test]
+fn unbind_multiple_keybinds_multiple_modes() {
+ let unbind_normal = Unbind::Keys(vec![Key::Alt('n'), Key::Ctrl('p')]);
+ let unbind_resize = Unbind::Keys(vec![Key::Char('h'), Key::Ctrl('r')]);
+ let unbind_from_yaml_normal = UnbindFromYaml {
+ unbind: unbind_normal,
+ };
+ let unbind_from_yaml_resize = UnbindFromYaml {
+ unbind: unbind_resize,
+ };
+ let key_action_unbinds_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml_normal)];
+ let key_action_unbinds_resize = vec![KeyActionUnbind::Unbind(unbind_from_yaml_resize)];
+
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbinds_normal);
+ keys.insert(InputMode::Resize, key_action_unbinds_resize);
+
+ let keybinds_from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::All(false),
+ };
+
+ let keybinds = Keybinds::unbind(keybinds_from_yaml);
+ let mode_keybinds_normal = keybinds.0.get(&InputMode::Normal);
+ let mode_keybinds_resize = keybinds.0.get(&InputMode::Resize);
+ let result_normal_1 = mode_keybinds_normal
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_normal_2 = mode_keybinds_normal
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Ctrl('p'));
+ let result_resize_1 = mode_keybinds_resize
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Char('h'));
+ let result_resize_2 = mode_keybinds_resize
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Ctrl('r'));
+ assert!(result_normal_1.is_none());
+ assert!(result_resize_1.is_none());
+ assert!(result_normal_2.is_none());
+ assert!(result_resize_2.is_none());
+}
+
+#[test]
+fn unbind_multiple_keybinds_all_modes() {
+ let unbind = Unbind::Keys(vec![Key::Alt('n'), Key::Alt('h')]);
+ let keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ let keybinds_from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind,
+ };
+
+ let keybinds = Keybinds::unbind(keybinds_from_yaml);
+ let mode_keybinds_normal = keybinds.0.get(&InputMode::Normal);
+ let mode_keybinds_resize = keybinds.0.get(&InputMode::Resize);
+ let result_normal_1 = mode_keybinds_normal
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_normal_2 = mode_keybinds_normal
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Ctrl('h'));
+ let result_resize_1 = mode_keybinds_resize
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Char('n'));
+ let result_resize_2 = mode_keybinds_resize
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Ctrl('h'));
+ assert!(result_normal_1.is_none());
+ assert!(result_resize_1.is_none());
+ assert!(result_normal_2.is_none());
+ assert!(result_resize_2.is_none());
+}
+
+#[test]
+fn unbind_all_toplevel_single_key_single_mode() {
+ let unbind = Unbind::Keys(vec![Key::Alt('h')]);
+ let unbind_from_yaml = UnbindFromYaml { unbind };
+ let key_action_unbinds_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbinds_normal);
+ let from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::All(true),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+ assert_eq!(keybinds_from_yaml, Keybinds::new());
+}
+
+#[test]
+fn unbind_all_toplevel_single_key_multiple_modes() {
+ let unbind_n = Unbind::Keys(vec![Key::Alt('n')]);
+ let unbind_h = Unbind::Keys(vec![Key::Alt('h')]);
+ let unbind_from_yaml_n = UnbindFromYaml { unbind: unbind_n };
+ let unbind_from_yaml_h = UnbindFromYaml { unbind: unbind_h };
+ let key_action_unbinds_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml_n)];
+ let key_action_unbinds_pane = vec![KeyActionUnbind::Unbind(unbind_from_yaml_h)];
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbinds_normal);
+ keys.insert(InputMode::Pane, key_action_unbinds_pane);
+ let from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::All(true),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+ assert_eq!(keybinds_from_yaml, Keybinds::new());
+}
+
+#[test]
+fn unbind_all_toplevel_multiple_key_multiple_modes() {
+ let unbind_n = Unbind::Keys(vec![Key::Alt('n'), Key::Ctrl('p')]);
+ let unbind_h = Unbind::Keys(vec![Key::Alt('h'), Key::Ctrl('t')]);
+ let unbind_from_yaml_n = UnbindFromYaml { unbind: unbind_n };
+ let unbind_from_yaml_h = UnbindFromYaml { unbind: unbind_h };
+ let key_action_unbinds_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml_n)];
+ let key_action_unbinds_pane = vec![KeyActionUnbind::Unbind(unbind_from_yaml_h)];
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbinds_normal);
+ keys.insert(InputMode::Pane, key_action_unbinds_pane);
+ let from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::All(true),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
assert_eq!(keybinds_from_yaml, Keybinds::new());
}
+
+#[test]
+fn unbind_all_toplevel_all_key_multiple_modes() {
+ let unbind = Unbind::All(true);
+ let unbind_from_yaml = UnbindFromYaml { unbind };
+ let key_action_unbinds_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml.clone())];
+ let key_action_unbinds_pane = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbinds_normal);
+ keys.insert(InputMode::Pane, key_action_unbinds_pane);
+ let from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::All(true),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+ assert_eq!(keybinds_from_yaml, Keybinds::new());
+}
+
+#[test]
+fn unbind_single_keybind_all_modes() {
+ let keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ let from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::Keys(vec![Key::Alt('n')]),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+
+ let result_normal = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_pane = keybinds_from_yaml
+ .0
+ .get(&InputMode::Pane)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_resize = keybinds_from_yaml
+ .0
+ .get(&InputMode::Resize)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_tab = keybinds_from_yaml
+ .0
+ .get(&InputMode::Tab)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+
+ assert!(result_normal.is_none());
+ assert!(result_pane.is_none());
+ assert!(result_resize.is_none());
+ assert!(result_tab.is_none());
+}
+
+#[test]
+fn unbind_single_toplevel_single_key_single_mode_identical() {
+ let unbind = Unbind::Keys(vec![Key::Alt('n')]);
+ let unbind_from_yaml = UnbindFromYaml { unbind };
+ let key_action_unbind = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbind);
+ let from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::Keys(vec![Key::Alt('n')]),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+
+ let result_normal = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_pane = keybinds_from_yaml
+ .0
+ .get(&InputMode::Pane)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_resize = keybinds_from_yaml
+ .0
+ .get(&InputMode::Resize)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_tab = keybinds_from_yaml
+ .0
+ .get(&InputMode::Tab)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+
+ assert!(result_normal.is_none());
+ assert!(result_pane.is_none());
+ assert!(result_resize.is_none());
+ assert!(result_tab.is_none());
+}
+
+#[test]
+fn unbind_single_toplevel_single_key_single_mode_differing() {
+ let unbind = Unbind::Keys(vec![Key::Alt('l')]);
+ let unbind_from_yaml = UnbindFromYaml { unbind };
+ let key_action_unbind = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbind);
+ let from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::Keys(vec![Key::Alt('n')]),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+
+ let result_normal_n = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_normal_l = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('l'));
+ let result_resize_n = keybinds_from_yaml
+ .0
+ .get(&InputMode::Resize)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_resize_l = keybinds_from_yaml
+ .0
+ .get(&InputMode::Resize)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('l'));
+
+ assert!(result_normal_n.is_none());
+ assert!(result_normal_l.is_none());
+ assert!(result_resize_n.is_none());
+ assert!(result_resize_l.is_some());
+}
+
+#[test]
+fn unbind_single_toplevel_single_key_multiple_modes() {
+ let unbind = Unbind::Keys(vec![Key::Alt('l')]);
+ let unbind_from_yaml = UnbindFromYaml { unbind };
+ let key_action_unbind = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbind.clone());
+ keys.insert(InputMode::Pane, key_action_unbind);
+ let from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::Keys(vec![Key::Alt('n')]),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+
+ let result_normal_n = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_normal_l = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('l'));
+ let result_pane_n = keybinds_from_yaml
+ .0
+ .get(&InputMode::Pane)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_pane_l = keybinds_from_yaml
+ .0
+ .get(&InputMode::Pane)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('l'));
+
+ assert!(result_normal_n.is_none());
+ assert!(result_normal_l.is_none());
+ assert!(result_pane_n.is_none());
+ assert!(result_pane_l.is_none());
+}
+
+#[test]
+fn unbind_single_toplevel_multiple_keys_single_mode() {
+ let unbind = Unbind::Keys(vec![
+ Key::Alt('l'),
+ Key::Alt('h'),
+ Key::Alt('j'),
+ Key::Alt('k'),
+ ]);
+ let unbind_from_yaml = UnbindFromYaml { unbind };
+ let key_action_unbind = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbind.clone());
+ keys.insert(InputMode::Pane, key_action_unbind);
+ let from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::Keys(vec![Key::Alt('n')]),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+
+ let result_normal_n = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_normal_l = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('l'));
+ let result_normal_k = keybinds_from_yaml
+ .0
+ .get(&InputMode::Pane)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('k'));
+ let result_normal_h = keybinds_from_yaml
+ .0
+ .get(&InputMode::Pane)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('h'));
+
+ assert!(result_normal_n.is_none());
+ assert!(result_normal_l.is_none());
+ assert!(result_normal_h.is_none());
+ assert!(result_normal_k.is_none());
+}
+
+#[test]
+fn unbind_single_toplevel_multiple_keys_multiple_modes() {
+ let unbind_normal = Unbind::Keys(vec![Key::Alt('l'), Key::Ctrl('p')]);
+ let unbind_from_yaml_normal = UnbindFromYaml {
+ unbind: unbind_normal,
+ };
+ let key_action_unbind_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml_normal)];
+ let unbind = Unbind::Keys(vec![Key::Alt('l'), Key::Alt('k')]);
+ let unbind_from_yaml = UnbindFromYaml { unbind };
+ let key_action_unbind = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbind_normal);
+ keys.insert(InputMode::Pane, key_action_unbind);
+ let from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::Keys(vec![Key::Alt('n')]),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+
+ let result_normal_n = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_normal_p = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Ctrl('p'));
+ let result_normal_l = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('l'));
+ let result_pane_p = keybinds_from_yaml
+ .0
+ .get(&InputMode::Pane)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Ctrl('p'));
+ let result_pane_n = keybinds_from_yaml
+ .0
+ .get(&InputMode::Pane)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_pane_l = keybinds_from_yaml
+ .0
+ .get(&InputMode::Pane)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('l'));
+
+ assert!(result_normal_n.is_none());
+ assert!(result_normal_l.is_none());
+ assert!(result_normal_p.is_none());
+ assert!(result_pane_n.is_none());
+ assert!(result_pane_p.is_some());
+ assert!(result_pane_l.is_none());
+}
+
+#[test]
+fn uppercase_and_lowercase_are_distinct() {
+ let key_action_n = KeyActionFromYaml {
+ key: vec![Key::Char('n')],
+ action: vec![Action::NewTab],
+ };
+ let key_action_large_n = KeyActionFromYaml {
+ key: vec![Key::Char('N')],
+ action: vec![Action::NewPane(None)],
+ };
+
+ let key_action_unbind = vec![
+ KeyActionUnbind::KeyAction(key_action_n),
+ KeyActionUnbind::KeyAction(key_action_large_n),
+ ];
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbind);
+ let from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::All(false),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+ let result_n = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Char('n'));
+ let result_large_n = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Char('N'));
+
+ assert!(result_n.is_some());
+ assert!(result_large_n.is_some());
+}
diff --git a/src/common/mod.rs b/src/common/mod.rs
index 6061f1a6..c91e1e05 100644
--- a/src/common/mod.rs
+++ b/src/common/mod.rs
@@ -3,73 +3,12 @@ pub mod errors;
pub mod input;
pub mod ipc;
pub mod os_input_output;
-pub mod pty_bus;
+pub mod pty;
pub mod screen;
pub mod setup;
+pub mod thread_bus;
pub mod utils;
pub mod wasm_vm;
use crate::panes::PaneId;
use crate::server::ServerInstruction;
-use async_std::task_local;
-use errors::{get_current_ctx, ErrorContext};
-use std::cell::RefCell;
-use std::sync::mpsc;
-
-/// An [MPSC](mpsc) asynchronous channel with added error context.
-pub type ChannelWithContext<T> = (
- mpsc::Sender<(T, ErrorContext)>,
- mpsc::Receiver<(T, ErrorContext)>,
-);
-/// An [MPSC](mpsc) synchronous channel with added error context.
-pub type SyncChannelWithContext<T> = (
- mpsc::SyncSender<(T, ErrorContext)>,
- mpsc::Receiver<(T, ErrorContext)>,
-);
-
-/// Wrappers around the two standard [MPSC](mpsc) sender types, [`mpsc::Sender`] and [`mpsc::SyncSender`], with an additional [`ErrorContext`].
-#[derive(Clone)]
-pub enum SenderType<T: Clone> {
- /// A wrapper around an [`mpsc::Sender`], adding an [`ErrorContext`].
- Sender(mpsc::Sender<(T, ErrorContext)>),
- /// A wrapper around an [`mpsc::SyncSender`], adding an [`ErrorContext`].
- SyncSender(mpsc::SyncSender<(T, ErrorContext)>),
-}
-
-/// Sends messages on an [MPSC](std::sync::mpsc) channel, along with an [`ErrorContext`],
-/// synchronously or asynchronously depending on the underlying [`SenderType`].
-#[derive(Clone)]
-pub struct SenderWithContext<T: Clone> {
- sender: SenderType<T>,
-}
-
-impl<T: Clone> SenderWithContext<T> {
- pub fn new(sender: SenderType<T>) -> Self {
- Self { sender }
- }
-
- /// Sends an event, along with the current [`ErrorContext`], on this
- /// [`SenderWithContext`]'s channel.
- pub fn send(&self, event: T) -> Result<(), mpsc::SendError<(T, ErrorContext)>> {
- let err_ctx = get_current_ctx();
- match self.sender {
- SenderType::Sender(ref s) => s.send((event, err_ctx)),
- SenderType::SyncSender(ref s) => s.send((event, err_ctx)),
- }
- }
-}
-
-unsafe impl<T: Clone> Send for SenderWithContext<T> {}
-unsafe impl<T: Clone> Sync for SenderWithContext<T> {}
-
-thread_local!(
- /// A key to some thread local storage (TLS) that holds a representation of the thread's call
- /// stack in the form of an [`ErrorContext`].
- pub static OPENCALLS: RefCell<ErrorContext> = RefCell::default()
-);
-
-task_local! {
- /// A key to some task local storage that holds a representation of the task's call
- /// stack in the form of an [`ErrorContext`].
- static ASYNCOPENCALLS: RefCell<ErrorContext> = RefCell::default()
-}
diff --git a/src/common/os_input_output.rs b/src/common/os_input_output.rs
index 5539ae70..6f891e0d 100644
--- a/src/common/os_input_output.rs
+++ b/src/common/os_input_output.rs
@@ -25,10 +25,10 @@ use zellij_tile::data::Palette;
const UNIX_PERMISSIONS: u32 = 0o700;
-pub fn set_permissions(path: &Path) {
- let mut permissions = fs::metadata(path).unwrap().permissions();
+pub fn set_permissions(path: &Path) -> io::Result<()> {
+ let mut permissions = fs::metadata(path)?.permissions();
permissions.set_mode(UNIX_PERMISSIONS);
- fs::set_permissions(path, permissions).unwrap();
+ fs::set_permissions(path, permissions)
}
fn into_raw_mode(pid: RawFd) {
diff --git a/src/common/pty_bus.rs b/src/common/pty.rs
index 31696186..3c471d6a 100644
--- a/src/common/pty_bus.rs
+++ b/src/common/pty.rs
@@ -1,23 +1,21 @@
-use ::async_std::stream::*;
-use ::async_std::task;
-use ::async_std::task::*;
-use ::std::collections::HashMap;
-use ::std::os::unix::io::RawFd;
-use ::std::pin::*;
-use ::std::sync::mpsc::Receiver;
-use ::std::time::{Duration, Instant};
+use async_std::stream::*;
+use async_std::task;
+use async_std::task::*;
+use std::collections::HashMap;
+use std::os::unix::io::RawFd;
use std::path::PathBuf;
+use std::pin::*;
+use std::time::{Duration, Instant};
-use super::SenderWithContext;
+use crate::client::panes::PaneId;
+use crate::common::errors::{get_current_ctx, ContextType, PtyContext};
+use crate::common::screen::ScreenInstruction;
+use crate::common::thread_bus::{Bus, ThreadSenders};
use crate::layout::Layout;
use crate::os_input_output::ServerOsApi;
+use crate::server::ServerInstruction;
use crate::utils::logging::debug_to_file;
-use crate::{
- errors::{get_current_ctx, ContextType, ErrorContext},
- panes::PaneId,
- screen::ScreenInstruction,
- wasm_vm::PluginInstruction,
-};
+use crate::wasm_vm::PluginInstruction;
pub struct ReadFromPid {
pid: RawFd,
@@ -79,19 +77,72 @@ pub enum PtyInstruction {
Exit,
}
-pub struct PtyBus {
- pub receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>,
+pub struct Pty {
+ pub bus: Bus<PtyInstruction>,
pub id_to_child_pid: HashMap<RawFd, RawFd>,
- pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
- send_plugin_instructions: SenderWithContext<PluginInstruction>,
- os_input: Box<dyn ServerOsApi>,
debug_to_file: bool,
task_handles: HashMap<RawFd, JoinHandle<()>>,
}
+pub fn pty_thread_main(mut pty: Pty, maybe_layout: Option<Layout>) {
+ loop {
+ let (event, mut err_ctx) = pty.bus.recv().expect("failed to receive event on channel");
+ err_ctx.add_call(ContextType::Pty(PtyContext::from(&event)));
+ match event {
+ PtyInstruction::SpawnTerminal(file_to_open) => {
+ let pid = pty.spawn_terminal(file_to_open);
+ pty.bus
+ .senders
+ .send_to_screen(ScreenInstruction::NewPane(PaneId::Terminal(pid)))
+ .unwrap();
+ }
+ PtyInstruction::SpawnTerminalVertically(file_to_open) => {
+ let pid = pty.spawn_terminal(file_to_open);
+ pty.bus
+ .senders
+ .send_to_screen(ScreenInstruction::VerticalSplit(PaneId::Terminal(pid)))
+ .unwrap();
+ }
+ PtyInstruction::SpawnTerminalHorizontally(file_to_open) => {
+ let pid = pty.spawn_terminal(file_to_open);
+ pty.bus
+ .senders
+ .send_to_screen(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid)))
+ .unwrap();
+ }
+ PtyInstruction::NewTab => {
+ if let Some(layout) = maybe_layout.clone() {
+ pty.spawn_terminals_for_layout(layout);
+ } else {
+ let pid = pty.spawn_terminal(None);
+ pty.bus
+ .senders
+ .send_to_screen(ScreenInstruction::NewTab(pid))
+ .unwrap();
+ }
+ }
+ PtyInstruction::ClosePane(id) => {
+ pty.close_pane(id);
+ pty.bus
+ .senders
+ .send_to_server(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ PtyInstruction::CloseTab(ids) => {
+ pty.close_tab(ids);
+ pty.bus
+ .senders
+ .send_to_server(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ PtyInstruction::Exit => break,
+ }
+ }
+}
+
fn stream_terminal_bytes(
pid: RawFd,
- send_screen_instructions: SenderWithContext<ScreenInstruction>,
+ senders: ThreadSenders,
os_input: Box<dyn ServerOsApi>,
debug: bool,
) -> JoinHandle<()> {
@@ -113,7 +164,7 @@ fn stream_terminal_bytes(
}
}
if !bytes_is_empty {
- let _ = send_screen_instructions.send(ScreenInstruction::PtyBytes(pid, bytes));
+ let _ = senders.send_to_screen(ScreenInstruction::PtyBytes(pid, bytes));
// for UX reasons, if we got something on the wire, we only send the render notice if:
// 1. there aren't any more bytes on the wire afterwards
// 2. a certain period (currently 30ms) has elapsed since the last render
@@ -124,9 +175,7 @@ fn stream_terminal_bytes(
Some(receive_time) => {
if receive_time.elapsed() > max_render_pause {
pending_render = false;
- send_screen_instructions
- .send(ScreenInstruction::Render)
- .unwrap();
+ let _ = senders.send_to_screen(ScreenInstruction::Render);
last_byte_receive_time = Some(Instant::now());
} else {
pending_render = true;
@@ -140,53 +189,44 @@ fn stream_terminal_bytes(
} else {
if pending_render {
pending_render = false;
- send_screen_instructions
- .send(ScreenInstruction::Render)
- .unwrap();
+ let _ = senders.send_to_screen(ScreenInstruction::Render);
}
last_byte_receive_time = None;
task::sleep(::std::time::Duration::from_millis(10)).await;
}
}
- send_screen_instructions
- .send(ScreenInstruction::Render)
- .unwrap();
+ senders.send_to_screen(ScreenInstruction::Render).unwrap();
#[cfg(not(test))]
// this is a little hacky, and is because the tests end the file as soon as
// we read everything, rather than hanging until there is new data
// a better solution would be to fix the test fakes, but this will do for now
- send_screen_instructions
- .send(ScreenInstruction::ClosePane(PaneId::Terminal(pid)))
+ senders
+ .send_to_screen(ScreenInstruction::ClosePane(PaneId::Terminal(pid)))
.unwrap();
}
})
}
-impl PtyBus {
- pub fn new(
- receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>,
- send_screen_instructions: SenderWithContext<ScreenInstruction>,
- send_plugin_instructions: SenderWithContext<PluginInstruction>,
- os_input: Box<dyn ServerOsApi>,
- debug_to_file: bool,
- ) -> Self {
- PtyBus {
- receive_pty_instructions,
- os_input,
+impl Pty {
+ pub fn new(bus: Bus<PtyInstruction>, debug_to_file: bool) -> Self {
+ Pty {
+ bus,
id_to_child_pid: HashMap::new(),
- send_screen_instructions,
- send_plugin_instructions,
debug_to_file,
task_handles: HashMap::new(),
}
}
pub fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> RawFd {
- let (pid_primary, pid_secondary): (RawFd, RawFd) =
- self.os_input.spawn_terminal(file_to_open);
+ let (pid_primary, pid_secondary): (RawFd, RawFd) = self
+ .bus
+ .os_input
+ .as_mut()
+ .unwrap()
+ .spawn_terminal(file_to_open);
let task_handle = stream_terminal_bytes(
pid_primary,
- self.send_screen_instructions.clone(),
- self.os_input.clone(),
+ self.bus.senders.clone(),
+ self.bus.os_input.as_ref().unwrap().clone(),
self.debug_to_file,
);
self.task_handles.insert(pid_primary, task_handle);
@@ -197,12 +237,14 @@ impl PtyBus {
let total_panes = layout.total_terminal_panes();
let mut new_pane_pids = vec![];
for _ in 0..total_panes {
- let (pid_primary, pid_secondary): (RawFd, RawFd) = self.os_input.spawn_terminal(None);
+ let (pid_primary, pid_secondary): (RawFd, RawFd) =
+ self.bus.os_input.as_mut().unwrap().spawn_terminal(None);
self.id_to_child_pid.insert(pid_primary, pid_secondary);
new_pane_pids.push(pid_primary);
}
- self.send_screen_instructions
- .send(ScreenInstruction::ApplyLayout(
+ self.bus
+ .senders
+ .send_to_screen(ScreenInstruction::ApplyLayout(
layout,
new_pane_pids.clone(),
))
@@ -210,8 +252,8 @@ impl PtyBus {
for id in new_pane_pids {
let task_handle = stream_terminal_bytes(
id,
- self.send_screen_instructions.clone(),
- self.os_input.clone(),
+ self.bus.senders.clone(),
+ self.bus.os_input.as_ref().unwrap().clone(),
self.debug_to_file,
);
self.task_handles.insert(id, task_handle);
@@ -222,15 +264,16 @@ impl PtyBus {
PaneId::Terminal(id) => {
let child_pid = self.id_to_child_pid.remove(&id).unwrap();
let handle = self.task_handles.remove(&id).unwrap();
- self.os_input.kill(child_pid).unwrap();
+ self.bus.os_input.as_mut().unwrap().kill(child_pid).unwrap();
task::block_on(async {
handle.cancel().await;
});
}
- PaneId::Plugin(pid) => self
- .send_plugin_instructions
- .send(PluginInstruction::Unload(pid))
- .unwrap(),
+ PaneId::Plugin(pid) => drop(
+ self.bus
+ .senders
+ .send_to_plugin(PluginInstruction::Unload(pid)),
+ ),
}
}
pub fn close_tab(&mut self, ids: Vec<PaneId>) {
@@ -240,7 +283,7 @@ impl PtyBus {
}
}
-impl Drop for PtyBus {
+impl Drop for Pty {
fn drop(&mut self) {
let child_ids: Vec<RawFd> = self.id_to_child_pid.keys().copied().collect();
for id in child_ids {
diff --git a/src/common/screen.rs b/src/common/screen.rs
index 562b0ffe..bc99f953 100644
--- a/src/common/screen.rs
+++ b/src/common/screen.rs
@@ -3,18 +3,19 @@
use std::collections::BTreeMap;
use std::os::unix::io::RawFd;
use std::str;
-use std::sync::mpsc::Receiver;
-use crate::common::SenderWithContext;
-use crate::os_input_output::ServerOsApi;
+use crate::common::input::options::Options;
+use crate::common::pty::{PtyInstruction, VteBytes};
+use crate::common::thread_bus::Bus;
+use crate::errors::{ContextType, ScreenContext};
+use crate::layout::Layout;
+use crate::panes::PaneId;
use crate::panes::PositionAndSize;
-use crate::pty_bus::{PtyInstruction, VteBytes};
use crate::server::ServerInstruction;
use crate::tab::Tab;
-use crate::{errors::ErrorContext, wasm_vm::PluginInstruction};
-use crate::{layout::Layout, panes::PaneId};
+use crate::wasm_vm::PluginInstruction;
-use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, TabInfo};
+use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, PluginCapabilities, TabInfo};
/// Instructions that can be sent to the [`Screen`].
#[derive(Debug, Clone)]
@@ -33,9 +34,11 @@ pub enum ScreenInstruction {
FocusNextPane,
FocusPreviousPane,
MoveFocusLeft,
+ MoveFocusLeftOrPreviousTab,
MoveFocusDown,
MoveFocusUp,
MoveFocusRight,
+ MoveFocusRightOrNextTab,
Exit,
ScrollUp,
ScrollDown,
@@ -52,7 +55,7 @@ pub enum ScreenInstruction {
NewTab(RawFd),
SwitchTabNext,
SwitchTabPrev,
- ToggleActiveSyncPanes,
+ ToggleActiveSyncTab,
CloseTab,
GoToTab(u32),
UpdateTabName(Vec<u8>),
@@ -63,55 +66,37 @@ pub enum ScreenInstruction {
/// A [`Screen`] holds multiple [`Tab`]s, each one holding multiple [`panes`](crate::client::panes).
/// It only directly controls which tab is active, delegating the rest to the individual `Tab`.
pub struct Screen {
- /// A [`ScreenInstruction`] and [`ErrorContext`] receiver.
- pub receiver: Receiver<(ScreenInstruction, ErrorContext)>,
+ /// A Bus for sending and receiving messages with the other threads.
+ pub bus: Bus<ScreenInstruction>,
/// An optional maximal amount of panes allowed per [`Tab`] in this [`Screen`] instance.
max_panes: Option<usize>,
/// A map between this [`Screen`]'s tabs and their ID/key.
tabs: BTreeMap<usize, Tab>,
- /// A [`PluginInstruction`] and [`ErrorContext`] sender.
- pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
- /// An [`PtyInstruction`] and [`ErrorContext`] sender.
- pub send_pty_instructions: SenderWithContext<PtyInstruction>,
- /// An [`ServerInstruction`] and [`ErrorContext`] sender.
- pub send_server_instructions: SenderWithContext<ServerInstruction>,
/// The full size of this [`Screen`].
full_screen_ws: PositionAndSize,
/// The index of this [`Screen`]'s active [`Tab`].
active_tab_index: Option<usize>,
- /// The [`ServerOsApi`] this [`Screen`] uses.
- os_api: Box<dyn ServerOsApi>,
mode_info: ModeInfo,
input_mode: InputMode,
colors: Palette,
}
impl Screen {
- // FIXME: This lint needs actual fixing! Maybe by bundling the Senders
/// Creates and returns a new [`Screen`].
- #[allow(clippy::too_many_arguments)]
pub fn new(
- receive_screen_instructions: Receiver<(ScreenInstruction, ErrorContext)>,
- send_plugin_instructions: SenderWithContext<PluginInstruction>,
- send_pty_instructions: SenderWithContext<PtyInstruction>,
- send_server_instructions: SenderWithContext<ServerInstruction>,
+ bus: Bus<ScreenInstruction>,
full_screen_ws: &PositionAndSize,
- os_api: Box<dyn ServerOsApi>,
max_panes: Option<usize>,
mode_info: ModeInfo,
input_mode: InputMode,
colors: Palette,
) -> Self {
Screen {
- receiver: receive_screen_instructions,
+ bus,
max_panes,
- send_plugin_instructions,
- send_pty_instructions,
- send_server_instructions,
full_screen_ws: *full_screen_ws,
active_tab_index: None,
tabs: BTreeMap::new(),
- os_api,
mode_info,
input_mode,
colors,
@@ -128,10 +113,8 @@ impl Screen {
position,
String::new(),
&self.full_screen_ws,
- self.os_api.clone(),
- self.send_plugin_instructions.clone(),
- self.send_pty_instructions.clone(),
- self.send_server_instructions.clone(),
+ self.bus.os_input.as_ref().unwrap().clone(),
+ self.bus.senders.clone(),
self.max_panes,
Some(PaneId::Terminal(pane_id)),
self.mode_info.clone(),
@@ -215,13 +198,15 @@ impl Screen {
// below we don't check the result of sending the CloseTab instruction to the pty thread
// because this might be happening when the app is closing, at which point the pty thread
// has already closed and this would result in an error
- self.send_pty_instructions
- .send(PtyInstruction::CloseTab(pane_ids))
+ self.bus
+ .senders
+ .send_to_pty(PtyInstruction::CloseTab(pane_ids))
.unwrap();
if self.tabs.is_empty() {
self.active_tab_index = None;
- self.send_server_instructions
- .send(ServerInstruction::Render(None))
+ self.bus
+ .senders
+ .send_to_server(ServerInstruction::Render(None))
.unwrap();
} else {
for t in self.tabs.values_mut() {
@@ -284,10 +269,8 @@ impl Screen {
position,
String::new(),
&self.full_screen_ws,
- self.os_api.clone(),
- self.send_plugin_instructions.clone(),
- self.send_pty_instructions.clone(),
- self.send_server_instructions.clone(),
+ self.bus.os_input.as_ref().unwrap().clone(),
+ self.bus.senders.clone(),
self.max_panes,
None,
self.mode_info.clone(),
@@ -311,8 +294,9 @@ impl Screen {
is_sync_panes_active: tab.is_sync_panes_active(),
});
}
- self.send_plugin_instructions
- .send(PluginInstruction::Update(None, Event::TabUpdate(tab_data)))
+ self.bus
+ .senders
+ .send_to_plugin(PluginInstruction::Update(None, Event::TabUpdate(tab_data)))
.unwrap();
}
@@ -340,3 +324,272 @@ impl Screen {
}
}
}
+
+pub fn screen_thread_main(
+ bus: Bus<ScreenInstruction>,
+ max_panes: Option<usize>,
+ full_screen_ws: PositionAndSize,
+ config_options: Options,
+) {
+ let colors = bus.os_input.as_ref().unwrap().load_palette();
+ let capabilities = config_options.simplified_ui;
+
+ let mut screen = Screen::new(
+ bus,
+ &full_screen_ws,
+ max_panes,
+ ModeInfo {
+ palette: colors,
+ capabilities: PluginCapabilities {
+ arrow_fonts: capabilities,
+ },
+ ..ModeInfo::default()
+ },
+ InputMode::Normal,
+ colors,
+ );
+ loop {
+ let (event, mut err_ctx) = screen
+ .bus
+ .recv()
+ .expect("failed to receive event on channel");
+ err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event)));
+ match event {
+ ScreenInstruction::PtyBytes(pid, vte_bytes) => {
+ let active_tab = screen.get_active_tab_mut().unwrap();
+ if active_tab.has_terminal_pid(pid) {
+ // it's most likely that this event is directed at the active tab
+ // look there first
+ active_tab.handle_pty_bytes(pid, vte_bytes);
+ } else {
+ // if this event wasn't directed at the active tab, start looking
+ // in other tabs
+ let all_tabs = screen.get_tabs_mut();
+ for tab in all_tabs.values_mut() {
+ if tab.has_terminal_pid(pid) {
+ tab.handle_pty_bytes(pid, vte_bytes);
+ break;
+ }
+ }
+ }
+ }
+ ScreenInstruction::Render => {
+ screen.render();
+ }
+ ScreenInstruction::NewPane(pid) => {
+ screen.get_active_tab_mut().unwrap().new_pane(pid);
+ screen
+ .bus
+ .senders
+ .send_to_server(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ ScreenInstruction::HorizontalSplit(pid) => {
+ screen.get_active_tab_mut().unwrap().horizontal_split(pid);
+ screen
+ .bus
+ .senders
+ .send_to_server(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ ScreenInstruction::VerticalSplit(pid) => {
+ screen.get_active_tab_mut().unwrap().vertical_split(pid);
+ screen
+ .bus
+ .senders
+ .send_to_server(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ ScreenInstruction::WriteCharacter(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();
+ }
+ ScreenInstruction::ResizeRight => {
+ screen.get_active_tab_mut().unwrap().resize_right();
+ }
+ ScreenInstruction::ResizeDown => {
+ screen.get_active_tab_mut().unwrap().resize_down();
+ }
+ ScreenInstruction::ResizeUp => {
+ screen.get_active_tab_mut().unwrap().resize_up();
+ }
+ ScreenInstruction::SwitchFocus => {
+ screen.get_active_tab_mut().unwrap().move_focus();
+ }
+ ScreenInstruction::FocusNextPane => {
+ screen.get_active_tab_mut().unwrap().focus_next_pane();
+ }
+ ScreenInstruction::FocusPreviousPane => {
+ screen.get_active_tab_mut().unwrap().focus_previous_pane();
+ }
+ ScreenInstruction::MoveFocusLeft => {
+ screen.get_active_tab_mut().unwrap().move_focus_left();
+ }
+ ScreenInstruction::MoveFocusLeftOrPreviousTab => {
+ if !screen.get_active_tab_mut().unwrap().move_focus_left() {
+ screen.switch_tab_prev();
+ }
+ screen
+ .bus
+ .senders
+ .send_to_server(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ ScreenInstruction::MoveFocusDown => {
+ screen.get_active_tab_mut().unwrap().move_focus_down();
+ }
+ ScreenInstruction::MoveFocusRight => {
+ screen.get_active_tab_mut().unwrap().move_focus_right();
+ }
+ ScreenInstruction::MoveFocusRightOrNextTab => {
+ if !screen.get_active_tab_mut().unwrap().move_focus_right() {
+ screen.switch_tab_next();
+ }
+ screen
+ .bus
+ .senders
+ .send_to_server(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ ScreenInstruction::MoveFocusUp => {
+ screen.get_active_tab_mut().unwrap().move_focus_up();
+ }
+ ScreenInstruction::ScrollUp => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .scroll_active_terminal_up();
+ }
+ ScreenInstruction::ScrollDown => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .scroll_active_terminal_down();
+ }
+ ScreenInstruction::PageScrollUp => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .scroll_active_terminal_up_page();
+ }
+ ScreenInstruction::PageScrollDown => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .scroll_active_terminal_down_page();
+ }
+ ScreenInstruction::ClearScroll => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .clear_active_terminal_scroll();
+ }
+ ScreenInstruction::CloseFocusedPane => {
+ screen.get_active_tab_mut().unwrap().close_focused_pane();
+ screen.render();
+ }
+ ScreenInstruction::SetSelectable(id, selectable) => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .set_pane_selectable(id, selectable);
+ }
+ ScreenInstruction::SetMaxHeight(id, max_height) => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .set_pane_max_height(id, max_height);
+ }
+ ScreenInstruction::SetInvisibleBorders(id, invisible_borders) => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .set_pane_invisible_borders(id, invisible_borders);
+ screen.render();
+ }
+ ScreenInstruction::ClosePane(id) => {
+ screen.get_active_tab_mut().unwrap().close_pane(id);
+ screen.render();
+ }
+ ScreenInstruction::ToggleActiveTerminalFullscreen => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .toggle_active_pane_fullscreen();
+ }
+ ScreenInstruction::NewTab(pane_id) => {
+ screen.new_tab(pane_id);
+ screen
+ .bus
+ .senders
+ .send_to_server(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ ScreenInstruction::SwitchTabNext => {
+ screen.switch_tab_next();
+ screen
+ .bus
+ .senders
+ .send_to_server(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ ScreenInstruction::SwitchTabPrev => {
+ screen.switch_tab_prev();
+ screen
+ .bus
+ .senders
+ .send_to_server(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ ScreenInstruction::CloseTab => {
+ screen.close_tab();
+ screen
+ .bus
+ .senders
+ .send_to_server(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ ScreenInstruction::ApplyLayout(layout, new_pane_pids) => {
+ screen.apply_layout(layout, new_pane_pids);
+ screen
+ .bus
+ .senders
+ .send_to_server(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ ScreenInstruction::GoToTab(tab_index) => {
+ screen.go_to_tab(tab_index as usize);
+ screen
+ .bus
+ .senders
+ .send_to_server(ServerInstruction::UnblockInputThread)
+ .unwrap();
+ }
+ ScreenInstruction::UpdateTabName(c) => {
+ screen.update_active_tab_name(c);
+ }
+ ScreenInstruction::TerminalResize(new_size) => {
+ screen.resize_to_screen(new_size);
+ }
+ ScreenInstruction::ChangeMode(mode_info) => {
+ screen.change_mode(mode_info);
+ }
+ ScreenInstruction::ToggleActiveSyncTab => {
+ screen
+ .get_active_tab_mut()
+ .unwrap()
+ .toggle_sync_panes_is_active();
+ screen.update_tabs();
+ }
+ ScreenInstruction::Exit => {
+ break;
+ }
+ }
+ }
+}
diff --git a/src/common/setup.rs b/src/common/setup.rs
index bcda4589..5913211a 100644
--- a/src/common/setup.rs
+++ b/src/common/setup.rs
@@ -6,7 +6,7 @@ use directories_next::BaseDirs;
use std::io::Write;
use std::{fs, path::Path, path::PathBuf};
-const CONFIG_LOCATION: &str = "/.config/zellij";
+const CONFIG_LOCATION: &str = ".config/zellij";
#[macro_export]
macro_rules! asset_map {
@@ -44,7 +44,7 @@ pub mod install {
let path = data_dir.join(path);
let parent_path = path.parent().unwrap();
fs::create_dir_all(parent_path).unwrap();
- set_permissions(parent_path);
+ set_permissions(parent_path).unwrap();
if out_of_date || !path.exists() {
fs::write(path, bytes).expect("Failed to install default assets!");
}
@@ -57,8 +57,8 @@ pub mod install {
/// existing config directory, returns the first match
pub fn find_default_config_dir() -> Option<PathBuf> {
vec![
- Some(xdg_config_dir()),
home_config_dir(),
+ Some(xdg_config_dir()),
Some(Path::new(SYSTEM_DEFAULT_CONFIG_DIR).to_path_buf()),
]
.into_iter()
diff --git a/src/common/thread_bus.rs b/src/common/thread_bus.rs
new file mode 100644
index 00000000..8827d068
--- /dev/null
+++ b/src/common/thread_bus.rs
@@ -0,0 +1,142 @@
+//! Definitions and helpers for sending and receiving messages between threads.
+
+use async_std::task_local;
+use std::cell::RefCell;
+use std::sync::mpsc;
+
+use crate::common::pty::PtyInstruction;
+use crate::common::ServerInstruction;
+use crate::errors::{get_current_ctx, ErrorContext};
+use crate::os_input_output::ServerOsApi;
+use crate::screen::ScreenInstruction;
+use crate::wasm_vm::PluginInstruction;
+
+/// An [MPSC](mpsc) asynchronous channel with added error context.
+pub type ChannelWithContext<T> = (
+ mpsc::Sender<(T, ErrorContext)>,
+ mpsc::Receiver<(T, ErrorContext)>,
+);
+/// An [MPSC](mpsc) synchronous channel with added error context.
+pub type SyncChannelWithContext<T> = (
+ mpsc::SyncSender<(T, ErrorContext)>,
+ mpsc::Receiver<(T, ErrorContext)>,
+);
+
+/// Wrappers around the two standard [MPSC](mpsc) sender types, [`mpsc::Sender`] and [`mpsc::SyncSender`], with an additional [`ErrorContext`].
+#[derive(Clone)]
+pub enum SenderType<T: Clone> {
+ /// A wrapper around an [`mpsc::Sender`], adding an [`ErrorContext`].
+ Sender(mpsc::Sender<(T, ErrorContext)>),
+ /// A wrapper around an [`mpsc::SyncSender`], adding an [`ErrorContext`].
+ SyncSender(mpsc::SyncSender<(T, ErrorContext)>),
+}
+
+/// Sends messages on an [MPSC](std::sync::mpsc) channel, along with an [`ErrorContext`],
+/// synchronously or asynchronously depending on the underlying [`SenderType`].
+#[derive(Clone)]
+pub struct SenderWithContext<T: Clone> {
+ sender: SenderType<T>,
+}
+
+impl<T: Clone> SenderWithContext<T> {
+ pub fn new(sender: SenderType<T>) -> Self {
+ Self { sender }
+ }
+
+ /// Sends an event, along with the current [`ErrorContext`], on this
+ /// [`SenderWithContext`]'s channel.
+ pub fn send(&self, event: T) -> Result<(), mpsc::SendError<(T, ErrorContext)>> {
+ let err_ctx = get_current_ctx();
+ match self.sender {
+ SenderType::Sender(ref s) => s.send((event, err_ctx)),
+ SenderType::SyncSender(ref s) => s.send((event, err_ctx)),
+ }
+ }
+}
+
+unsafe impl<T: Clone> Send for SenderWithContext<T> {}
+unsafe impl<T: Clone> Sync for SenderWithContext<T> {}
+
+thread_local!(
+ /// A key to some thread local storage (TLS) that holds a representation of the thread's call
+ /// stack in the form of an [`ErrorContext`].
+ pub static OPENCALLS: RefCell<ErrorContext> = RefCell::default()
+);
+
+task_local! {
+ /// A key to some task local storage that holds a representation of the task's call
+ /// stack in the form of an [`ErrorContext`].
+ pub static ASYNCOPENCALLS: RefCell<ErrorContext> = RefCell::default()
+}
+
+/// A container for senders to the different threads in zellij on the server side
+#[derive(Clone)]
+pub struct ThreadSenders {
+ pub to_screen: Option<SenderWithContext<ScreenInstruction>>,
+ pub to_pty: Option<SenderWithContext<PtyInstruction>>,
+ pub to_plugin: Option<SenderWithContext<PluginInstruction>>,
+ pub to_server: Option<SenderWithContext<ServerInstruction>>,
+}
+
+impl ThreadSenders {
+ pub fn send_to_screen(
+ &self,
+ instruction: ScreenInstruction,
+ ) -> Result<(), mpsc::SendError<(ScreenInstruction, ErrorContext)>> {
+ self.to_screen.as_ref().unwrap().send(instruction)
+ }
+
+ pub fn send_to_pty(
+ &self,
+ instruction: PtyInstruction,
+ ) -> Result<(), mpsc::SendError<(PtyInstruction, ErrorContext)>> {
+ self.to_pty.as_ref().unwrap().send(instruction)
+ }
+
+ pub fn send_to_plugin(
+ &self,
+ instruction: PluginInstruction,
+ ) -> Result<(), mpsc::SendError<(PluginInstruction, ErrorContext)>> {
+ self.to_plugin.as_ref().unwrap().send(instruction)
+ }
+
+ pub fn send_to_server(
+ &self,
+ instruction: ServerInstruction,
+ ) -> Result<(), mpsc::SendError<(ServerInstruction, ErrorContext)>> {
+ self.to_server.as_ref().unwrap().send(instruction)
+ }
+}
+
+/// A container for a receiver, OS input and the senders to a given thread
+pub struct Bus<T> {
+ pub receiver: mpsc::Receiver<(T, ErrorContext)>,
+ pub senders: ThreadSenders,
+ pub os_input: Option<Box<dyn ServerOsApi>>,
+}
+
+impl<T> Bus<T> {
+ pub fn new(
+ receiver: mpsc::Receiver<(T, ErrorContext)>,
+ to_screen: Option<&SenderWithContext<ScreenInstruction>>,
+ to_pty: Option<&SenderWithContext<PtyInstruction>>,
+ to_plugin: Option<&SenderWithContext<PluginInstruction>>,
+ to_server: Option<&SenderWithContext<ServerInstruction>>,
+ os_input: Option<Box<dyn ServerOsApi>>,
+ ) -> Self {
+ Bus {
+ receiver,
+ senders: ThreadSenders {
+ to_screen: to_screen.cloned(),
+ to_pty: to_pty.cloned(),
+ to_plugin: to_plugin.cloned(),
+ to_server: to_server.cloned(),
+ },
+ os_input: os_input.clone(),
+ }
+ }
+
+ pub fn recv(&self) -> Result<(T, ErrorContext), mpsc::RecvError> {
+ self.receiver.recv()
+ }
+}
diff --git a/src/common/utils/consts.rs b/src/common/utils/consts.rs
index 29870d97..c77f1c9e 100644
--- a/src/common/utils/consts.rs
+++ b/src/common/utils/consts.rs
@@ -38,7 +38,7 @@ lazy_static! {
);
ipc_dir.push(VERSION);
fs::create_dir_all(&ipc_dir).unwrap();
- set_permissions(&ipc_dir);
+ set_permissions(&ipc_dir).unwrap();
ipc_dir.push(&*SESSION_NAME);
ipc_dir
};
diff --git a/src/common/utils/logging.rs b/src/common/utils/logging.rs
index ff3fb44c..0dd382d6 100644
--- a/src/common/utils/logging.rs
+++ b/src/common/utils/logging.rs
@@ -10,10 +10,12 @@ use std::{
use crate::os_input_output::set_permissions;
use crate::utils::consts::{ZELLIJ_TMP_LOG_DIR, ZELLIJ_TMP_LOG_FILE};
-pub fn atomic_create_file(file_name: &Path) {
- let _ = fs::OpenOptions::new().create(true).open(file_name);
- #[cfg(not(test))]
- set_permissions(file_name);
+pub fn atomic_create_file(file_name: &Path) -> io::Result<()> {
+ let _ = fs::OpenOptions::new()
+ .append(true)
+ .create(true)
+ .open(file_name)?;
+ set_permissions(file_name)
}
pub fn atomic_create_dir(dir_name: &Path) -> io::Result<()> {
@@ -27,7 +29,7 @@ pub fn atomic_create_dir(dir_name: &Path) -> io::Result<()> {
Ok(())
};
if result.is_ok() {
- set_permissions(dir_name);
+ set_permissions(dir_name)?;
}
result
}
@@ -38,10 +40,9 @@ pub fn debug_log_to_file(mut message: String) -> io::Result<()> {
}
pub fn debug_log_to_file_without_newline(message: String) -> io::Result<()> {
- atomic_create_file(&*ZELLIJ_TMP_LOG_FILE);
+ atomic_create_file(&*ZELLIJ_TMP_LOG_FILE)?;
let mut file = fs::OpenOptions::new()
.append(true)
- .create(true)
.open(&*ZELLIJ_TMP_LOG_FILE)?;
file.write_all(message.as_bytes())
}
@@ -78,6 +79,7 @@ pub fn debug_to_file(message: u8, pid: RawFd) -> io::Result<()> {
let mut file = fs::OpenOptions::new()
.append(true)
.create(true)
- .open(path)?;
+ .open(&path)?;
+ set_permissions(&path)?;
file.write_all(&[message])
}
diff --git a/src/common/wasm_vm.rs b/src/common/wasm_vm.rs
index db52c2bb..e5567285 100644
--- a/src/common/wasm_vm.rs
+++ b/src/common/wasm_vm.rs
@@ -1,17 +1,25 @@
+use std::collections::{HashMap, HashSet};
+use std::fs;
+use std::path::PathBuf;
+use std::process;
+use std::str::FromStr;
+use std::sync::{mpsc::Sender, Arc, Mutex};
+use std::thread;
+use std::time::{Duration, Instant};
+
use serde::{de::DeserializeOwned, Serialize};
-use std::{
- collections::HashSet,
- path::PathBuf,
- process,
- sync::{mpsc::Sender, Arc, Mutex},
- thread,
- time::{Duration, Instant},
+use wasmer::{
+ imports, ChainableNamedResolver, Function, ImportObject, Instance, Module, Store, Value,
+ WasmerEnv,
};
-use wasmer::{imports, Function, ImportObject, Store, WasmerEnv};
-use wasmer_wasi::WasiEnv;
+use wasmer_wasi::{Pipe, WasiEnv, WasiState};
use zellij_tile::data::{Event, EventType, PluginIds};
-use super::{pty_bus::PtyInstruction, screen::ScreenInstruction, PaneId, SenderWithContext};
+use crate::common::errors::{ContextType, PluginContext};
+use crate::common::pty::PtyInstruction;
+use crate::common::screen::ScreenInstruction;
+use crate::common::thread_bus::{Bus, ThreadSenders};
+use crate::common::PaneId;
#[derive(Clone, Debug)]
pub enum PluginInstruction {
@@ -25,14 +33,97 @@ 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_pty_instructions: SenderWithContext<PtyInstruction>,
- pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
+ pub senders: ThreadSenders,
pub wasi_env: WasiEnv,
pub subscriptions: Arc<Mutex<HashSet<EventType>>>,
}
+// Thread main --------------------------------------------------------------------------------------------------------
+pub fn wasm_thread_main(bus: Bus<PluginInstruction>, store: Store, data_dir: PathBuf) {
+ let mut plugin_id = 0;
+ let mut plugin_map = HashMap::new();
+ loop {
+ let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel");
+ err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event)));
+ match event {
+ PluginInstruction::Load(pid_tx, path) => {
+ let plugin_dir = data_dir.join("plugins/");
+ let wasm_bytes = fs::read(&path)
+ .or_else(|_| fs::read(&path.with_extension("wasm")))
+ .or_else(|_| fs::read(&plugin_dir.join(&path).with_extension("wasm")))
+ .unwrap_or_else(|_| panic!("cannot find plugin {}", &path.display()));
+
+ // FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that
+ let module = Module::new(&store, &wasm_bytes).unwrap();
+
+ let output = Pipe::new();
+ let input = Pipe::new();
+ let mut wasi_env = WasiState::new("Zellij")
+ .env("CLICOLOR_FORCE", "1")
+ .preopen(|p| {
+ p.directory(".") // FIXME: Change this to a more meaningful dir
+ .alias(".")
+ .read(true)
+ .write(true)
+ .create(true)
+ })
+ .unwrap()
+ .stdin(Box::new(input))
+ .stdout(Box::new(output))
+ .finalize()
+ .unwrap();
+
+ let wasi = wasi_env.import_object(&module).unwrap();
+
+ let plugin_env = PluginEnv {
+ plugin_id,
+ senders: bus.senders.clone(),
+ wasi_env,
+ subscriptions: Arc::new(Mutex::new(HashSet::new())),
+ };
+
+ 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 `.load()` method
+ start.call(&[]).unwrap();
+
+ plugin_map.insert(plugin_id, (instance, plugin_env));
+ pid_tx.send(plugin_id).unwrap();
+ plugin_id += 1;
+ }
+ PluginInstruction::Update(pid, event) => {
+ for (&i, (instance, plugin_env)) in &plugin_map {
+ let subs = plugin_env.subscriptions.lock().unwrap();
+ // FIXME: This is very janky... Maybe I should write my own macro for Event -> EventType?
+ 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_object(&plugin_env.wasi_env, &event);
+ update.call(&[]).unwrap();
+ }
+ }
+ drop(bus.senders.send_to_screen(ScreenInstruction::Render));
+ }
+ PluginInstruction::Render(buf_tx, pid, rows, cols) => {
+ let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
+
+ let render = instance.exports.get_function("render").unwrap();
+
+ render
+ .call(&[Value::I32(rows as i32), Value::I32(cols as i32)])
+ .unwrap();
+
+ buf_tx.send(wasi_read_string(&plugin_env.wasi_env)).unwrap();
+ }
+ PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)),
+ PluginInstruction::Exit => break,
+ }
+ }
+}
+
// Plugin API ---------------------------------------------------------------------------------------------------------
pub fn zellij_exports(store: &Store, plugin_env: &PluginEnv) -> ImportObject {
@@ -74,8 +165,8 @@ fn host_unsubscribe(plugin_env: &PluginEnv) {
fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) {
let selectable = selectable != 0;
plugin_env
- .send_screen_instructions
- .send(ScreenInstruction::SetSelectable(
+ .senders
+ .send_to_screen(ScreenInstruction::SetSelectable(
PaneId::Plugin(plugin_env.plugin_id),
selectable,
))
@@ -85,8 +176,8 @@ fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) {
fn host_set_max_height(plugin_env: &PluginEnv, max_height: i32) {
let max_height = max_height as usize;
plugin_env
- .send_screen_instructions
- .send(ScreenInstruction::SetMaxHeight(
+ .senders
+ .send_to_screen(ScreenInstruction::SetMaxHeight(
PaneId::Plugin(plugin_env.plugin_id),
max_height,
))
@@ -96,8 +187,8 @@ fn host_set_max_height(plugin_env: &PluginEnv, max_height: i32) {
fn host_set_invisible_borders(plugin_env: &PluginEnv, invisible_borders: i32) {
let invisible_borders = invisible_borders != 0;
plugin_env
- .send_screen_instructions
- .send(ScreenInstruction::SetInvisibleBorders(
+ .senders
+ .send_to_screen(ScreenInstruction::SetInvisibleBorders(
PaneId::Plugin(plugin_env.plugin_id),
invisible_borders,
))
@@ -115,8 +206,8 @@ fn host_get_plugin_ids(plugin_env: &PluginEnv) {
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)))
+ .senders
+ .send_to_pty(PtyInstruction::SpawnTerminal(Some(path)))
.unwrap();
}
@@ -130,7 +221,7 @@ fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) {
// 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 send_plugin_instructions = plugin_env.senders.to_plugin.clone();
let update_target = Some(plugin_env.plugin_id);
thread::spawn(move || {
let start_time = Instant::now();
@@ -140,6 +231,7 @@ fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) {
let elapsed_time = Instant::now().duration_since(start_time).as_secs_f64();
send_plugin_instructions
+ .unwrap()
.send(PluginInstruction::Update(
update_target,
Event::Timer(elapsed_time),