diff options
| author | Kunal Mohan <kunalmohan99@gmail.com> | 2021-05-16 16:42:50 +0530 |
|---|---|---|
| committer | Kunal Mohan <kunalmohan99@gmail.com> | 2021-05-16 21:41:56 +0530 |
| commit | 2038947a14d32d89da2bdbcaa1381c3015ac9c7a (patch) | |
| tree | 5a332470e32b380816098594a89d3c3db1f6eb53 /src/common | |
| parent | handle error on termios initialization (#511) (diff) | |
| download | sl0ppij-2038947a14d32d89da2bdbcaa1381c3015ac9c7a.tar.gz sl0ppij-2038947a14d32d89da2bdbcaa1381c3015ac9c7a.tar.xz sl0ppij-2038947a14d32d89da2bdbcaa1381c3015ac9c7a.zip | |
Big refactor: separate crates for client, server and utilities
Diffstat (limited to 'src/common')
| -rw-r--r-- | src/common/command_is_executing.rs | 33 | ||||
| -rw-r--r-- | src/common/errors.rs | 393 | ||||
| -rw-r--r-- | src/common/input/actions.rs | 64 | ||||
| -rw-r--r-- | src/common/input/config.rs | 218 | ||||
| -rw-r--r-- | src/common/input/handler.rs | 268 | ||||
| -rw-r--r-- | src/common/input/keybinds.rs | 301 | ||||
| -rw-r--r-- | src/common/input/mod.rs | 7 | ||||
| -rw-r--r-- | src/common/input/options.rs | 45 | ||||
| -rw-r--r-- | src/common/input/unit/keybinds_test.rs | 801 | ||||
| -rw-r--r-- | src/common/ipc.rs | 131 | ||||
| -rw-r--r-- | src/common/mod.rs | 14 | ||||
| -rw-r--r-- | src/common/os_input_output.rs | 414 | ||||
| -rw-r--r-- | src/common/pty.rs | 293 | ||||
| -rw-r--r-- | src/common/screen.rs | 595 | ||||
| -rw-r--r-- | src/common/setup.rs | 240 | ||||
| -rw-r--r-- | src/common/thread_bus.rs | 142 | ||||
| -rw-r--r-- | src/common/utils/consts.rs | 54 | ||||
| -rw-r--r-- | src/common/utils/logging.rs | 85 | ||||
| -rw-r--r-- | src/common/utils/mod.rs | 5 | ||||
| -rw-r--r-- | src/common/utils/shared.rs | 108 | ||||
| -rw-r--r-- | src/common/wasm_vm.rs | 267 |
21 files changed, 0 insertions, 4478 deletions
diff --git a/src/common/command_is_executing.rs b/src/common/command_is_executing.rs deleted file mode 100644 index ad032557..00000000 --- a/src/common/command_is_executing.rs +++ /dev/null @@ -1,33 +0,0 @@ -#![allow(clippy::mutex_atomic)] -use std::sync::{Arc, Condvar, Mutex}; - -#[derive(Clone)] -pub struct CommandIsExecuting { - input_thread: Arc<(Mutex<bool>, Condvar)>, -} - -impl CommandIsExecuting { - pub fn new() -> Self { - CommandIsExecuting { - input_thread: Arc::new((Mutex::new(false), Condvar::new())), - } - } - pub fn blocking_input_thread(&mut self) { - let (lock, _cvar) = &*self.input_thread; - let mut input_thread = lock.lock().unwrap(); - *input_thread = true; - } - pub fn unblock_input_thread(&mut self) { - let (lock, cvar) = &*self.input_thread; - let mut input_thread = lock.lock().unwrap(); - *input_thread = false; - cvar.notify_all(); - } - pub fn wait_until_input_thread_is_unblocked(&self) { - let (lock, cvar) = &*self.input_thread; - let mut input_thread = lock.lock().unwrap(); - while *input_thread { - input_thread = cvar.wait(input_thread).unwrap(); - } - } -} diff --git a/src/common/errors.rs b/src/common/errors.rs deleted file mode 100644 index 262dfd6b..00000000 --- a/src/common/errors.rs +++ /dev/null @@ -1,393 +0,0 @@ -//! Error context system based on a thread-local representation of the call stack, itself based on -//! the instructions that are sent between threads. - -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::thread_bus::SenderWithContext; -#[cfg(not(test))] -use std::panic::PanicInfo; - -pub trait ErrorInstruction { - fn error(err: String) -> Self; -} - -impl ErrorInstruction for ClientInstruction { - fn error(err: String) -> Self { - ClientInstruction::Error(err) - } -} - -impl ErrorInstruction for ServerInstruction { - fn error(err: String) -> Self { - ServerInstruction::Error(err) - } -} - -/// Custom panic handler/hook. Prints the [`ErrorContext`]. -#[cfg(not(test))] -pub fn handle_panic<T>(info: &PanicInfo<'_>, sender: &SenderWithContext<T>) -where - T: ErrorInstruction + Clone, -{ - use backtrace::Backtrace; - use std::{process, thread}; - let backtrace = Backtrace::new(); - let thread = thread::current(); - let thread = thread.name().unwrap_or("unnamed"); - - let msg = match info.payload().downcast_ref::<&'static str>() { - Some(s) => Some(*s), - None => info.payload().downcast_ref::<String>().map(|s| &**s), - }; - - let err_ctx = OPENCALLS.with(|ctx| *ctx.borrow()); - - let backtrace = match (info.location(), msg) { - (Some(location), Some(msg)) => format!( - "{}\n\u{1b}[0;0mError: \u{1b}[0;31mthread '{}' panicked at '{}': {}:{}\n\u{1b}[0;0m{:?}", - err_ctx, - thread, - msg, - location.file(), - location.line(), - backtrace, - ), - (Some(location), None) => format!( - "{}\n\u{1b}[0;0mError: \u{1b}[0;31mthread '{}' panicked: {}:{}\n\u{1b}[0;0m{:?}", - err_ctx, - thread, - location.file(), - location.line(), - backtrace - ), - (None, Some(msg)) => format!( - "{}\n\u{1b}[0;0mError: \u{1b}[0;31mthread '{}' panicked at '{}'\n\u{1b}[0;0m{:?}", - err_ctx, thread, msg, backtrace - ), - (None, None) => format!( - "{}\n\u{1b}[0;0mError: \u{1b}[0;31mthread '{}' panicked\n\u{1b}[0;0m{:?}", - err_ctx, thread, backtrace - ), - }; - - if thread == "main" { - println!("{}", backtrace); - process::exit(1); - } else { - let _ = sender.send(T::error(backtrace)); - } -} - -pub fn get_current_ctx() -> ErrorContext { - ASYNCOPENCALLS - .try_with(|ctx| *ctx.borrow()) - .unwrap_or_else(|_| OPENCALLS.with(|ctx| *ctx.borrow())) -} - -/// A representation of the call stack. -#[derive(Clone, Copy, Serialize, Deserialize)] -pub struct ErrorContext { - calls: [ContextType; MAX_THREAD_CALL_STACK], -} - -impl ErrorContext { - /// Returns a new, blank [`ErrorContext`] containing only [`Empty`](ContextType::Empty) - /// calls. - pub fn new() -> Self { - Self { - calls: [ContextType::Empty; MAX_THREAD_CALL_STACK], - } - } - - /// Adds a call to this [`ErrorContext`]'s call stack representation. - pub fn add_call(&mut self, call: ContextType) { - for ctx in self.calls.iter_mut() { - if *ctx == ContextType::Empty { - *ctx = call; - break; - } - } - self.update_thread_ctx() - } - - /// Updates the thread local [`ErrorContext`]. - pub fn update_thread_ctx(&self) { - ASYNCOPENCALLS - .try_with(|ctx| *ctx.borrow_mut() = *self) - .unwrap_or_else(|_| OPENCALLS.with(|ctx| *ctx.borrow_mut() = *self)); - } -} - -impl Default for ErrorContext { - fn default() -> Self { - Self::new() - } -} - -impl Display for ErrorContext { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - writeln!(f, "Originating Thread(s):")?; - for (index, ctx) in self.calls.iter().enumerate() { - if *ctx == ContextType::Empty { - break; - } - writeln!(f, "\u{1b}[0;0m{}. {}", index + 1, ctx)?; - } - Ok(()) - } -} - -/// Different types of calls that form an [`ErrorContext`] call stack. -/// -/// Complex variants store a variant of a related enum, whose variants can be built from -/// the corresponding Zellij MSPC instruction enum variants ([`ScreenInstruction`], -/// [`PtyInstruction`], [`ClientInstruction`], etc). -#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)] -pub enum ContextType { - /// A screen-related call. - Screen(ScreenContext), - /// A PTY-related call. - Pty(PtyContext), - /// A plugin-related call. - Plugin(PluginContext), - /// An app-related call. - Client(ClientContext), - /// A server-related call. - IPCServer(ServerContext), - StdinHandler, - AsyncTask, - /// An empty, placeholder call. This should be thought of as representing no call at all. - /// A call stack representation filled with these is the representation of an empty call stack. - Empty, -} - -// TODO use the `colored` crate for color formatting -impl Display for ContextType { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - let purple = "\u{1b}[1;35m"; - let green = "\u{1b}[0;32m"; - match *self { - ContextType::Screen(c) => write!(f, "{}screen_thread: {}{:?}", purple, green, c), - ContextType::Pty(c) => write!(f, "{}pty_thread: {}{:?}", purple, green, c), - ContextType::Plugin(c) => write!(f, "{}plugin_thread: {}{:?}", purple, green, c), - ContextType::Client(c) => write!(f, "{}main_thread: {}{:?}", purple, green, c), - ContextType::IPCServer(c) => write!(f, "{}ipc_server: {}{:?}", purple, green, c), - ContextType::StdinHandler => { - write!(f, "{}stdin_handler_thread: {}AcceptInput", purple, green) - } - ContextType::AsyncTask => { - write!(f, "{}stream_terminal_bytes: {}AsyncTask", purple, green) - } - ContextType::Empty => write!(f, ""), - } - } -} - -// FIXME: Just deriving EnumDiscriminants from strum will remove the need for any of this!!! -/// Stack call representations corresponding to the different types of [`ScreenInstruction`]s. -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -pub enum ScreenContext { - HandlePtyBytes, - Render, - NewPane, - HorizontalSplit, - VerticalSplit, - WriteCharacter, - ResizeLeft, - ResizeRight, - ResizeDown, - ResizeUp, - SwitchFocus, - FocusNextPane, - FocusPreviousPane, - MoveFocusLeft, - MoveFocusLeftOrPreviousTab, - MoveFocusDown, - MoveFocusUp, - MoveFocusRight, - MoveFocusRightOrNextTab, - Exit, - ScrollUp, - ScrollDown, - PageScrollUp, - PageScrollDown, - ClearScroll, - CloseFocusedPane, - ToggleActiveSyncTab, - ToggleActiveTerminalFullscreen, - SetSelectable, - SetInvisibleBorders, - SetMaxHeight, - ClosePane, - ApplyLayout, - NewTab, - SwitchTabNext, - SwitchTabPrev, - CloseTab, - GoToTab, - UpdateTabName, - TerminalResize, - ChangeMode, -} - -// FIXME: Just deriving EnumDiscriminants from strum will remove the need for any of this!!! -impl From<&ScreenInstruction> for ScreenContext { - fn from(screen_instruction: &ScreenInstruction) -> Self { - match *screen_instruction { - ScreenInstruction::PtyBytes(..) => ScreenContext::HandlePtyBytes, - ScreenInstruction::Render => ScreenContext::Render, - ScreenInstruction::NewPane(_) => ScreenContext::NewPane, - ScreenInstruction::HorizontalSplit(_) => ScreenContext::HorizontalSplit, - ScreenInstruction::VerticalSplit(_) => ScreenContext::VerticalSplit, - ScreenInstruction::WriteCharacter(_) => ScreenContext::WriteCharacter, - ScreenInstruction::ResizeLeft => ScreenContext::ResizeLeft, - ScreenInstruction::ResizeRight => ScreenContext::ResizeRight, - ScreenInstruction::ResizeDown => ScreenContext::ResizeDown, - ScreenInstruction::ResizeUp => ScreenContext::ResizeUp, - ScreenInstruction::SwitchFocus => ScreenContext::SwitchFocus, - 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, - ScreenInstruction::PageScrollUp => ScreenContext::PageScrollUp, - ScreenInstruction::PageScrollDown => ScreenContext::PageScrollDown, - ScreenInstruction::ClearScroll => ScreenContext::ClearScroll, - ScreenInstruction::CloseFocusedPane => ScreenContext::CloseFocusedPane, - ScreenInstruction::ToggleActiveTerminalFullscreen => { - ScreenContext::ToggleActiveTerminalFullscreen - } - ScreenInstruction::SetSelectable(..) => ScreenContext::SetSelectable, - ScreenInstruction::SetInvisibleBorders(..) => ScreenContext::SetInvisibleBorders, - ScreenInstruction::SetMaxHeight(..) => ScreenContext::SetMaxHeight, - ScreenInstruction::ClosePane(_) => ScreenContext::ClosePane, - ScreenInstruction::ApplyLayout(..) => ScreenContext::ApplyLayout, - ScreenInstruction::NewTab(_) => ScreenContext::NewTab, - ScreenInstruction::SwitchTabNext => ScreenContext::SwitchTabNext, - ScreenInstruction::SwitchTabPrev => ScreenContext::SwitchTabPrev, - ScreenInstruction::CloseTab => ScreenContext::CloseTab, - ScreenInstruction::GoToTab(_) => ScreenContext::GoToTab, - ScreenInstruction::UpdateTabName(_) => ScreenContext::UpdateTabName, - ScreenInstruction::TerminalResize(_) => ScreenContext::TerminalResize, - ScreenInstruction::ChangeMode(_) => ScreenContext::ChangeMode, - ScreenInstruction::ToggleActiveSyncTab => ScreenContext::ToggleActiveSyncTab, - } - } -} - -/// Stack call representations corresponding to the different types of [`PtyInstruction`]s. -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -pub enum PtyContext { - SpawnTerminal, - SpawnTerminalVertically, - SpawnTerminalHorizontally, - NewTab, - ClosePane, - CloseTab, - Exit, -} - -impl From<&PtyInstruction> for PtyContext { - fn from(pty_instruction: &PtyInstruction) -> Self { - match *pty_instruction { - PtyInstruction::SpawnTerminal(_) => PtyContext::SpawnTerminal, - PtyInstruction::SpawnTerminalVertically(_) => PtyContext::SpawnTerminalVertically, - PtyInstruction::SpawnTerminalHorizontally(_) => PtyContext::SpawnTerminalHorizontally, - PtyInstruction::ClosePane(_) => PtyContext::ClosePane, - PtyInstruction::CloseTab(_) => PtyContext::CloseTab, - PtyInstruction::NewTab => PtyContext::NewTab, - PtyInstruction::Exit => PtyContext::Exit, - } - } -} - -// FIXME: This whole pattern *needs* a macro eventually, it's soul-crushing to write - -use crate::wasm_vm::PluginInstruction; - -/// Stack call representations corresponding to the different types of [`PluginInstruction`]s. -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -pub enum PluginContext { - Load, - Update, - Render, - Unload, - Exit, -} - -impl From<&PluginInstruction> for PluginContext { - fn from(plugin_instruction: &PluginInstruction) -> Self { - match *plugin_instruction { - PluginInstruction::Load(..) => PluginContext::Load, - PluginInstruction::Update(..) => PluginContext::Update, - PluginInstruction::Render(..) => PluginContext::Render, - PluginInstruction::Unload(_) => PluginContext::Unload, - PluginInstruction::Exit => PluginContext::Exit, - } - } -} - -/// Stack call representations corresponding to the different types of [`ClientInstruction`]s. -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -pub enum ClientContext { - Exit, - Error, - UnblockInputThread, - Render, - ServerError, -} - -impl From<&ClientInstruction> for ClientContext { - fn from(client_instruction: &ClientInstruction) -> Self { - match *client_instruction { - ClientInstruction::Exit => ClientContext::Exit, - ClientInstruction::Error(_) => ClientContext::Error, - ClientInstruction::ServerError(_) => ClientContext::ServerError, - ClientInstruction::Render(_) => ClientContext::Render, - ClientInstruction::UnblockInputThread => ClientContext::UnblockInputThread, - } - } -} - -/// Stack call representations corresponding to the different types of [`ServerInstruction`]s. -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -pub enum ServerContext { - NewClient, - Render, - UnblockInputThread, - ClientExit, - Error, -} - -impl From<&ServerInstruction> for ServerContext { - fn from(server_instruction: &ServerInstruction) -> Self { - match *server_instruction { - ServerInstruction::NewClient(..) => ServerContext::NewClient, - ServerInstruction::Render(_) => ServerContext::Render, - ServerInstruction::UnblockInputThread => ServerContext::UnblockInputThread, - ServerInstruction::ClientExit => ServerContext::ClientExit, - ServerInstruction::Error(_) => ServerContext::Error, - } - } -} diff --git a/src/common/input/actions.rs b/src/common/input/actions.rs deleted file mode 100644 index b806aa3f..00000000 --- a/src/common/input/actions.rs +++ /dev/null @@ -1,64 +0,0 @@ -//! Definition of the actions that can be bound to keys. - -use serde::{Deserialize, Serialize}; -use zellij_tile::data::InputMode; - -/// The four directions (left, right, up, down). -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub enum Direction { - Left, - Right, - Up, - Down, -} - -/// Actions that can be bound to keys. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub enum Action { - /// Quit Zellij. - Quit, - /// Write to the terminal. - Write(Vec<u8>), - /// Switch to the specified input mode. - SwitchToMode(InputMode), - /// Resize focus pane in specified direction. - Resize(Direction), - /// Switch focus to next pane in specified direction. - FocusNextPane, - FocusPreviousPane, - /// 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. - ScrollDown, - /// Scroll up one page in focus pane. - PageScrollUp, - /// Scroll down one page in focus pane. - PageScrollDown, - /// Toggle between fullscreen focus pane and normal layout. - ToggleFocusFullscreen, - /// 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>), - /// Close the focus pane. - CloseFocus, - /// Create a new tab. - NewTab, - /// Do nothing. - NoOp, - /// Go to the next tab. - GoToNextTab, - /// Go to the previous tab. - GoToPreviousTab, - /// Close the current tab. - CloseTab, - GoToTab(u32), - TabNameInput(Vec<u8>), -} diff --git a/src/common/input/config.rs b/src/common/input/config.rs deleted file mode 100644 index 3dffe71c..00000000 --- a/src/common/input/config.rs +++ /dev/null @@ -1,218 +0,0 @@ -//! Deserializes configuration options. -use std::error; -use std::fmt::{self, Display}; -use std::fs::File; -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, Serialize}; -use std::convert::TryFrom; - -const DEFAULT_CONFIG_FILE_NAME: &str = "config.yaml"; - -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, Deserialize, Serialize)] -pub struct Config { - pub keybinds: Keybinds, - pub options: Options, -} - -#[derive(Debug)] -pub enum ConfigError { - // Deserialization error - Serde(serde_yaml::Error), - // Io error - Io(io::Error), - // Io error with path context - IoPath(io::Error, PathBuf), - // Internal Deserialization Error - FromUtf8(std::string::FromUtf8Error), -} - -impl Default for Config { - fn default() -> Self { - let keybinds = Keybinds::default(); - let options = Options::default(); - Config { keybinds, options } - } -} - -impl TryFrom<&CliArgs> for Config { - type Error = ConfigError; - - fn try_from(opts: &CliArgs) -> ConfigResult { - if let Some(ref path) = opts.config { - return Config::new(&path); - } - - if let Some(ConfigCli::Setup(setup)) = opts.option.clone() { - if setup.clean { - return Config::from_default_assets(); - } - } - - let config_dir = opts - .config_dir - .clone() - .or_else(setup::find_default_config_dir); - - if let Some(ref config) = config_dir { - let path = config.join(DEFAULT_CONFIG_FILE_NAME); - if path.exists() { - Config::new(&path) - } else { - Config::from_default_assets() - } - } else { - Config::from_default_assets() - } - } -} - -impl Config { - /// Uses defaults, but lets config override them. - 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); - let options = Options::from_yaml(config_from_yaml.options); - Ok(Config { keybinds, options }) - } - - /// Deserializes from given path. - pub fn new(path: &Path) -> ConfigResult { - match File::open(path) { - Ok(mut file) => { - let mut yaml_config = String::new(); - file.read_to_string(&mut yaml_config) - .map_err(|e| ConfigError::IoPath(e, path.to_path_buf()))?; - Ok(Config::from_yaml(&yaml_config)?) - } - Err(e) => Err(ConfigError::IoPath(e, path.into())), - } - } - - /// 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(setup::DEFAULT_CONFIG.to_vec())?.as_str()) - } -} - -impl Display for ConfigError { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - match self { - ConfigError::Io(ref err) => write!(formatter, "IoError: {}", err), - ConfigError::IoPath(ref err, ref path) => { - write!(formatter, "IoError: {}, File: {}", err, path.display(),) - } - ConfigError::Serde(ref err) => write!(formatter, "Deserialization error: {}", err), - ConfigError::FromUtf8(ref err) => write!(formatter, "FromUtf8Error: {}", err), - } - } -} - -impl std::error::Error for ConfigError { - fn cause(&self) -> Option<&dyn error::Error> { - match *self { - ConfigError::Io(ref err) => Some(err), - ConfigError::IoPath(ref err, _) => Some(err), - ConfigError::Serde(ref err) => Some(err), - ConfigError::FromUtf8(ref err) => Some(err), - } - } -} - -impl From<io::Error> for ConfigError { - fn from(err: io::Error) -> ConfigError { - ConfigError::Io(err) - } -} - -impl From<serde_yaml::Error> for ConfigError { - fn from(err: serde_yaml::Error) -> ConfigError { - ConfigError::Serde(err) - } -} - -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 { - use std::io::Write; - - use tempfile::tempdir; - - use super::*; - - #[test] - fn try_from_cli_args_with_config() { - let arbitrary_config = PathBuf::from("nonexistent.yaml"); - let mut opts = CliArgs::default(); - opts.config = Some(arbitrary_config); - println!("OPTS= {:?}", opts); - let result = Config::try_from(&opts); - assert!(result.is_err()); - } - - #[test] - fn try_from_cli_args_with_option_clean() { - use crate::common::setup::Setup; - let mut opts = CliArgs::default(); - opts.option = Some(ConfigCli::Setup(Setup { - clean: true, - ..Setup::default() - })); - let result = Config::try_from(&opts); - assert!(result.is_ok()); - } - - #[test] - fn try_from_cli_args_with_config_dir() { - let mut opts = CliArgs::default(); - let tmp = tempdir().unwrap(); - File::create(tmp.path().join(DEFAULT_CONFIG_FILE_NAME)) - .unwrap() - .write_all(b"keybinds: invalid\n") - .unwrap(); - opts.config_dir = Some(tmp.path().to_path_buf()); - let result = Config::try_from(&opts); - assert!(result.is_err()); - } - - #[test] - fn try_from_cli_args_with_config_dir_without_config() { - let mut opts = CliArgs::default(); - let tmp = tempdir().unwrap(); - opts.config_dir = Some(tmp.path().to_path_buf()); - let result = Config::try_from(&opts); - assert_eq!(result.unwrap(), Config::default()); - } - - #[test] - fn try_from_cli_args_default() { - let opts = CliArgs::default(); - let result = Config::try_from(&opts); - assert_eq!(result.unwrap(), Config::default()); - } -} diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs deleted file mode 100644 index 4ac2cf83..00000000 --- a/src/common/input/handler.rs +++ /dev/null @@ -1,268 +0,0 @@ -//! Main input logic. - -use super::actions::Action; -use super::keybinds::Keybinds; -use crate::client::ClientInstruction; -use crate::common::input::config::Config; -use crate::common::ipc::ClientToServerMsg; -use crate::common::thread_bus::{SenderWithContext, OPENCALLS}; -use crate::errors::ContextType; -use crate::os_input_output::ClientOsApi; -use crate::CommandIsExecuting; - -use termion::input::{TermRead, TermReadEventsAndRaw}; -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`]. -struct InputHandler { - /// The current input mode - mode: InputMode, - os_input: Box<dyn ClientOsApi>, - config: Config, - command_is_executing: CommandIsExecuting, - send_client_instructions: SenderWithContext<ClientInstruction>, - should_exit: bool, - pasting: bool, -} - -impl InputHandler { - /// Returns a new [`InputHandler`] with the attributes specified as arguments. - fn new( - os_input: Box<dyn ClientOsApi>, - command_is_executing: CommandIsExecuting, - config: Config, - send_client_instructions: SenderWithContext<ClientInstruction>, - ) -> Self { - InputHandler { - mode: InputMode::Normal, - os_input, - config, - command_is_executing, - send_client_instructions, - should_exit: false, - pasting: false, - } - } - - /// Main input event loop. Interprets the terminal [`Event`](termion::event::Event)s - /// as [`Action`]s according to the current [`InputMode`], and dispatches those actions. - fn handle_input(&mut self) { - 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; - } - let stdin_buffer = self.os_input.read_from_stdin(); - for key_result in stdin_buffer.events_and_raw() { - match key_result { - Ok((event, raw_bytes)) => match event { - termion::event::Event::Key(key) => { - let key = cast_termion_key(key); - 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); - } else if unsupported_key == bracketed_paste_start { - self.pasting = true; - } else if unsupported_key == bracketed_paste_end { - self.pasting = false; - } else { - // this is a hack because termion doesn't recognize certain keys - // in this case we just forward it to the terminal - self.handle_unknown_key(raw_bytes); - } - } - termion::event::Event::Mouse(_) => { - // Mouse events aren't implemented yet, - // use a NoOp untill then. - } - }, - Err(err) => panic!("Encountered read error: {:?}", err), - } - } - } - } - fn handle_unknown_key(&mut self, raw_bytes: Vec<u8>) { - if self.mode == InputMode::Normal || self.mode == InputMode::Locked { - let action = Action::Write(raw_bytes); - self.dispatch_action(action); - } - } - fn handle_key(&mut self, key: &Key, raw_bytes: Vec<u8>) { - let keybinds = &self.config.keybinds; - 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; - } - } - } - } - - /// Dispatches an [`Action`]. - /// - /// This function's body dictates what each [`Action`] actually does when - /// dispatched. - /// - /// # Return value - /// Currently, this function returns a boolean that indicates whether - /// [`Self::handle_input()`] should break after this action is dispatched. - /// This is a temporary measure that is only necessary due to the way that the - /// framework works, and shouldn't be necessary anymore once the test framework - /// is revised. See [issue#183](https://github.com/zellij-org/zellij/issues/183). - fn dispatch_action(&mut self, action: Action) -> bool { - let mut should_break = false; - - match action { - Action::Quit => { - self.exit(); - should_break = true; - } - Action::SwitchToMode(mode) => { - self.mode = mode; - self.os_input - .send_to_server(ClientToServerMsg::Action(action)); - } - Action::CloseFocus - | Action::NewPane(_) - | Action::NewTab - | Action::GoToNextTab - | Action::GoToPreviousTab - | Action::CloseTab - | Action::GoToTab(_) - | Action::MoveFocusOrTab(_) => { - self.command_is_executing.blocking_input_thread(); - self.os_input - .send_to_server(ClientToServerMsg::Action(action)); - self.command_is_executing - .wait_until_input_thread_is_unblocked(); - } - _ => self - .os_input - .send_to_server(ClientToServerMsg::Action(action)), - } - - should_break - } - - /// Routine to be called when the input handler exits (at the moment this is the - /// same as quitting Zellij). - fn exit(&mut self) { - self.send_client_instructions - .send(ClientInstruction::Exit) - .unwrap(); - } -} - -/// 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, - capabilities: PluginCapabilities, -) -> ModeInfo { - let mut keybinds: Vec<(String, String)> = vec![]; - match mode { - InputMode::Normal | InputMode::Locked => {} - InputMode::Resize => { - keybinds.push(("←↓↑→".to_string(), "Resize".to_string())); - } - InputMode::Pane => { - keybinds.push(("←↓↑→".to_string(), "Move focus".to_string())); - keybinds.push(("p".to_string(), "Next".to_string())); - keybinds.push(("n".to_string(), "New".to_string())); - 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(("f".to_string(), "Fullscreen".to_string())); - } - InputMode::Tab => { - keybinds.push(("←↓↑→".to_string(), "Move focus".to_string())); - 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())); - keybinds.push(("PgUp/PgDn".to_string(), "Scroll Page".to_string())); - } - InputMode::RenameTab => { - keybinds.push(("Enter".to_string(), "when done".to_string())); - } - } - ModeInfo { - mode, - keybinds, - palette, - capabilities, - } -} - -/// Entry point to the module. Instantiates an [`InputHandler`] and starts -/// its [`InputHandler::handle_input()`] loop. -pub fn input_loop( - os_input: Box<dyn ClientOsApi>, - config: Config, - command_is_executing: CommandIsExecuting, - send_client_instructions: SenderWithContext<ClientInstruction>, -) { - let _handler = InputHandler::new( - os_input, - command_is_executing, - config, - send_client_instructions, - ) - .handle_input(); -} - -pub fn parse_keys(input_bytes: &[u8]) -> Vec<Key> { - input_bytes.keys().flatten().map(cast_termion_key).collect() -} - -// FIXME: This is an absolutely cursed function that should be destroyed as soon -// as an alternative that doesn't touch zellij-tile can be developed... -fn cast_termion_key(event: termion::event::Key) -> Key { - match event { - termion::event::Key::Backspace => Key::Backspace, - termion::event::Key::Left => Key::Left, - termion::event::Key::Right => Key::Right, - termion::event::Key::Up => Key::Up, - termion::event::Key::Down => Key::Down, - termion::event::Key::Home => Key::Home, - termion::event::Key::End => Key::End, - termion::event::Key::PageUp => Key::PageUp, - termion::event::Key::PageDown => Key::PageDown, - termion::event::Key::BackTab => Key::BackTab, - termion::event::Key::Delete => Key::Delete, - termion::event::Key::Insert => Key::Insert, - termion::event::Key::F(n) => Key::F(n), - termion::event::Key::Char(c) => Key::Char(c), - termion::event::Key::Alt(c) => Key::Alt(c), - termion::event::Key::Ctrl(c) => Key::Ctrl(c), - termion::event::Key::Null => Key::Null, - termion::event::Key::Esc => Key::Esc, - _ => { - unimplemented!("Encountered an unknown key!") - } - } -} diff --git a/src/common/input/keybinds.rs b/src/common/input/keybinds.rs deleted file mode 100644 index 8bef47fd..00000000 --- a/src/common/input/keybinds.rs +++ /dev/null @@ -1,301 +0,0 @@ -//! Mapping of inputs to sequences of actions. -use std::collections::HashMap; - -use super::actions::Action; -use super::config; - -use serde::{Deserialize, Serialize}; -use strum::IntoEnumIterator; -use zellij_tile::data::*; - -/// Used in the config struct -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct Keybinds(HashMap<InputMode, ModeKeybinds>); -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct ModeKeybinds(HashMap<Key, Vec<Action>>); - -/// Intermediate struct used for deserialisation -/// Used in the config file. -#[derive(Clone, Debug, PartialEq, Deserialize)] -pub struct KeybindsFromYaml { - #[serde(flatten)] - keybinds: HashMap<InputMode, Vec<KeyActionUnbind>>, - #[serde(default)] - unbind: Unbind, -} - -/// Intermediate enum used for deserialisation -#[derive(Clone, Debug, PartialEq, Deserialize)] -#[serde(untagged)] -enum KeyActionUnbind { - KeyAction(KeyActionFromYaml), - Unbind(UnbindFromYaml), -} - -/// Intermediate struct used for deserialisation -#[derive(Clone, Debug, PartialEq, Deserialize)] -struct KeyActionUnbindFromYaml { - keybinds: Vec<KeyActionFromYaml>, - unbind: Unbind, -} - -/// Intermediate struct used for deserialisation -#[derive(Clone, Debug, PartialEq, Deserialize)] -pub struct KeyActionFromYaml { - action: Vec<Action>, - key: Vec<Key>, -} - -/// Intermediate struct used for deserialisation -#[derive(Clone, Debug, PartialEq, Deserialize)] -struct UnbindFromYaml { - unbind: Unbind, -} - -/// List of keys, for which to disable their respective default actions -/// `All` is a catch all, and will disable the default actions for all keys. -#[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), -} - -impl Default for Keybinds { - // Use once per codepath - // TODO investigate why - fn default() -> Keybinds { - Self::from_default_assets() - } -} - -impl Keybinds { - pub fn new() -> 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) | Unbind::Keys(_) => Keybinds::unbind(keybinds), - }, - None => Keybinds::default(), - }; - - if let Some(keybinds) = from_yaml { - default_keybinds.merge_keybinds(Keybinds::from(keybinds)) - } else { - default_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 { - let mut keybinds = Keybinds::new(); - - for mode in InputMode::iter() { - let mut mode_keybinds = ModeKeybinds::new(); - if let Some(keybind) = self.0.get(&mode) { - mode_keybinds.0.extend(keybind.0.clone()); - }; - if let Some(keybind) = other.0.get(&mode) { - mode_keybinds.0.extend(keybind.0.clone()); - } - if !mode_keybinds.0.is_empty() { - keybinds.0.insert(mode, mode_keybinds); - } - } - keybinds - } - - /// Converts a [`Key`] terminal event to a sequence of [`Action`]s according to the current - /// [`InputMode`] and [`Keybinds`]. - pub fn key_to_actions( - key: &Key, - input: Vec<u8>, - mode: &InputMode, - keybinds: &Keybinds, - ) -> Vec<Action> { - let mode_keybind_or_action = |action: Action| { - keybinds - .0 - .get(mode) - .unwrap_or_else(|| unreachable!("Unrecognized mode: {:?}", mode)) - .0 - .get(key) - .cloned() - .unwrap_or_else(|| vec![action]) - }; - match *mode { - InputMode::Normal | InputMode::Locked => mode_keybind_or_action(Action::Write(input)), - InputMode::RenameTab => mode_keybind_or_action(Action::TabNameInput(input)), - _ => mode_keybind_or_action(Action::NoOp), - } - } -} - -impl ModeKeybinds { - fn new() -> ModeKeybinds { - ModeKeybinds(HashMap::<Key, Vec<Action>>::new()) - } - - /// Merges `self` with `other`, if keys are the same, `other` overwrites. - fn merge(self, other: ModeKeybinds) -> ModeKeybinds { - let mut merged = self; - 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 { - fn from(keybinds_from_yaml: KeybindsFromYaml) -> Keybinds { - let mut keybinds = Keybinds::new(); - - for mode in InputMode::iter() { - let mut mode_keybinds = ModeKeybinds::new(); - for key_action in keybinds_from_yaml.keybinds.get(&mode).iter() { - for keybind in key_action.iter() { - mode_keybinds = mode_keybinds.merge(ModeKeybinds::from(keybind.clone())); - } - } - keybinds.0.insert(mode, mode_keybinds); - } - keybinds - } -} - -/// 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 actions = key_action.action; - - ModeKeybinds( - key_action - .key - .into_iter() - .map(|k| (k, actions.clone())) - .collect::<HashMap<Key, Vec<Action>>>(), - ) - } -} - -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 - } -} - -impl Default for Unbind { - fn default() -> Unbind { - Unbind::All(false) - } -} - -// The unit test location. -#[cfg(test)] -#[path = "./unit/keybinds_test.rs"] -mod keybinds_test; diff --git a/src/common/input/mod.rs b/src/common/input/mod.rs deleted file mode 100644 index f30ccf23..00000000 --- a/src/common/input/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! The way terminal input is handled. - -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 deleted file mode 100644 index 33625b65..00000000 --- a/src/common/input/options.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! 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 deleted file mode 100644 index 4767fc15..00000000 --- a/src/common/input/unit/keybinds_test.rs +++ /dev/null @@ -1,801 +0,0 @@ -use super::super::actions::*; -use super::super::keybinds::*; -use zellij_tile::data::Key; - -#[test] -fn merge_keybinds_merges_different_keys() { - let mut mode_keybinds_self = ModeKeybinds::new(); - mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]); - let mut mode_keybinds_other = ModeKeybinds::new(); - mode_keybinds_other - .0 - .insert(Key::Backspace, vec![Action::NoOp]); - - let mut mode_keybinds_expected = ModeKeybinds::new(); - mode_keybinds_expected - .0 - .insert(Key::F(1), vec![Action::NoOp]); - mode_keybinds_expected - .0 - .insert(Key::Backspace, vec![Action::NoOp]); - - let mode_keybinds_merged = mode_keybinds_self.merge(mode_keybinds_other); - - assert_eq!(mode_keybinds_expected, mode_keybinds_merged); -} - -#[test] -fn merge_mode_keybinds_overwrites_same_keys() { - let mut mode_keybinds_self = ModeKeybinds::new(); - mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]); - let mut mode_keybinds_other = ModeKeybinds::new(); - mode_keybinds_other - .0 - .insert(Key::F(1), vec![Action::GoToTab(1)]); - - let mut mode_keybinds_expected = ModeKeybinds::new(); - mode_keybinds_expected - .0 - .insert(Key::F(1), vec![Action::GoToTab(1)]); - - let mode_keybinds_merged = mode_keybinds_self.merge(mode_keybinds_other); - - assert_eq!(mode_keybinds_expected, mode_keybinds_merged); -} - -#[test] -fn merge_keybinds_merges() { - let mut mode_keybinds_self = ModeKeybinds::new(); - mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]); - let mut mode_keybinds_other = ModeKeybinds::new(); - mode_keybinds_other - .0 - .insert(Key::Backspace, vec![Action::NoOp]); - let mut keybinds_self = Keybinds::new(); - keybinds_self - .0 - .insert(InputMode::Normal, mode_keybinds_self.clone()); - let mut keybinds_other = Keybinds::new(); - keybinds_other - .0 - .insert(InputMode::Resize, mode_keybinds_other.clone()); - let mut keybinds_expected = Keybinds::new(); - keybinds_expected - .0 - .insert(InputMode::Normal, mode_keybinds_self); - keybinds_expected - .0 - .insert(InputMode::Resize, mode_keybinds_other); - - assert_eq!( - keybinds_expected, - keybinds_self.merge_keybinds(keybinds_other) - ) -} - -#[test] -fn merge_keybinds_overwrites_same_keys() { - let mut mode_keybinds_self = ModeKeybinds::new(); - mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]); - mode_keybinds_self.0.insert(Key::F(2), vec![Action::NoOp]); - mode_keybinds_self.0.insert(Key::F(3), vec![Action::NoOp]); - let mut mode_keybinds_other = ModeKeybinds::new(); - mode_keybinds_other - .0 - .insert(Key::F(1), vec![Action::GoToTab(1)]); - mode_keybinds_other - .0 - .insert(Key::F(2), vec![Action::GoToTab(2)]); - mode_keybinds_other - .0 - .insert(Key::F(3), vec![Action::GoToTab(3)]); - let mut keybinds_self = Keybinds::new(); - keybinds_self - .0 - .insert(InputMode::Normal, mode_keybinds_self.clone()); - let mut keybinds_other = Keybinds::new(); - keybinds_other - .0 - .insert(InputMode::Normal, mode_keybinds_other.clone()); - let mut keybinds_expected = Keybinds::new(); - keybinds_expected - .0 - .insert(InputMode::Normal, mode_keybinds_other); - - assert_eq!( - keybinds_expected, - keybinds_self.merge_keybinds(keybinds_other) - ) -} - -#[test] -fn from_keyaction_from_yaml_to_mode_keybindings() { - let actions = vec![Action::NoOp, Action::GoToTab(1)]; - let keyaction = KeyActionFromYaml { - action: actions.clone(), - key: vec![Key::F(1), Key::Backspace, Key::Char('t')], - }; - - let mut expected = ModeKeybinds::new(); - expected.0.insert(Key::F(1), actions.clone()); - expected.0.insert(Key::Backspace, actions.clone()); - expected.0.insert(Key::Char('t'), actions); - - assert_eq!(expected, ModeKeybinds::from(keyaction)); -} - -#[test] -fn toplevel_unbind_unbinds_all() { - let from_yaml = KeybindsFromYaml { - unbind: Unbind::All(true), - keybinds: HashMap::new(), - }; - - let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml)); - - assert_eq!(keybinds_from_yaml, Keybinds::new()); -} - -#[test] -fn no_unbind_unbinds_none() { - let from_yaml = KeybindsFromYaml { - unbind: Unbind::All(false), - keybinds: HashMap::new(), - }; - - 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/ipc.rs b/src/common/ipc.rs deleted file mode 100644 index 129727a1..00000000 --- a/src/common/ipc.rs +++ /dev/null @@ -1,131 +0,0 @@ -//! IPC stuff for starting to split things into a client and server model. - -use crate::cli::CliArgs; -use crate::common::{ - errors::{get_current_ctx, ErrorContext}, - input::{actions::Action, options::Options}, -}; -use crate::panes::PositionAndSize; -use interprocess::local_socket::LocalSocketStream; -use nix::unistd::dup; -use serde::{Deserialize, Serialize}; -use std::io::{self, Write}; -use std::marker::PhantomData; -use std::os::unix::io::{AsRawFd, FromRawFd}; - -type SessionId = u64; - -#[derive(PartialEq, Eq, Serialize, Deserialize, Hash)] -pub struct Session { - // Unique ID for this session - id: SessionId, - // Identifier for the underlying IPC primitive (socket, pipe) - conn_name: String, - // User configured alias for the session - alias: String, -} - -// How do we want to connect to a session? -#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum ClientType { - Reader, - Writer, -} - -// Types of messages sent from the client to the server -#[derive(Serialize, Deserialize, Debug, Clone)] -pub enum ClientToServerMsg { - /*// List which sessions are available - ListSessions, - // Create a new session - CreateSession, - // Attach to a running session - AttachToSession(SessionId, ClientType), - // Force detach - DetachSession(SessionId), - // Disconnect from the session we're connected to - DisconnectFromSession,*/ - ClientExit, - TerminalResize(PositionAndSize), - NewClient(PositionAndSize, CliArgs, Options), - Action(Action), -} - -// Types of messages sent from the server to the client -#[derive(Serialize, Deserialize, Debug, Clone)] -pub enum ServerToClientMsg { - /*// Info about a particular session - SessionInfo(Session), - // A list of sessions - SessionList(HashSet<Session>),*/ - Render(Option<String>), - UnblockInputThread, - Exit, - ServerError(String), -} - -/// Sends messages on a stream socket, along with an [`ErrorContext`]. -pub struct IpcSenderWithContext<T: Serialize> { - sender: io::BufWriter<LocalSocketStream>, - _phantom: PhantomData<T>, -} - -impl<T: Serialize> IpcSenderWithContext<T> { - /// Returns a sender to the given [LocalSocketStream](interprocess::local_socket::LocalSocketStream). - pub fn new(sender: LocalSocketStream) -> Self { - Self { - sender: io::BufWriter::new(sender), - _phantom: PhantomData, - } - } - - /// Sends an event, along with the current [`ErrorContext`], on this [`IpcSenderWithContext`]'s socket. - pub fn send(&mut self, msg: T) { - let err_ctx = get_current_ctx(); - bincode::serialize_into(&mut self.sender, &(msg, err_ctx)).unwrap(); - self.sender.flush().unwrap(); - } - - /// Returns an [`IpcReceiverWithContext`] with the same socket as this sender. - pub fn get_receiver<F>(&self) -> IpcReceiverWithContext<F> - where - F: for<'de> Deserialize<'de> + Serialize, - { - let sock_fd = self.sender.get_ref().as_raw_fd(); - let dup_sock = dup(sock_fd).unwrap(); - let socket = unsafe { LocalSocketStream::from_raw_fd(dup_sock) }; - IpcReceiverWithContext::new(socket) - } -} - -/// Receives messages on a stream socket, along with an [`ErrorContext`]. -pub struct IpcReceiverWithContext<T> { - receiver: io::BufReader<LocalSocketStream>, - _phantom: PhantomData<T>, -} - -impl<T> IpcReceiverWithContext<T> -where - T: for<'de> Deserialize<'de> + Serialize, -{ - /// Returns a receiver to the given [LocalSocketStream](interprocess::local_socket::LocalSocketStream). - pub fn new(receiver: LocalSocketStream) -> Self { - Self { - receiver: io::BufReader::new(receiver), - _phantom: PhantomData, - } - } - - /// Receives an event, along with the current [`ErrorContext`], on this [`IpcReceiverWithContext`]'s socket. - pub fn recv(&mut self) -> (T, ErrorContext) { - bincode::deserialize_from(&mut self.receiver).unwrap() - } - - /// Returns an [`IpcSenderWithContext`] with the same socket as this receiver. - pub fn get_sender<F: Serialize>(&self) -> IpcSenderWithContext<F> { - let sock_fd = self.receiver.get_ref().as_raw_fd(); - let dup_sock = dup(sock_fd).unwrap(); - let socket = unsafe { LocalSocketStream::from_raw_fd(dup_sock) }; - IpcSenderWithContext::new(socket) - } -} diff --git a/src/common/mod.rs b/src/common/mod.rs deleted file mode 100644 index c91e1e05..00000000 --- a/src/common/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub mod command_is_executing; -pub mod errors; -pub mod input; -pub mod ipc; -pub mod os_input_output; -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; diff --git a/src/common/os_input_output.rs b/src/common/os_input_output.rs deleted file mode 100644 index 6b849efa..00000000 --- a/src/common/os_input_output.rs +++ /dev/null @@ -1,414 +0,0 @@ -use crate::common::ipc::{ - ClientToServerMsg, IpcReceiverWithContext, IpcSenderWithContext, ServerToClientMsg, -}; -use crate::errors::ErrorContext; -use crate::panes::PositionAndSize; -use crate::utils::shared::default_palette; -use interprocess::local_socket::LocalSocketStream; -use nix::fcntl::{fcntl, FcntlArg, OFlag}; -use nix::pty::{forkpty, Winsize}; -use nix::sys::signal::{kill, Signal}; -use nix::sys::termios; -use nix::sys::wait::waitpid; -use nix::unistd::{self, ForkResult, Pid}; -use signal_hook::{consts::signal::*, iterator::Signals}; -use std::io::prelude::*; -use std::os::unix::{fs::PermissionsExt, io::RawFd}; -use std::path::{Path, PathBuf}; -use std::process::{Child, Command}; -use std::sync::{Arc, Mutex}; -use std::{env, fs, io}; -use zellij_tile::data::Palette; - -const UNIX_PERMISSIONS: u32 = 0o700; - -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) -} - -fn into_raw_mode(pid: RawFd) { - let mut tio = termios::tcgetattr(pid).expect("could not get terminal attribute"); - termios::cfmakeraw(&mut tio); - match termios::tcsetattr(pid, termios::SetArg::TCSANOW, &tio) { - Ok(_) => {} - Err(e) => panic!("error {:?}", e), - }; -} - -fn unset_raw_mode(pid: RawFd, orig_termios: termios::Termios) { - match termios::tcsetattr(pid, termios::SetArg::TCSANOW, &orig_termios) { - Ok(_) => {} - Err(e) => panic!("error {:?}", e), - }; -} - -pub fn get_terminal_size_using_fd(fd: RawFd) -> PositionAndSize { - // TODO: do this with the nix ioctl - use libc::ioctl; - use libc::TIOCGWINSZ; - - let mut winsize = Winsize { - ws_row: 0, - ws_col: 0, - ws_xpixel: 0, - ws_ypixel: 0, - }; - - unsafe { ioctl(fd, TIOCGWINSZ, &mut winsize) }; - PositionAndSize::from(winsize) -} - -pub fn set_terminal_size_using_fd(fd: RawFd, columns: u16, rows: u16) { - // TODO: do this with the nix ioctl - use libc::ioctl; - use libc::TIOCSWINSZ; - - let winsize = Winsize { - ws_col: columns, - ws_row: rows, - ws_xpixel: 0, - ws_ypixel: 0, - }; - unsafe { ioctl(fd, TIOCSWINSZ, &winsize) }; -} - -/// Handle some signals for the child process. This will loop until the child -/// process exits. -fn handle_command_exit(mut child: Child) { - // register the SIGINT signal (TODO handle more signals) - let mut signals = ::signal_hook::iterator::Signals::new(&[SIGINT]).unwrap(); - 'handle_exit: loop { - // test whether the child process has exited - match child.try_wait() { - Ok(Some(_status)) => { - // if the child process has exited, break outside of the loop - // and exit this function - // TODO: handle errors? - break 'handle_exit; - } - Ok(None) => { - ::std::thread::sleep(::std::time::Duration::from_millis(100)); - } - Err(e) => panic!("error attempting to wait: {}", e), - } - - for signal in signals.pending() { - if let SIGINT = signal { - child.kill().unwrap(); - child.wait().unwrap(); - break 'handle_exit; - } - } - } -} - -/// Spawns a new terminal from the parent terminal with [`termios`](termios::Termios) -/// `orig_termios`. -/// -/// If a `file_to_open` is given, the text editor specified by environment variable `EDITOR` -/// (or `VISUAL`, if `EDITOR` is not set) will be started in the new terminal, with the given -/// file open. If no file is given, the shell specified by environment variable `SHELL` will -/// be started in the new terminal. -/// -/// # Panics -/// -/// This function will panic if both the `EDITOR` and `VISUAL` environment variables are not -/// set. -// FIXME this should probably be split into different functions, or at least have less levels -// of indentation in some way -fn spawn_terminal(file_to_open: Option<PathBuf>, orig_termios: termios::Termios) -> (RawFd, RawFd) { - let (pid_primary, pid_secondary): (RawFd, RawFd) = { - match forkpty(None, Some(&orig_termios)) { - Ok(fork_pty_res) => { - let pid_primary = fork_pty_res.master; - let pid_secondary = match fork_pty_res.fork_result { - ForkResult::Parent { child } => { - // fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::empty())).expect("could not fcntl"); - fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)) - .expect("could not fcntl"); - child - } - ForkResult::Child => match file_to_open { - Some(file_to_open) => { - if env::var("EDITOR").is_err() && env::var("VISUAL").is_err() { - panic!("Can't edit files if an editor is not defined. To fix: define the EDITOR or VISUAL environment variables with the path to your editor (eg. /usr/bin/vim)"); - } - let editor = - env::var("EDITOR").unwrap_or_else(|_| env::var("VISUAL").unwrap()); - - let child = Command::new(editor) - .args(&[file_to_open]) - .spawn() - .expect("failed to spawn"); - handle_command_exit(child); - ::std::process::exit(0); - } - None => { - let child = Command::new(env::var("SHELL").unwrap()) - .spawn() - .expect("failed to spawn"); - handle_command_exit(child); - ::std::process::exit(0); - } - }, - }; - (pid_primary, pid_secondary.as_raw()) - } - Err(e) => { - panic!("failed to fork {:?}", e); - } - } - }; - (pid_primary, pid_secondary) -} - -#[derive(Clone)] -pub struct ServerOsInputOutput { - orig_termios: Arc<Mutex<termios::Termios>>, - receive_instructions_from_client: Option<Arc<Mutex<IpcReceiverWithContext<ClientToServerMsg>>>>, - send_instructions_to_client: Arc<Mutex<Option<IpcSenderWithContext<ServerToClientMsg>>>>, -} - -/// The `ServerOsApi` trait represents an abstract interface to the features of an operating system that -/// Zellij server requires. -pub trait ServerOsApi: Send + Sync { - /// Sets the size of the terminal associated to file descriptor `fd`. - fn set_terminal_size_using_fd(&mut self, fd: RawFd, cols: u16, rows: u16); - /// Spawn a new terminal, with an optional file to open in a terminal program. - fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> (RawFd, RawFd); - /// Read bytes from the standard output of the virtual terminal referred to by `fd`. - fn read_from_tty_stdout(&mut self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>; - /// Write bytes to the standard input of the virtual terminal referred to by `fd`. - fn write_to_tty_stdin(&mut self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>; - /// Wait until all output written to the object referred to by `fd` has been transmitted. - fn tcdrain(&mut self, fd: RawFd) -> Result<(), nix::Error>; - /// Terminate the process with process ID `pid`. - // FIXME `RawFd` is semantically the wrong type here. It should either be a raw libc::pid_t, - // or a nix::unistd::Pid. See `man kill.3`, nix::sys::signal::kill (both take an argument - // called `pid` and of type `pid_t`, and not `fd`) - fn kill(&mut self, pid: RawFd) -> Result<(), nix::Error>; - /// Returns a [`Box`] pointer to this [`ServerOsApi`] struct. - fn box_clone(&self) -> Box<dyn ServerOsApi>; - /// Receives a message on server-side IPC channel - fn recv_from_client(&self) -> (ClientToServerMsg, ErrorContext); - /// Sends a message to client - fn send_to_client(&self, msg: ServerToClientMsg); - /// Adds a sender to client - fn add_client_sender(&mut self); - /// Update the receiver socket for the client - fn update_receiver(&mut self, stream: LocalSocketStream); - fn load_palette(&self) -> Palette; -} - -impl ServerOsApi for ServerOsInputOutput { - fn set_terminal_size_using_fd(&mut self, fd: RawFd, cols: u16, rows: u16) { - set_terminal_size_using_fd(fd, cols, rows); - } - fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> (RawFd, RawFd) { - let orig_termios = self.orig_termios.lock().unwrap(); - spawn_terminal(file_to_open, orig_termios.clone()) - } - fn read_from_tty_stdout(&mut self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> { - unistd::read(fd, buf) - } - fn write_to_tty_stdin(&mut self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> { - unistd::write(fd, buf) - } - fn tcdrain(&mut self, fd: RawFd) -> Result<(), nix::Error> { - termios::tcdrain(fd) - } - fn box_clone(&self) -> Box<dyn ServerOsApi> { - Box::new((*self).clone()) - } - fn kill(&mut self, pid: RawFd) -> Result<(), nix::Error> { - // TODO: - // Ideally, we should be using SIGINT rather than SIGKILL here, but there are cases in which - // the terminal we're trying to kill hangs on SIGINT and so all the app gets stuck - // that's why we're sending SIGKILL here - // A better solution would be to send SIGINT here and not wait for it, and then have - // a background thread do the waitpid stuff and send SIGKILL if the process is stuck - kill(Pid::from_raw(pid), Some(Signal::SIGKILL)).unwrap(); - waitpid(Pid::from_raw(pid), None).unwrap(); - Ok(()) - } - fn recv_from_client(&self) -> (ClientToServerMsg, ErrorContext) { - self.receive_instructions_from_client - .as_ref() - .unwrap() - .lock() - .unwrap() - .recv() - } - fn send_to_client(&self, msg: ServerToClientMsg) { - self.send_instructions_to_client - .lock() - .unwrap() - .as_mut() - .unwrap() - .send(msg); - } - fn add_client_sender(&mut self) { - assert!(self.send_instructions_to_client.lock().unwrap().is_none()); - let sender = self - .receive_instructions_from_client - .as_ref() - .unwrap() - .lock() - .unwrap() - .get_sender(); - *self.send_instructions_to_client.lock().unwrap() = Some(sender); - } - fn update_receiver(&mut self, stream: LocalSocketStream) { - self.receive_instructions_from_client = - Some(Arc::new(Mutex::new(IpcReceiverWithContext::new(stream)))); - } - fn load_palette(&self) -> Palette { - default_palette() - } -} - -impl Clone for Box<dyn ServerOsApi> { - fn clone(&self) -> Box<dyn ServerOsApi> { - self.box_clone() - } -} - -pub fn get_server_os_input() -> Result<ServerOsInputOutput, nix::Error> { - let current_termios = termios::tcgetattr(0)?; - let orig_termios = Arc::new(Mutex::new(current_termios)); - Ok(ServerOsInputOutput { - orig_termios, - receive_instructions_from_client: None, - send_instructions_to_client: Arc::new(Mutex::new(None)), - }) -} - -#[derive(Clone)] -pub struct ClientOsInputOutput { - orig_termios: Arc<Mutex<termios::Termios>>, - send_instructions_to_server: Arc<Mutex<Option<IpcSenderWithContext<ClientToServerMsg>>>>, - receive_instructions_from_server: Arc<Mutex<Option<IpcReceiverWithContext<ServerToClientMsg>>>>, -} - -/// The `ClientOsApi` trait represents an abstract interface to the features of an operating system that -/// Zellij client requires. -pub trait ClientOsApi: Send + Sync { - /// Returns the size of the terminal associated to file descriptor `fd`. - fn get_terminal_size_using_fd(&self, fd: RawFd) -> PositionAndSize; - /// Set the terminal associated to file descriptor `fd` to - /// [raw mode](https://en.wikipedia.org/wiki/Terminal_mode). - fn set_raw_mode(&mut self, fd: RawFd); - /// Set the terminal associated to file descriptor `fd` to - /// [cooked mode](https://en.wikipedia.org/wiki/Terminal_mode). - fn unset_raw_mode(&self, fd: RawFd); - /// Returns the writer that allows writing to standard output. - fn get_stdout_writer(&self) -> Box<dyn io::Write>; - /// Returns the raw contents of standard input. - fn read_from_stdin(&self) -> Vec<u8>; - /// Returns a [`Box`] pointer to this [`ClientOsApi`] struct. - fn box_clone(&self) -> Box<dyn ClientOsApi>; - /// Sends a message to the server. - fn send_to_server(&self, msg: ClientToServerMsg); - /// Receives a message on client-side IPC channel - // This should be called from the client-side router thread only. - fn recv_from_server(&self) -> (ServerToClientMsg, ErrorContext); - fn handle_signals(&self, sigwinch_cb: Box<dyn Fn()>, quit_cb: Box<dyn Fn()>); - /// Establish a connection with the server socket. - fn connect_to_server(&self, path: &Path); -} - -impl ClientOsApi for ClientOsInputOutput { - fn get_terminal_size_using_fd(&self, fd: RawFd) -> PositionAndSize { - get_terminal_size_using_fd(fd) - } - fn set_raw_mode(&mut self, fd: RawFd) { - into_raw_mode(fd); - } - fn unset_raw_mode(&self, fd: RawFd) { - let orig_termios = self.orig_termios.lock().unwrap(); - unset_raw_mode(fd, orig_termios.clone()); - } - fn box_clone(&self) -> Box<dyn ClientOsApi> { - Box::new((*self).clone()) - } - fn read_from_stdin(&self) -> Vec<u8> { - let stdin = std::io::stdin(); - let mut stdin = stdin.lock(); - let buffer = stdin.fill_buf().unwrap(); - let length = buffer.len(); - let read_bytes = Vec::from(buffer); - stdin.consume(length); - read_bytes - } - fn get_stdout_writer(&self) -> Box<dyn io::Write> { - let stdout = ::std::io::stdout(); - Box::new(stdout) - } - fn send_to_server(&self, msg: ClientToServerMsg) { - self.send_instructions_to_server - .lock() - .unwrap() - .as_mut() - .unwrap() - .send(msg); - } - fn recv_from_server(&self) -> (ServerToClientMsg, ErrorContext) { - self.receive_instructions_from_server - .lock() - .unwrap() - .as_mut() - .unwrap() - .recv() - } - fn handle_signals(&self, sigwinch_cb: Box<dyn Fn()>, quit_cb: Box<dyn Fn()>) { - let mut signals = Signals::new(&[SIGWINCH, SIGTERM, SIGINT, SIGQUIT, SIGHUP]).unwrap(); - for signal in signals.forever() { - match signal { - SIGWINCH => { - sigwinch_cb(); - } - SIGTERM | SIGINT | SIGQUIT | SIGHUP => { - quit_cb(); - break; - } - _ => unreachable!(), - } - } - } - fn connect_to_server(&self, path: &Path) { - let socket; - loop { - match LocalSocketStream::connect(path) { - Ok(sock) => { - socket = sock; - break; - } - Err(_) => { - std::thread::sleep(std::time::Duration::from_millis(50)); - } - } - } - let sender = IpcSenderWithContext::new(socket); - let receiver = sender.get_receiver(); - *self.send_instructions_to_server.lock().unwrap() = Some(sender); - *self.receive_instructions_from_server.lock().unwrap() = Some(receiver); - } -} - -impl Clone for Box<dyn ClientOsApi> { - fn clone(&self) -> Box<dyn ClientOsApi> { - self.box_clone() - } -} - -pub fn get_client_os_input() -> Result<ClientOsInputOutput, nix::Error> { - let current_termios = termios::tcgetattr(0)?; - let orig_termios = Arc::new(Mutex::new(current_termios)); - Ok(ClientOsInputOutput { - orig_termios, - send_instructions_to_server: Arc::new(Mutex::new(None)), - receive_instructions_from_server: Arc::new(Mutex::new(None)), - }) -} diff --git a/src/common/pty.rs b/src/common/pty.rs deleted file mode 100644 index 5f5442b5..00000000 --- a/src/common/pty.rs +++ /dev/null @@ -1,293 +0,0 @@ -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 crate::client::panes::PaneId; -use crate::common::errors::{get_current_ctx, ContextType}; -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::wasm_vm::PluginInstruction; - -pub struct ReadFromPid { - pid: RawFd, - os_input: Box<dyn ServerOsApi>, -} - -impl ReadFromPid { - pub fn new(pid: &RawFd, os_input: Box<dyn ServerOsApi>) -> ReadFromPid { - ReadFromPid { - pid: *pid, - os_input, - } - } -} - -impl Stream for ReadFromPid { - type Item = Vec<u8>; - fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { - let mut read_buffer = [0; 65535]; - let pid = self.pid; - let read_result = &self.os_input.read_from_tty_stdout(pid, &mut read_buffer); - match read_result { - Ok(res) => { - if *res == 0 { - // indicates end of file - Poll::Ready(None) - } else { - let res = Some(read_buffer[..*res].to_vec()); - Poll::Ready(res) - } - } - Err(e) => { - match e { - nix::Error::Sys(errno) => { - if *errno == nix::errno::Errno::EAGAIN { - Poll::Ready(Some(vec![])) // TODO: better with timeout waker somehow - } else { - Poll::Ready(None) - } - } - _ => Poll::Ready(None), - } - } - } - } -} - -pub type VteBytes = Vec<u8>; - -/// Instructions related to PTYs (pseudoterminals). -#[derive(Clone, Debug)] -pub enum PtyInstruction { - SpawnTerminal(Option<PathBuf>), - SpawnTerminalVertically(Option<PathBuf>), - SpawnTerminalHorizontally(Option<PathBuf>), - NewTab, - ClosePane(PaneId), - CloseTab(Vec<PaneId>), - Exit, -} - -pub struct Pty { - pub bus: Bus<PtyInstruction>, - pub id_to_child_pid: HashMap<RawFd, RawFd>, - 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((&event).into())); - 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, - senders: ThreadSenders, - os_input: Box<dyn ServerOsApi>, - debug: bool, -) -> JoinHandle<()> { - let mut err_ctx = get_current_ctx(); - task::spawn({ - async move { - err_ctx.add_call(ContextType::AsyncTask); - let mut terminal_bytes = ReadFromPid::new(&pid, os_input); - - let mut last_byte_receive_time: Option<Instant> = None; - let mut pending_render = false; - let max_render_pause = Duration::from_millis(30); - - while let Some(bytes) = terminal_bytes.next().await { - let bytes_is_empty = bytes.is_empty(); - if debug { - for byte in bytes.iter() { - debug_to_file(*byte, pid).unwrap(); - } - } - if !bytes_is_empty { - 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 - // (otherwise if we get a large amount of data, the display would hang - // until it's done) - // 3. the stream has ended, and so we render 1 last time - match last_byte_receive_time.as_mut() { - Some(receive_time) => { - if receive_time.elapsed() > max_render_pause { - pending_render = false; - let _ = senders.send_to_screen(ScreenInstruction::Render); - last_byte_receive_time = Some(Instant::now()); - } else { - pending_render = true; - } - } - None => { - last_byte_receive_time = Some(Instant::now()); - pending_render = true; - } - }; - } else { - if pending_render { - pending_render = false; - let _ = senders.send_to_screen(ScreenInstruction::Render); - } - last_byte_receive_time = None; - task::sleep(::std::time::Duration::from_millis(10)).await; - } - } - 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 - senders - .send_to_screen(ScreenInstruction::ClosePane(PaneId::Terminal(pid))) - .unwrap(); - } - }) -} - -impl Pty { - pub fn new(bus: Bus<PtyInstruction>, debug_to_file: bool) -> Self { - Pty { - bus, - id_to_child_pid: HashMap::new(), - 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 - .bus - .os_input - .as_mut() - .unwrap() - .spawn_terminal(file_to_open); - let task_handle = stream_terminal_bytes( - pid_primary, - self.bus.senders.clone(), - self.bus.os_input.as_ref().unwrap().clone(), - self.debug_to_file, - ); - self.task_handles.insert(pid_primary, task_handle); - self.id_to_child_pid.insert(pid_primary, pid_secondary); - pid_primary - } - pub fn spawn_terminals_for_layout(&mut self, layout: Layout) { - 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.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.bus - .senders - .send_to_screen(ScreenInstruction::ApplyLayout( - layout, - new_pane_pids.clone(), - )) - .unwrap(); - for id in new_pane_pids { - let task_handle = stream_terminal_bytes( - id, - self.bus.senders.clone(), - self.bus.os_input.as_ref().unwrap().clone(), - self.debug_to_file, - ); - self.task_handles.insert(id, task_handle); - } - } - pub fn close_pane(&mut self, id: PaneId) { - match id { - PaneId::Terminal(id) => { - let child_pid = self.id_to_child_pid.remove(&id).unwrap(); - let handle = self.task_handles.remove(&id).unwrap(); - self.bus.os_input.as_mut().unwrap().kill(child_pid).unwrap(); - task::block_on(async { - handle.cancel().await; - }); - } - PaneId::Plugin(pid) => drop( - self.bus - .senders - .send_to_plugin(PluginInstruction::Unload(pid)), - ), - } - } - pub fn close_tab(&mut self, ids: Vec<PaneId>) { - ids.iter().for_each(|&id| { - self.close_pane(id); - }); - } -} - -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 { - self.close_pane(PaneId::Terminal(id)); - } - } -} diff --git a/src/common/screen.rs b/src/common/screen.rs deleted file mode 100644 index 62fcec19..00000000 --- a/src/common/screen.rs +++ /dev/null @@ -1,595 +0,0 @@ -//! Things related to [`Screen`]s. - -use std::collections::BTreeMap; -use std::os::unix::io::RawFd; -use std::str; - -use crate::common::input::options::Options; -use crate::common::pty::{PtyInstruction, VteBytes}; -use crate::common::thread_bus::Bus; -use crate::errors::ContextType; -use crate::layout::Layout; -use crate::panes::PaneId; -use crate::panes::PositionAndSize; -use crate::server::ServerInstruction; -use crate::tab::Tab; -use crate::wasm_vm::PluginInstruction; - -use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, PluginCapabilities, TabInfo}; - -/// Instructions that can be sent to the [`Screen`]. -#[derive(Debug, Clone)] -pub enum ScreenInstruction { - PtyBytes(RawFd, VteBytes), - Render, - NewPane(PaneId), - HorizontalSplit(PaneId), - VerticalSplit(PaneId), - WriteCharacter(Vec<u8>), - ResizeLeft, - ResizeRight, - ResizeDown, - ResizeUp, - SwitchFocus, - FocusNextPane, - FocusPreviousPane, - MoveFocusLeft, - MoveFocusLeftOrPreviousTab, - MoveFocusDown, - MoveFocusUp, - MoveFocusRight, - MoveFocusRightOrNextTab, - Exit, - ScrollUp, - ScrollDown, - PageScrollUp, - PageScrollDown, - ClearScroll, - CloseFocusedPane, - ToggleActiveTerminalFullscreen, - SetSelectable(PaneId, bool), - SetMaxHeight(PaneId, usize), - SetInvisibleBorders(PaneId, bool), - ClosePane(PaneId), - ApplyLayout(Layout, Vec<RawFd>), - NewTab(RawFd), - SwitchTabNext, - SwitchTabPrev, - ToggleActiveSyncTab, - CloseTab, - GoToTab(u32), - UpdateTabName(Vec<u8>), - TerminalResize(PositionAndSize), - ChangeMode(ModeInfo), -} - -/// 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 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>, - /// The full size of this [`Screen`]. - full_screen_ws: PositionAndSize, - /// The index of this [`Screen`]'s active [`Tab`]. - active_tab_index: Option<usize>, - mode_info: ModeInfo, - input_mode: InputMode, - colors: Palette, -} - -impl Screen { - /// Creates and returns a new [`Screen`]. - pub fn new( - bus: Bus<ScreenInstruction>, - full_screen_ws: &PositionAndSize, - max_panes: Option<usize>, - mode_info: ModeInfo, - input_mode: InputMode, - colors: Palette, - ) -> Self { - Screen { - bus, - max_panes, - full_screen_ws: *full_screen_ws, - active_tab_index: None, - tabs: BTreeMap::new(), - mode_info, - input_mode, - colors, - } - } - - /// Creates a new [`Tab`] in this [`Screen`], containing a single - /// [pane](crate::client::panes) with PTY file descriptor `pane_id`. - pub fn new_tab(&mut self, pane_id: RawFd) { - let tab_index = self.get_new_tab_index(); - let position = self.tabs.len(); - let tab = Tab::new( - tab_index, - position, - String::new(), - &self.full_screen_ws, - self.bus.os_input.as_ref().unwrap().clone(), - self.bus.senders.clone(), - self.max_panes, - Some(PaneId::Terminal(pane_id)), - self.mode_info.clone(), - self.input_mode, - self.colors, - ); - self.active_tab_index = Some(tab_index); - self.tabs.insert(tab_index, tab); - self.update_tabs(); - self.render(); - } - - /// Returns the index where a new [`Tab`] should be created in this [`Screen`]. - /// Currently, this is right after the last currently existing tab, or `0` if - /// no tabs exist in this screen yet. - fn get_new_tab_index(&self) -> usize { - if let Some(index) = self.tabs.keys().last() { - *index + 1 - } else { - 0 - } - } - - /// Sets this [`Screen`]'s active [`Tab`] to the next tab. - pub fn switch_tab_next(&mut self) { - 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_mut() { - if tab.position == new_tab_pos { - tab.set_force_render(); - self.active_tab_index = Some(tab.index); - break; - } - } - self.update_tabs(); - self.render(); - } - - /// Sets this [`Screen`]'s active [`Tab`] to the previous tab. - pub fn switch_tab_prev(&mut self) { - let active_tab_pos = self.get_active_tab().unwrap().position; - let new_tab_pos = if active_tab_pos == 0 { - self.tabs.len() - 1 - } else { - active_tab_pos - 1 - }; - 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; - } - } - self.update_tabs(); - self.render(); - } - - pub fn go_to_tab(&mut self, mut tab_index: usize) { - tab_index -= 1; - 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(); - } - } - } - - /// Closes this [`Screen`]'s active [`Tab`], exiting the application if it happens - /// to be the last tab. - pub fn close_tab(&mut self) { - let active_tab_index = self.active_tab_index.unwrap(); - if self.tabs.len() > 1 { - self.switch_tab_prev(); - } - let active_tab = self.tabs.remove(&active_tab_index).unwrap(); - let pane_ids = active_tab.get_pane_ids(); - // 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.bus - .senders - .send_to_pty(PtyInstruction::CloseTab(pane_ids)) - .unwrap(); - if self.tabs.is_empty() { - self.active_tab_index = None; - self.bus - .senders - .send_to_server(ServerInstruction::Render(None)) - .unwrap(); - } else { - for t in self.tabs.values_mut() { - if t.position > active_tab.position { - t.position -= 1; - } - } - self.update_tabs(); - } - } - - pub fn resize_to_screen(&mut self, new_screen_size: PositionAndSize) { - self.full_screen_ws = new_screen_size; - 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(); - } - - /// Renders this [`Screen`], which amounts to rendering its active [`Tab`]. - pub fn render(&mut self) { - if let Some(active_tab) = self.get_active_tab_mut() { - if active_tab.get_active_pane().is_some() { - active_tab.render(); - } else { - self.close_tab(); - } - }; - } - - /// Returns a mutable reference to this [`Screen`]'s tabs. - pub fn get_tabs_mut(&mut self) -> &mut BTreeMap<usize, Tab> { - &mut self.tabs - } - - /// Returns an immutable reference to this [`Screen`]'s active [`Tab`]. - pub fn get_active_tab(&self) -> Option<&Tab> { - match self.active_tab_index { - Some(tab) => self.tabs.get(&tab), - None => None, - } - } - - /// Returns a mutable reference to this [`Screen`]'s active [`Tab`]. - pub fn get_active_tab_mut(&mut self) -> Option<&mut Tab> { - match self.active_tab_index { - Some(tab) => self.get_tabs_mut().get_mut(&tab), - None => None, - } - } - - /// Creates a new [`Tab`] in this [`Screen`], applying the specified [`Layout`] - /// and switching to it. - pub fn apply_layout(&mut self, layout: Layout, new_pids: Vec<RawFd>) { - let tab_index = self.get_new_tab_index(); - let position = self.tabs.len(); - let mut tab = Tab::new( - tab_index, - position, - String::new(), - &self.full_screen_ws, - self.bus.os_input.as_ref().unwrap().clone(), - self.bus.senders.clone(), - self.max_panes, - None, - self.mode_info.clone(), - self.input_mode, - self.colors, - ); - tab.apply_layout(layout, new_pids); - self.active_tab_index = Some(tab_index); - self.tabs.insert(tab_index, tab); - self.update_tabs(); - } - - 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() { - tab_data.push(TabInfo { - position: tab.position, - name: tab.name.clone(), - active: active_tab_index == tab.index, - is_sync_panes_active: tab.is_sync_panes_active(), - }); - } - self.bus - .senders - .send_to_plugin(PluginInstruction::Update(None, Event::TabUpdate(tab_data))) - .unwrap(); - } - - pub fn update_active_tab_name(&mut self, buf: Vec<u8>) { - let s = str::from_utf8(&buf).unwrap(); - let active_tab = self.get_active_tab_mut().unwrap(); - match s { - "\0" => { - active_tab.name = String::new(); - } - "\u{007F}" | "\u{0008}" => { - //delete and backspace keys - active_tab.name.pop(); - } - c => { - active_tab.name.push_str(c); - } - } - self.update_tabs(); - } - pub fn change_mode(&mut self, mode_info: ModeInfo) { - self.mode_info = mode_info; - for tab in self.tabs.values_mut() { - tab.mode_info = self.mode_info.clone(); - } - } -} - -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((&event).into())); - 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 deleted file mode 100644 index 2e076776..00000000 --- a/src/common/setup.rs +++ /dev/null @@ -1,240 +0,0 @@ -use crate::cli::CliArgs; -use crate::common::utils::consts::{ - FEATURES, SYSTEM_DEFAULT_CONFIG_DIR, SYSTEM_DEFAULT_DATA_DIR_PREFIX, VERSION, ZELLIJ_PROJ_DIR, -}; -use crate::os_input_output::set_permissions; -use directories_next::BaseDirs; -use serde::{Deserialize, Serialize}; -use std::io::Write; -use std::{fs, path::Path, path::PathBuf}; -use structopt::StructOpt; - -const CONFIG_LOCATION: &str = ".config/zellij"; -const CONFIG_NAME: &str = "config.yaml"; -static ARROW_SEPARATOR: &str = ""; - -#[macro_export] -macro_rules! asset_map { - ($($src:literal => $dst:literal),+ $(,)?) => { - { - let mut assets = std::collections::HashMap::new(); - $( - assets.insert($dst, include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/", $src)).to_vec()); - )+ - assets - } - } -} - -pub mod install { - use super::*; - - pub fn populate_data_dir(data_dir: &Path) { - // First run installation of default plugins & layouts - let mut assets = asset_map! { - "assets/layouts/default.yaml" => "layouts/default.yaml", - "assets/layouts/strider.yaml" => "layouts/strider.yaml", - }; - assets.extend(asset_map! { - "assets/plugins/status-bar.wasm" => "plugins/status-bar.wasm", - "assets/plugins/tab-bar.wasm" => "plugins/tab-bar.wasm", - "assets/plugins/strider.wasm" => "plugins/strider.wasm", - }); - assets.insert("VERSION", VERSION.as_bytes().to_vec()); - - let last_version = fs::read_to_string(data_dir.join("VERSION")).unwrap_or_default(); - let out_of_date = VERSION != last_version; - - for (path, bytes) in assets { - let path = data_dir.join(path); - let parent_path = path.parent().unwrap(); - fs::create_dir_all(parent_path).unwrap(); - set_permissions(parent_path).unwrap(); - if out_of_date || !path.exists() { - fs::write(path, bytes).expect("Failed to install default assets!"); - } - } - } -} - -#[cfg(not(test))] -/// Goes through a predefined list and checks for an already -/// existing config directory, returns the first match -pub fn find_default_config_dir() -> Option<PathBuf> { - default_config_dirs() - .into_iter() - .filter(|p| p.is_some()) - .find(|p| p.clone().unwrap().exists()) - .flatten() -} - -#[cfg(test)] -pub fn find_default_config_dir() -> Option<PathBuf> { - None -} - -/// Order in which config directories are checked -fn default_config_dirs() -> Vec<Option<PathBuf>> { - vec![ - home_config_dir(), - Some(xdg_config_dir()), - Some(Path::new(SYSTEM_DEFAULT_CONFIG_DIR).to_path_buf()), - ] -} - -/// Looks for an existing dir, uses that, else returns a -/// dir matching the config spec. -pub fn get_default_data_dir() -> PathBuf { - vec![ - xdg_data_dir(), - Path::new(SYSTEM_DEFAULT_DATA_DIR_PREFIX).join("share/zellij"), - ] - .into_iter() - .find(|p| p.exists()) - .unwrap_or_else(xdg_data_dir) -} - -pub fn xdg_config_dir() -> PathBuf { - ZELLIJ_PROJ_DIR.config_dir().to_owned() -} - -pub fn xdg_data_dir() -> PathBuf { - ZELLIJ_PROJ_DIR.data_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_LOCATION); - 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) -} - -#[derive(Debug, Default, Clone, StructOpt, Serialize, Deserialize)] -pub struct Setup { - /// Dump the default configuration file to stdout - #[structopt(long)] - pub dump_config: bool, - /// Disables loading of configuration file at default location, - /// loads the defaults that zellij ships with - #[structopt(long)] - pub clean: bool, - /// Checks the configuration of zellij and displays - /// currently used directories - #[structopt(long)] - pub check: bool, - - #[structopt(long)] - pub generate_completion: Option<String>, -} - -impl Setup { - /// Entrypoint from main - pub fn from_cli(&self, opts: CliArgs) -> std::io::Result<()> { - if self.dump_config { - dump_default_config()?; - } - - if self.check { - Setup::check_defaults_config(opts)?; - } - - if let Some(shell) = &self.generate_completion { - Self::generate_completion(shell.into()); - } - - Ok(()) - } - - pub fn check_defaults_config(opts: CliArgs) -> std::io::Result<()> { - let data_dir = opts.data_dir.unwrap_or_else(get_default_data_dir); - let config_dir = opts.config_dir.or_else(find_default_config_dir); - let plugin_dir = data_dir.join("plugins"); - let layout_dir = data_dir.join("layouts"); - let system_data_dir = PathBuf::from(SYSTEM_DEFAULT_DATA_DIR_PREFIX).join("share/zellij"); - let config_file = opts - .config - .or_else(|| config_dir.clone().map(|p| p.join(CONFIG_NAME))); - - let mut message = String::new(); - - message.push_str(&format!("[Version]: {:?}\n", VERSION)); - if let Some(config_dir) = config_dir { - message.push_str(&format!("[CONFIG DIR]: {:?}\n", config_dir)); - } else { - message.push_str(&"[CONFIG DIR]: Not Found\n"); - let mut default_config_dirs = default_config_dirs() - .iter() - .filter_map(|p| p.clone()) - .collect::<Vec<PathBuf>>(); - default_config_dirs.dedup(); - message.push_str( - &" On your system zellij looks in the following config directories by default:\n", - ); - for dir in default_config_dirs { - message.push_str(&format!(" {:?}\n", dir)); - } - } - if let Some(config_file) = config_file { - use crate::common::input::config::Config; - message.push_str(&format!("[CONFIG FILE]: {:?}\n", config_file)); - match Config::new(&config_file) { - Ok(_) => message.push_str(&"[CONFIG FILE]: Well defined.\n"), - Err(e) => message.push_str(&format!("[CONFIG ERROR]: {}\n", e)), - } - } else { - message.push_str(&"[CONFIG FILE]: Not Found\n"); - message.push_str(&format!( - " By default zellij looks for a file called [{}] in the configuration directory\n", - CONFIG_NAME - )); - } - message.push_str(&format!("[DATA DIR]: {:?}\n", data_dir)); - message.push_str(&format!("[PLUGIN DIR]: {:?}\n", plugin_dir)); - message.push_str(&format!("[LAYOUT DIR]: {:?}\n", layout_dir)); - message.push_str(&format!("[SYSTEM DATA DIR]: {:?}\n", system_data_dir)); - - message.push_str(&format!("[ARROW SEPARATOR]: {}\n", ARROW_SEPARATOR)); - message.push_str(&" Is the [ARROW_SEPARATOR] displayed correctly?\n"); - message.push_str(&" If not you may want to either start zellij with a compatible mode 'zellij options --simple-ui'\n"); - message.push_str(&" Or check the font that is in use:\n https://zellij.dev/documentation/compatibility.html#the-status-bar-fonts-dont-render-correctly\n"); - - message.push_str(&format!("[FEATURES]: {:?}\n", FEATURES)); - message.push_str(&"[DOCUMENTATION]: zellij.dev/documentation/\n"); - - std::io::stdout().write_all(message.as_bytes())?; - - Ok(()) - } - fn generate_completion(shell: String) { - let shell = match shell.as_ref() { - "bash" => structopt::clap::Shell::Bash, - "fish" => structopt::clap::Shell::Fish, - "zsh" => structopt::clap::Shell::Zsh, - "powerShell" => structopt::clap::Shell::PowerShell, - "elvish" => structopt::clap::Shell::Elvish, - other => { - eprintln!("Unsupported shell: {}", other); - std::process::exit(1); - } - }; - let mut out = std::io::stdout(); - CliArgs::clap().gen_completions_to("zellij", shell, &mut out); - } -} diff --git a/src/common/thread_bus.rs b/src/common/thread_bus.rs deleted file mode 100644 index 8827d068..00000000 --- a/src/common/thread_bus.rs +++ /dev/null @@ -1,142 +0,0 @@ -//! 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 deleted file mode 100644 index 42843eb8..00000000 --- a/src/common/utils/consts.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! Zellij program-wide constants. - -use crate::os_input_output::set_permissions; -use directories_next::ProjectDirs; -use lazy_static::lazy_static; -use nix::unistd::Uid; -use std::path::PathBuf; -use std::{env, fs}; - -pub const ZELLIJ_CONFIG_FILE_ENV: &str = "ZELLIJ_CONFIG_FILE"; -pub const ZELLIJ_CONFIG_DIR_ENV: &str = "ZELLIJ_CONFIG_DIR"; -pub const VERSION: &str = env!("CARGO_PKG_VERSION"); - -pub const SYSTEM_DEFAULT_CONFIG_DIR: &str = "/etc/zellij"; -pub const SYSTEM_DEFAULT_DATA_DIR_PREFIX: &str = system_default_data_dir(); - -const fn system_default_data_dir() -> &'static str { - if let Some(data_dir) = std::option_env!("PREFIX") { - data_dir - } else { - &"/usr" - } -} - -lazy_static! { - static ref UID: Uid = Uid::current(); - pub static ref SESSION_NAME: String = names::Generator::default().next().unwrap(); - pub static ref ZELLIJ_PROJ_DIR: ProjectDirs = - ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap(); - pub static ref ZELLIJ_IPC_PIPE: PathBuf = { - let mut ipc_dir = env::var("ZELLIJ_SOCKET_DIR").map_or_else( - |_| { - ZELLIJ_PROJ_DIR - .runtime_dir() - .map_or_else(|| ZELLIJ_TMP_DIR.clone(), |p| p.to_owned()) - }, - PathBuf::from, - ); - ipc_dir.push(VERSION); - fs::create_dir_all(&ipc_dir).unwrap(); - set_permissions(&ipc_dir).unwrap(); - ipc_dir.push(&*SESSION_NAME); - ipc_dir - }; - pub static ref ZELLIJ_TMP_DIR: PathBuf = - PathBuf::from("/tmp/zellij-".to_string() + &format!("{}", *UID)); - pub static ref ZELLIJ_TMP_LOG_DIR: PathBuf = ZELLIJ_TMP_DIR.join("zellij-log"); - pub static ref ZELLIJ_TMP_LOG_FILE: PathBuf = ZELLIJ_TMP_LOG_DIR.join("log.txt"); -} - -pub const FEATURES: &[&str] = &[ - #[cfg(feature = "disable_automatic_asset_installation")] - "disable_automatic_asset_installation", -]; diff --git a/src/common/utils/logging.rs b/src/common/utils/logging.rs deleted file mode 100644 index 0dd382d6..00000000 --- a/src/common/utils/logging.rs +++ /dev/null @@ -1,85 +0,0 @@ -//! Zellij logging utility functions. - -use std::{ - fs, - io::{self, prelude::*}, - os::unix::io::RawFd, - path::{Path, PathBuf}, -}; - -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) -> 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<()> { - let result = if let Err(e) = fs::create_dir(dir_name) { - if e.kind() == std::io::ErrorKind::AlreadyExists { - Ok(()) - } else { - Err(e) - } - } else { - Ok(()) - }; - if result.is_ok() { - set_permissions(dir_name)?; - } - result -} - -pub fn debug_log_to_file(mut message: String) -> io::Result<()> { - message.push('\n'); - debug_log_to_file_without_newline(message) -} - -pub fn debug_log_to_file_without_newline(message: String) -> io::Result<()> { - atomic_create_file(&*ZELLIJ_TMP_LOG_FILE)?; - let mut file = fs::OpenOptions::new() - .append(true) - .open(&*ZELLIJ_TMP_LOG_FILE)?; - file.write_all(message.as_bytes()) -} - -pub fn _debug_log_to_file_pid_3(message: String, pid: RawFd) -> io::Result<()> { - if pid == 3 { - debug_log_to_file(message) - } else { - Ok(()) - } -} - -pub fn _delete_log_file() -> io::Result<()> { - if fs::metadata(&*ZELLIJ_TMP_LOG_FILE).is_ok() { - fs::remove_file(&*ZELLIJ_TMP_LOG_FILE) - } else { - Ok(()) - } -} - -pub fn _delete_log_dir() -> io::Result<()> { - if fs::metadata(&*ZELLIJ_TMP_LOG_DIR).is_ok() { - fs::remove_dir_all(&*ZELLIJ_TMP_LOG_DIR) - } else { - Ok(()) - } -} - -pub fn debug_to_file(message: u8, pid: RawFd) -> io::Result<()> { - let mut path = PathBuf::new(); - path.push(&*ZELLIJ_TMP_LOG_DIR); - path.push(format!("zellij-{}.log", pid.to_string())); - - let mut file = fs::OpenOptions::new() - .append(true) - .create(true) - .open(&path)?; - set_permissions(&path)?; - file.write_all(&[message]) -} diff --git a/src/common/utils/mod.rs b/src/common/utils/mod.rs deleted file mode 100644 index 1cb4df27..00000000 --- a/src/common/utils/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! Zellij utilities. - -pub mod consts; -pub mod logging; -pub mod shared; diff --git a/src/common/utils/shared.rs b/src/common/utils/shared.rs deleted file mode 100644 index 26a2f90a..00000000 --- a/src/common/utils/shared.rs +++ /dev/null @@ -1,108 +0,0 @@ -//! Some general utility functions. - -use std::{iter, str::from_utf8}; - -use strip_ansi_escapes::strip; - -use colors_transform::{Color, Rgb}; -use zellij_tile::data::{Palette, PaletteColor, PaletteSource, Theme}; - -fn ansi_len(s: &str) -> usize { - from_utf8(&strip(s.as_bytes()).unwrap()) - .unwrap() - .chars() - .count() -} - -pub fn adjust_to_size(s: &str, rows: usize, columns: usize) -> String { - s.lines() - .map(|l| { - let actual_len = ansi_len(l); - if actual_len > columns { - let mut line = String::from(l); - line.truncate(columns); - line - } else { - [l, &str::repeat(" ", columns - ansi_len(l))].concat() - } - }) - .chain(iter::repeat(str::repeat(" ", columns))) - .take(rows) - .collect::<Vec<_>>() - .join("\n\r") -} - -// Colors -pub mod colors { - pub const WHITE: u8 = 255; - pub const GREEN: u8 = 154; - pub const GRAY: u8 = 238; - pub const BRIGHT_GRAY: u8 = 245; - pub const RED: u8 = 88; - pub const ORANGE: u8 = 166; - pub const BLACK: u8 = 16; -} - -pub fn _hex_to_rgb(hex: &str) -> (u8, u8, u8) { - let rgb = Rgb::from_hex_str(hex) - .expect("The passed argument must be a valid hex color") - .as_tuple(); - (rgb.0 as u8, rgb.1 as u8, rgb.2 as u8) -} - -pub fn default_palette() -> Palette { - Palette { - source: PaletteSource::Default, - theme: Theme::Dark, - fg: PaletteColor::EightBit(colors::BRIGHT_GRAY), - bg: PaletteColor::EightBit(colors::GRAY), - black: PaletteColor::EightBit(colors::BLACK), - red: PaletteColor::EightBit(colors::RED), - green: PaletteColor::EightBit(colors::GREEN), - yellow: PaletteColor::EightBit(colors::GRAY), - blue: PaletteColor::EightBit(colors::GRAY), - magenta: PaletteColor::EightBit(colors::GRAY), - cyan: PaletteColor::EightBit(colors::GRAY), - white: PaletteColor::EightBit(colors::WHITE), - orange: PaletteColor::EightBit(colors::ORANGE), - } -} - -// Dark magic -pub fn _detect_theme(bg: PaletteColor) -> Theme { - match bg { - PaletteColor::Rgb((r, g, b)) => { - // HSP, P stands for perceived brightness - let hsp: f64 = (0.299 * (r as f64 * r as f64) - + 0.587 * (g as f64 * g as f64) - + 0.114 * (b as f64 * b as f64)) - .sqrt(); - match hsp > 127.5 { - true => Theme::Light, - false => Theme::Dark, - } - } - _ => Theme::Dark, - } -} - -// (this was shamelessly copied from alacritty) -// -// This returns the current terminal version as a unique number based on the -// semver version. The different versions are padded to ensure that a higher semver version will -// always report a higher version number. -pub fn version_number(mut version: &str) -> usize { - if let Some(separator) = version.rfind('-') { - version = &version[..separator]; - } - - let mut version_number = 0; - - let semver_versions = version.split('.'); - for (i, semver_version) in semver_versions.rev().enumerate() { - let semver_number = semver_version.parse::<usize>().unwrap_or(0); - version_number += usize::pow(100, i as u32) * semver_number; - } - - version_number -} diff --git a/src/common/wasm_vm.rs b/src/common/wasm_vm.rs deleted file mode 100644 index 51eb4ed8..00000000 --- a/src/common/wasm_vm.rs +++ /dev/null @@ -1,267 +0,0 @@ -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 wasmer::{ - imports, ChainableNamedResolver, Function, ImportObject, Instance, Module, Store, Value, - WasmerEnv, -}; -use wasmer_wasi::{Pipe, WasiEnv, WasiState}; -use zellij_tile::data::{Event, EventType, PluginIds}; - -use crate::common::errors::ContextType; -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 { - Load(Sender<u32>, PathBuf), - Update(Option<u32>, Event), // Focused plugin / broadcast, event data - Render(Sender<String>, u32, usize, usize), // String buffer, plugin id, rows, cols - Unload(u32), - Exit, -} - -#[derive(WasmerEnv, Clone)] -pub struct PluginEnv { - pub plugin_id: u32, - 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((&event).into())); - 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 { - 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> = 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> = wasi_read_object(&plugin_env.wasi_env); - subscriptions.retain(|k| !old.contains(k)); -} - -fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) { - let selectable = selectable != 0; - plugin_env - .senders - .send_to_screen(ScreenInstruction::SetSelectable( - PaneId::Plugin(plugin_env.plugin_id), - selectable, - )) - .unwrap() -} - -fn host_set_max_height(plugin_env: &PluginEnv, max_height: i32) { - let max_height = max_height as usize; - plugin_env - .senders - .send_to_screen(ScreenInstruction::SetMaxHeight( - PaneId::Plugin(plugin_env.plugin_id), - max_height, - )) - .unwrap() -} - -fn host_set_invisible_borders(plugin_env: &PluginEnv, invisible_borders: i32) { - let invisible_borders = invisible_borders != 0; - plugin_env - .senders - .send_to_screen(ScreenInstruction::SetInvisibleBorders( - PaneId::Plugin(plugin_env.plugin_id), - invisible_borders, - )) - .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 - .senders - .send_to_pty(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.senders.to_plugin.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 - .unwrap() - .send(PluginInstruction::Update( - update_target, - Event::Timer(elapsed_time), - )) - .unwrap(); - }); -} - -// Helper Functions --------------------------------------------------------------------------------------------------- - -// FIXME: Unwrap city -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(); - wasi_file.read_to_string(&mut buf).unwrap(); - buf -} - -pub fn wasi_write_string(wasi_env: &WasiEnv, buf: &str) { - let mut state = wasi_env.state(); - 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() -} |
