aboutsummaryrefslogtreecommitdiffstats
path: root/src/common
diff options
context:
space:
mode:
authorKunal Mohan <kunalmohan99@gmail.com>2021-05-16 16:42:50 +0530
committerKunal Mohan <kunalmohan99@gmail.com>2021-05-16 21:41:56 +0530
commit2038947a14d32d89da2bdbcaa1381c3015ac9c7a (patch)
tree5a332470e32b380816098594a89d3c3db1f6eb53 /src/common
parenthandle error on termios initialization (#511) (diff)
downloadsl0ppij-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.rs33
-rw-r--r--src/common/errors.rs393
-rw-r--r--src/common/input/actions.rs64
-rw-r--r--src/common/input/config.rs218
-rw-r--r--src/common/input/handler.rs268
-rw-r--r--src/common/input/keybinds.rs301
-rw-r--r--src/common/input/mod.rs7
-rw-r--r--src/common/input/options.rs45
-rw-r--r--src/common/input/unit/keybinds_test.rs801
-rw-r--r--src/common/ipc.rs131
-rw-r--r--src/common/mod.rs14
-rw-r--r--src/common/os_input_output.rs414
-rw-r--r--src/common/pty.rs293
-rw-r--r--src/common/screen.rs595
-rw-r--r--src/common/setup.rs240
-rw-r--r--src/common/thread_bus.rs142
-rw-r--r--src/common/utils/consts.rs54
-rw-r--r--src/common/utils/logging.rs85
-rw-r--r--src/common/utils/mod.rs5
-rw-r--r--src/common/utils/shared.rs108
-rw-r--r--src/common/wasm_vm.rs267
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()
-}