From 23450a708c48d4f0bf65db81425168734fc85487 Mon Sep 17 00:00:00 2001 From: henil Date: Fri, 7 May 2021 10:27:45 +0530 Subject: [PATCH 01/44] fix(build): Make sure `assets/man` folder exists before building manpage. --- Makefile.toml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile.toml b/Makefile.toml index db68a76f..167afc34 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -94,15 +94,19 @@ end ''' [tasks.manpage] +workspace = false description = "Use mandown crate to create or update man entry from docs/MANPAGES.md" -script = "mandown docs/MANPAGE.md ZELLIJ 1 > assets/man/zellij.1" +script = ''' +root_dir=${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY} +mkdir -p ${root_dir}/assets/man +mandown ${root_dir}/docs/MANPAGE.md ZELLIJ 1 > ${root_dir}/assets/man/zellij.1 +''' dependencies = ["install-mandown"] [tasks.install-mandown] command = "cargo" args = ["install", "mandown"] - # CI Releasing Zellij [tasks.ci-build-release] workspace = false From 79a4f76e93dd25c4eb71af18885b4c043807b78c Mon Sep 17 00:00:00 2001 From: a-kenji Date: Fri, 7 May 2021 10:09:26 +0200 Subject: [PATCH 02/44] Add more Functionality to Unbind The default keybinds can be unbound either for a specific mode, or for every mode. It supports either a list of `keybinds`, or a bool indicating that every keybind should be unbound: ``` keybinds: unbind: true ``` Will unbind every default binding. ``` keybinds: unbind: [ Ctrl: 'p'] ``` Will unbind every default `^P` binding for each mode. ``` keybinds: normal: - unbind: true ``` Will unbind every default keybind for the `normal` mode. ``` keybinds: normal: - unbind: [ Alt: 'n', Ctrl: 'g'] ``` Will unbind every default keybind for `n` and `^g` for the `normal` mode. --- docs/MANPAGE.md | 28 ++ src/client/layout.rs | 2 +- src/common/input/keybinds.rs | 131 ++++- src/common/input/unit/keybinds_test.rs | 653 +++++++++++++++++++++++++ 4 files changed, 800 insertions(+), 14 deletions(-) diff --git a/docs/MANPAGE.md b/docs/MANPAGE.md index 6ef937c1..8f9f45b5 100644 --- a/docs/MANPAGE.md +++ b/docs/MANPAGE.md @@ -101,6 +101,34 @@ where "normal" stands for a mode name (see MODES section), "action" part specifies the actions to be executed by Zellij (see ACTIONS section) and "key" is used to list keys or key combinations bound to given actions (see KEYS). +The default keybinds can be unbound either for a specific mode, or for every mode. +It supports either a list of `keybinds`, or a bool indicating that every keybind +should be unbound: + +``` +keybinds: + unbind: true +``` +Will unbind every default binding. + +``` +keybinds: + unbind: [ Ctrl: 'p'] +``` +Will unbind every default `^P` binding for each mode. +``` +keybinds: + normal: + - unbind: true +``` +Will unbind every default keybind for the `normal` mode. +``` +keybinds: + normal: + - unbind: [ Alt: 'n', Ctrl: 'g'] +``` +Will unbind every default keybind for `n` and `^g` for the `normal` mode. + ACTIONS ------- diff --git a/src/client/layout.rs b/src/client/layout.rs index ca1adb4d..3648ece9 100644 --- a/src/client/layout.rs +++ b/src/client/layout.rs @@ -206,7 +206,7 @@ impl Layout { } // It wants to use Path here, but that doesn't compile. - #[warn(clippy::ptr_arg)] + #[allow(clippy::ptr_arg)] pub fn from_defaults(layout_path: &PathBuf, data_dir: &Path) -> Self { Self::new( &data_dir diff --git a/src/common/input/keybinds.rs b/src/common/input/keybinds.rs index 9842fbe2..20f2de89 100644 --- a/src/common/input/keybinds.rs +++ b/src/common/input/keybinds.rs @@ -30,7 +30,14 @@ pub struct KeybindsFromYaml { enum KeyActionUnbind { KeyAction(KeyActionFromYaml), // TODO: use the enum - //Unbind(UnbindFromYaml), + Unbind(UnbindFromYaml), +} + +/// Intermediate struct used for deserialisation +#[derive(Clone, Debug, PartialEq, Deserialize)] +struct KeyActionUnbindFromYaml { + keybinds: Vec, + unbind: Unbind, } /// Intermediate struct used for deserialisation @@ -41,7 +48,7 @@ pub struct KeyActionFromYaml { } /// Intermediate struct used for deserialisation -#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize)] +#[derive(Clone, Debug, PartialEq, Deserialize)] struct UnbindFromYaml { unbind: Unbind, } @@ -51,16 +58,19 @@ struct UnbindFromYaml { #[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize)] #[serde(untagged)] enum Unbind { + // This is the correct order, don't rearrange! + // Suspected Bug in the untagged macro. + // 1. Keys + Keys(Vec), + // 2. All All(bool), - // TODO@a-kenji: use the enum - //Keys(Vec), } impl Default for Keybinds { + // Use once per codepath + // TODO investigate why fn default() -> Keybinds { - config::Config::from_default_assets() - .expect("Keybinds from default assets Error") - .keybinds + Self::from_default_assets() } } @@ -69,11 +79,18 @@ impl Keybinds { Keybinds(HashMap::::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) -> Keybinds { let default_keybinds = match from_yaml.clone() { Some(keybinds) => match keybinds.unbind { Unbind::All(true) => Keybinds::new(), - Unbind::All(false) => Keybinds::default(), + Unbind::All(false) | Unbind::Keys(_) => Keybinds::unbind(keybinds), }, None => Keybinds::default(), }; @@ -85,6 +102,71 @@ impl Keybinds { } } + /// Unbinds the default keybindings in relation to their mode + fn unbind(from_yaml: KeybindsFromYaml) -> Keybinds { + let mut keybind_config = Self::new(); + let mut unbind_config: HashMap = 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 = 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) -> Keybinds { + let mut keybinds = Keybinds::new(); + + for mode in InputMode::iter() { + if let Some(unbind) = unbind.get(&mode) { + match unbind { + Unbind::All(true) => {} + Unbind::Keys(keys) => { + if let Some(defaults) = self.0.get(&mode) { + keybinds + .0 + .insert(mode, defaults.clone().unbind_keys(keys.to_vec())); + } + } + Unbind::All(false) => { + if let Some(defaults) = self.0.get(&mode) { + keybinds.0.insert(mode, defaults.clone()); + } + } + } + } else if let Some(defaults) = self.0.get(&mode) { + keybinds.0.insert(mode, defaults.clone()); + } + } + keybinds + } + /// Merges two Keybinds structs into one Keybinds struct /// `other` overrides the ModeKeybinds of `self`. fn merge_keybinds(&self, other: Keybinds) -> Keybinds { @@ -142,6 +224,15 @@ impl ModeKeybinds { merged.0.extend(other.0); merged } + + /// Remove [`Key`]'s from [`ModeKeybinds`] + fn unbind_keys(self, unbind: Vec) -> Self { + let mut keymap = self; + for key in unbind { + keymap.0.remove(&key); + } + keymap + } } impl From for Keybinds { @@ -161,30 +252,44 @@ impl From for Keybinds { } } -/// For each `Key` assigned to `Action`s, -/// map the `Action`s to the key +/// For each [`Key`] assigned to [`Action`]s, +/// map the [`Action`]s to the [`Key`] impl From for ModeKeybinds { fn from(key_action: KeyActionFromYaml) -> ModeKeybinds { - let keys = key_action.key; let actions = key_action.action; ModeKeybinds( - keys.into_iter() + key_action + .key + .into_iter() .map(|k| (k, actions.clone())) .collect::>>(), ) } } -// Currently an enum for future use impl From 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> for ModeKeybinds { + fn from(key_action_from_yaml: Vec) -> 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) diff --git a/src/common/input/unit/keybinds_test.rs b/src/common/input/unit/keybinds_test.rs index e0d78b62..4767fc15 100644 --- a/src/common/input/unit/keybinds_test.rs +++ b/src/common/input/unit/keybinds_test.rs @@ -136,6 +136,7 @@ fn toplevel_unbind_unbinds_all() { assert_eq!(keybinds_from_yaml, Keybinds::new()); } +#[test] fn no_unbind_unbinds_none() { let from_yaml = KeybindsFromYaml { unbind: Unbind::All(false), @@ -144,5 +145,657 @@ fn no_unbind_unbinds_none() { let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml)); + assert_eq!(keybinds_from_yaml, Keybinds::default()); +} + +#[test] +fn last_keybind_is_taken() { + let actions_1 = vec![Action::NoOp, Action::NewTab]; + let keyaction_1 = KeyActionFromYaml { + action: actions_1.clone(), + key: vec![Key::F(1), Key::Backspace, Key::Char('t')], + }; + let actions_2 = vec![Action::GoToTab(1)]; + let keyaction_2 = KeyActionFromYaml { + action: actions_2.clone(), + key: vec![Key::F(1), Key::Backspace, Key::Char('t')], + }; + + let mut expected = ModeKeybinds::new(); + expected.0.insert(Key::F(1), actions_2.clone()); + expected.0.insert(Key::Backspace, actions_2.clone()); + expected.0.insert(Key::Char('t'), actions_2); + + assert_eq!(expected, ModeKeybinds::from(vec![keyaction_1, keyaction_2])); +} + +#[test] +fn last_keybind_overwrites() { + let actions_1 = vec![Action::NoOp, Action::NewTab]; + let keyaction_1 = KeyActionFromYaml { + action: actions_1.clone(), + key: vec![Key::F(1), Key::Backspace, Key::Char('t')], + }; + let actions_2 = vec![Action::GoToTab(1)]; + let keyaction_2 = KeyActionFromYaml { + action: actions_2.clone(), + key: vec![Key::F(1), Key::Char('t')], + }; + + let mut expected = ModeKeybinds::new(); + expected.0.insert(Key::F(1), actions_2.clone()); + expected.0.insert(Key::Backspace, actions_1.clone()); + expected.0.insert(Key::Char('t'), actions_2); + + assert_eq!(expected, ModeKeybinds::from(vec![keyaction_1, keyaction_2])); +} + +#[test] +fn unbind_single_mode() { + let unbind = Unbind::All(true); + let unbind_from_yaml = UnbindFromYaml { unbind }; + let key_action_unbinds = vec![KeyActionUnbind::Unbind(unbind_from_yaml)]; + + let mut keys = HashMap::>::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::>::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::>::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::>::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::>::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::>::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::>::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::>::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::>::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::>::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::>::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::>::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::>::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::>::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::>::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::>::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::>::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::>::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()); +} From c7ad6d33eacd69476ca73526364a132faaae2774 Mon Sep 17 00:00:00 2001 From: a-kenji Date: Fri, 7 May 2021 10:54:35 +0200 Subject: [PATCH 03/44] !fixup Add more Functionality to Unbind * Used the unbind --- src/common/input/keybinds.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/common/input/keybinds.rs b/src/common/input/keybinds.rs index 20f2de89..823ca264 100644 --- a/src/common/input/keybinds.rs +++ b/src/common/input/keybinds.rs @@ -29,7 +29,6 @@ pub struct KeybindsFromYaml { #[serde(untagged)] enum KeyActionUnbind { KeyAction(KeyActionFromYaml), - // TODO: use the enum Unbind(UnbindFromYaml), } From 2446aec82c9446638027ced2ae3a22cc1e6e5bee Mon Sep 17 00:00:00 2001 From: Kunal Mohan Date: Fri, 7 May 2021 15:40:04 +0530 Subject: [PATCH 04/44] hotfix(logging): fix atomic_create_file and make set_permissions() return an error --- src/common/os_input_output.rs | 6 +++--- src/common/setup.rs | 2 +- src/common/utils/consts.rs | 2 +- src/common/utils/logging.rs | 14 +++++++++----- src/server/mod.rs | 2 +- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/common/os_input_output.rs b/src/common/os_input_output.rs index 5539ae70..6f891e0d 100644 --- a/src/common/os_input_output.rs +++ b/src/common/os_input_output.rs @@ -25,10 +25,10 @@ use zellij_tile::data::Palette; const UNIX_PERMISSIONS: u32 = 0o700; -pub fn set_permissions(path: &Path) { - let mut permissions = fs::metadata(path).unwrap().permissions(); +pub fn set_permissions(path: &Path) -> io::Result<()> { + let mut permissions = fs::metadata(path)?.permissions(); permissions.set_mode(UNIX_PERMISSIONS); - fs::set_permissions(path, permissions).unwrap(); + fs::set_permissions(path, permissions) } fn into_raw_mode(pid: RawFd) { diff --git a/src/common/setup.rs b/src/common/setup.rs index edfa858a..2eceba18 100644 --- a/src/common/setup.rs +++ b/src/common/setup.rs @@ -42,7 +42,7 @@ pub mod install { let path = data_dir.join(path); let parent_path = path.parent().unwrap(); fs::create_dir_all(parent_path).unwrap(); - set_permissions(parent_path); + set_permissions(parent_path).unwrap(); if out_of_date || !path.exists() { fs::write(path, bytes).expect("Failed to install default assets!"); } diff --git a/src/common/utils/consts.rs b/src/common/utils/consts.rs index a46f8243..bc8768ce 100644 --- a/src/common/utils/consts.rs +++ b/src/common/utils/consts.rs @@ -30,7 +30,7 @@ lazy_static! { ); ipc_dir.push(VERSION); fs::create_dir_all(&ipc_dir).unwrap(); - set_permissions(&ipc_dir); + set_permissions(&ipc_dir).unwrap(); ipc_dir.push(&*SESSION_NAME); ipc_dir }; diff --git a/src/common/utils/logging.rs b/src/common/utils/logging.rs index ff3fb44c..4c53e2b2 100644 --- a/src/common/utils/logging.rs +++ b/src/common/utils/logging.rs @@ -11,9 +11,13 @@ use crate::os_input_output::set_permissions; use crate::utils::consts::{ZELLIJ_TMP_LOG_DIR, ZELLIJ_TMP_LOG_FILE}; pub fn atomic_create_file(file_name: &Path) { - let _ = fs::OpenOptions::new().create(true).open(file_name); + let _ = fs::OpenOptions::new() + .append(true) + .create(true) + .open(file_name) + .unwrap(); #[cfg(not(test))] - set_permissions(file_name); + set_permissions(file_name).unwrap(); } pub fn atomic_create_dir(dir_name: &Path) -> io::Result<()> { @@ -27,7 +31,7 @@ pub fn atomic_create_dir(dir_name: &Path) -> io::Result<()> { Ok(()) }; if result.is_ok() { - set_permissions(dir_name); + set_permissions(dir_name)?; } result } @@ -41,7 +45,6 @@ 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) - .create(true) .open(&*ZELLIJ_TMP_LOG_FILE)?; file.write_all(message.as_bytes()) } @@ -78,6 +81,7 @@ pub fn debug_to_file(message: u8, pid: RawFd) -> io::Result<()> { let mut file = fs::OpenOptions::new() .append(true) .create(true) - .open(path)?; + .open(&path)?; + set_permissions(&path)?; file.write_all(&[message]) } diff --git a/src/server/mod.rs b/src/server/mod.rs index 6eb2455b..c0cf482c 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -87,7 +87,7 @@ pub fn start_server(os_input: Box) -> thread::JoinHandle<()> { move || { drop(std::fs::remove_file(&*ZELLIJ_IPC_PIPE)); let listener = LocalSocketListener::bind(&**ZELLIJ_IPC_PIPE).unwrap(); - set_permissions(&*ZELLIJ_IPC_PIPE); + set_permissions(&*ZELLIJ_IPC_PIPE).unwrap(); for stream in listener.incoming() { match stream { Ok(stream) => { From d3a38763cbfa7f9f25571e10464c3ca519b72615 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Fri, 7 May 2021 12:28:35 +0200 Subject: [PATCH 05/44] docs(changelog): update infra change --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3900dde6..91d2a4d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] * Terminal compatibility: pass vttest 8 (https://github.com/zellij-org/zellij/pull/461) * Add a Manpage (https://github.com/zellij-org/zellij/pull/455) +* Code infrastructure changes to support the upcoming session detach (https://github.com/zellij-org/zellij/pull/223) ## [0.7.0] - 2021-05-04 * Fix the tab '(Sync)' suffix in named tabs (https://github.com/zellij-org/zellij/pull/410) From 61e8d0915163e1c28fe688fa920058ded2745ade Mon Sep 17 00:00:00 2001 From: Kunal Mohan Date: Fri, 7 May 2021 16:50:58 +0530 Subject: [PATCH 06/44] hotfix(logging): let logging fail silently while testing --- src/client/panes/grid.rs | 4 +++- src/common/utils/logging.rs | 10 ++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/client/panes/grid.rs b/src/client/panes/grid.rs index e893fd32..9d924220 100644 --- a/src/client/panes/grid.rs +++ b/src/client/panes/grid.rs @@ -1261,7 +1261,9 @@ impl vte::Perform for Grid { self.insert_character_at_cursor_position(EMPTY_TERMINAL_CHARACTER); } } else { - let _ = debug_log_to_file(format!("Unhandled csi: {}->{:?}", c, params)); + let result = debug_log_to_file(format!("Unhandled csi: {}->{:?}", c, params)); + #[cfg(not(test))] + result.unwrap(); } } diff --git a/src/common/utils/logging.rs b/src/common/utils/logging.rs index 4c53e2b2..0dd382d6 100644 --- a/src/common/utils/logging.rs +++ b/src/common/utils/logging.rs @@ -10,14 +10,12 @@ use std::{ use crate::os_input_output::set_permissions; use crate::utils::consts::{ZELLIJ_TMP_LOG_DIR, ZELLIJ_TMP_LOG_FILE}; -pub fn atomic_create_file(file_name: &Path) { +pub fn atomic_create_file(file_name: &Path) -> io::Result<()> { let _ = fs::OpenOptions::new() .append(true) .create(true) - .open(file_name) - .unwrap(); - #[cfg(not(test))] - set_permissions(file_name).unwrap(); + .open(file_name)?; + set_permissions(file_name) } pub fn atomic_create_dir(dir_name: &Path) -> io::Result<()> { @@ -42,7 +40,7 @@ pub fn debug_log_to_file(mut message: String) -> io::Result<()> { } pub fn debug_log_to_file_without_newline(message: String) -> io::Result<()> { - atomic_create_file(&*ZELLIJ_TMP_LOG_FILE); + atomic_create_file(&*ZELLIJ_TMP_LOG_FILE)?; let mut file = fs::OpenOptions::new() .append(true) .open(&*ZELLIJ_TMP_LOG_FILE)?; From ad9576dd95ad943c43d6103772f913df67491f4f Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Fri, 7 May 2021 14:45:13 +0200 Subject: [PATCH 07/44] chore(version): bump development version --- CHANGELOG.md | 2 ++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91d2a4d4..da403217 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] + +## [0.8.0] - 2021-05-07 * Terminal compatibility: pass vttest 8 (https://github.com/zellij-org/zellij/pull/461) * Add a Manpage (https://github.com/zellij-org/zellij/pull/455) * Code infrastructure changes to support the upcoming session detach (https://github.com/zellij-org/zellij/pull/223) diff --git a/Cargo.lock b/Cargo.lock index b1fbaf3e..f499d1b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2254,7 +2254,7 @@ dependencies = [ [[package]] name = "zellij" -version = "0.8.0" +version = "0.9.0" dependencies = [ "ansi_term 0.12.1", "async-std", diff --git a/Cargo.toml b/Cargo.toml index f59b1e8b..df9b199d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zellij" -version = "0.8.0" +version = "0.9.0" authors = ["Aram Drevekenin "] edition = "2018" description = "A terminal workspace with batteries included" From 08f80c52894f386dd26adda1be568c572cc522dc Mon Sep 17 00:00:00 2001 From: a-kenji Date: Fri, 7 May 2021 15:08:47 +0200 Subject: [PATCH 08/44] docs(changelog): Add more Functionality to Unbind #468 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index da403217..45ce5078 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] +* Add more functionality to unbinding the default keybindings (https://github.com/zellij-org/zellij/pull/468) ## [0.8.0] - 2021-05-07 * Terminal compatibility: pass vttest 8 (https://github.com/zellij-org/zellij/pull/461) From 62991f138bfa35b739234fd89e252b16c9c1cc97 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Fri, 7 May 2021 15:43:17 +0200 Subject: [PATCH 09/44] docs(issue): update issue template to new folder location --- .github/ISSUE_TEMPLATE/bug_report.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 064af282..d3b68127 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -9,13 +9,13 @@ You can erase any parts of this template not applicable to your issue. ## In Case of Graphical, or Performance Issues Please: -1. Delete the contents of `/tmp/zellij/zellij-log`. +1. Delete the contents of `/tmp/zellij-1000/zellij-log`. 2. Run `zellij --debug` and then recreate your issue. 3. Quit Zellij immediately with ctrl-q (your bug should ideally still be visible on screen) Please attach the files that were created in -`/tmp/zellij/zellij-log/` +`/tmp/zellij-1000/zellij-log/` To the extent you are comfortable with. From 632a7a3209f16da1ff7330f8465106959e996d75 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Fri, 7 May 2021 16:03:45 +0200 Subject: [PATCH 10/44] fix(compatibility): upgrade vte to support csi subparameters (#469) * fix(compatibility): upgrade vte to support csi subparameters * style(fmt): rustfmt and clippy --- Cargo.lock | 6 +- Cargo.toml | 2 +- src/client/panes/grid.rs | 208 ++++++-------- src/client/panes/terminal_character.rs | 263 +++++++++--------- ...ration__compatibility__htop_scrolling.snap | 2 +- 5 files changed, 218 insertions(+), 263 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f499d1b8..4f7255df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1871,9 +1871,9 @@ dependencies = [ [[package]] name = "vte" -version = "0.8.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96cc8a191608603611e78c6ec11dafef37e3cca0775aeef1931824753e81711d" +checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" dependencies = [ "arrayvec", "utf8parse 0.2.0", @@ -2282,7 +2282,7 @@ dependencies = [ "termios", "unicode-truncate", "unicode-width", - "vte 0.8.0", + "vte 0.10.1", "wasmer", "wasmer-wasi", "zellij-tile", diff --git a/Cargo.toml b/Cargo.toml index df9b199d..0e9136c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ termion = "1.5.0" termios = "0.3" unicode-truncate = "0.2.0" unicode-width = "0.1.8" -vte = "0.8.0" +vte = "0.10.1" strum = "0.20.0" lazy_static = "1.4.0" wasmer = "1.0.0" diff --git a/src/client/panes/grid.rs b/src/client/panes/grid.rs index 9d924220..824c8384 100644 --- a/src/client/panes/grid.rs +++ b/src/client/panes/grid.rs @@ -4,6 +4,8 @@ use std::{ fmt::{self, Debug, Formatter}, }; +use vte::{Params, Perform}; + const TABSTOP_WIDTH: usize = 8; // TODO: is this always right? const SCROLL_BACK: usize = 10_000; @@ -811,7 +813,8 @@ impl Grid { pub fn show_cursor(&mut self) { self.cursor.is_hidden = false; } - pub fn set_scroll_region(&mut self, top_line_index: usize, bottom_line_index: usize) { + pub fn set_scroll_region(&mut self, top_line_index: usize, bottom_line_index: Option) { + let bottom_line_index = bottom_line_index.unwrap_or(self.height); self.scroll_region = Some((top_line_index, bottom_line_index)); } pub fn clear_scroll_region(&mut self) { @@ -925,7 +928,7 @@ impl Grid { } } -impl vte::Perform for Grid { +impl Perform for Grid { fn print(&mut self, c: char) { let c = self.cursor.charsets[self.active_charset].map(c); // apparently, building TerminalCharacter like this without a "new" method @@ -966,7 +969,7 @@ impl vte::Perform for Grid { } } - fn hook(&mut self, _params: &[i64], _intermediates: &[u8], _ignore: bool, _c: char) { + fn hook(&mut self, _params: &Params, _intermediates: &[u8], _ignore: bool, _c: char) { // TBD } @@ -982,80 +985,71 @@ impl vte::Perform for Grid { // TBD } - fn csi_dispatch(&mut self, params: &[i64], _intermediates: &[u8], _ignore: bool, c: char) { + fn csi_dispatch(&mut self, params: &Params, _intermediates: &[u8], _ignore: bool, c: char) { + let mut params_iter = params.iter(); + let mut next_param_or = |default: u16| { + params_iter + .next() + .map(|param| param[0]) + .filter(|¶m| param != 0) + .unwrap_or(default) as usize + }; if c == 'm' { self.cursor .pending_styles - .add_style_from_ansi_params(params); + .add_style_from_ansi_params(&mut params_iter); } else if c == 'C' { // move cursor forward - let move_by = if params[0] == 0 { - 1 - } else { - params[0] as usize - }; + let move_by = next_param_or(1); self.move_cursor_forward_until_edge(move_by); } else if c == 'K' { // clear line (0 => right, 1 => left, 2 => all) - if params[0] == 0 { - let mut char_to_replace = EMPTY_TERMINAL_CHARACTER; - char_to_replace.styles = self.cursor.pending_styles; - self.replace_characters_in_line_after_cursor(char_to_replace); - } else if params[0] == 1 { - let mut char_to_replace = EMPTY_TERMINAL_CHARACTER; - char_to_replace.styles = self.cursor.pending_styles; - self.replace_characters_in_line_before_cursor(char_to_replace); - } else if params[0] == 2 { - self.clear_cursor_line(); - } + if let Some(clear_type) = params_iter.next().map(|param| param[0]) { + if clear_type == 0 { + let mut char_to_replace = EMPTY_TERMINAL_CHARACTER; + char_to_replace.styles = self.cursor.pending_styles; + self.replace_characters_in_line_after_cursor(char_to_replace); + } else if clear_type == 1 { + let mut char_to_replace = EMPTY_TERMINAL_CHARACTER; + char_to_replace.styles = self.cursor.pending_styles; + self.replace_characters_in_line_before_cursor(char_to_replace); + } else if clear_type == 2 { + self.clear_cursor_line(); + } + }; } else if c == 'J' { // clear all (0 => below, 1 => above, 2 => all, 3 => saved) let mut char_to_replace = EMPTY_TERMINAL_CHARACTER; char_to_replace.styles = self.cursor.pending_styles; - if params[0] == 0 { - self.clear_all_after_cursor(char_to_replace); - } else if params[0] == 1 { - self.clear_all_before_cursor(char_to_replace); - } else if params[0] == 2 { - self.clear_all(char_to_replace); - } + + if let Some(clear_type) = params_iter.next().map(|param| param[0]) { + if clear_type == 0 { + self.clear_all_after_cursor(char_to_replace); + } else if clear_type == 1 { + self.clear_all_before_cursor(char_to_replace); + } else if clear_type == 2 { + self.clear_all(char_to_replace); + } + }; // TODO: implement 1 } else if c == 'H' || c == 'f' { // goto row/col // we subtract 1 from the row/column because these are 1 indexed - // (except when they are 0, in which case they should be 1 - // don't look at me, I don't make the rules) - let (row, col) = if params.len() == 1 { - if params[0] == 0 { - (0, params[0] as usize) - } else { - ((params[0] as usize).saturating_sub(1), params[0] as usize) - } - } else if params[0] == 0 { - (0, (params[1] as usize).saturating_sub(1)) - } else { - ( - (params[0] as usize).saturating_sub(1), - (params[1] as usize).saturating_sub(1), - ) - }; + let row = next_param_or(1).saturating_sub(1); + let col = next_param_or(1).saturating_sub(1); let pad_character = EMPTY_TERMINAL_CHARACTER; self.move_cursor_to(col, row, pad_character); } else if c == 'A' { // move cursor up until edge of screen - let move_up_count = if params[0] == 0 { 1 } else { params[0] }; + let move_up_count = next_param_or(1); self.move_cursor_up(move_up_count as usize); } else if c == 'B' { // move cursor down until edge of screen - let move_down_count = if params[0] == 0 { 1 } else { params[0] }; + let move_down_count = next_param_or(1); let pad_character = EMPTY_TERMINAL_CHARACTER; self.move_cursor_down(move_down_count as usize, pad_character); } else if c == 'D' { - let move_back_count = if params[0] == 0 { - 1 - } else { - params[0] as usize - }; + let move_back_count = next_param_or(1); self.move_cursor_back(move_back_count); } else if c == 'l' { let first_intermediate_is_questionmark = match _intermediates.get(0) { @@ -1064,8 +1058,8 @@ impl vte::Perform for Grid { _ => false, }; if first_intermediate_is_questionmark { - match params.get(0) { - Some(&1049) => { + match params_iter.next().map(|param| param[0]) { + Some(1049) => { if let Some(( alternative_lines_above, alternative_viewport, @@ -1081,29 +1075,29 @@ impl vte::Perform for Grid { self.change_size(self.height, self.width); // the alternative_viewport might have been of a different size... self.mark_for_rerender(); } - Some(&25) => { + Some(25) => { self.hide_cursor(); self.mark_for_rerender(); } - Some(&1) => { + Some(1) => { self.cursor_key_mode = false; } - Some(&3) => { + Some(3) => { // DECCOLM - only side effects self.scroll_region = None; self.clear_all(EMPTY_TERMINAL_CHARACTER); self.cursor.x = 0; self.cursor.y = 0; } - Some(&6) => { + Some(6) => { self.erasure_mode = false; } - Some(&7) => { + Some(7) => { self.disable_linewrap = true; } _ => {} }; - } else if let Some(&4) = params.get(0) { + } else if let Some(4) = params_iter.next().map(|param| param[0]) { self.insert_mode = false; } } else if c == 'h' { @@ -1113,12 +1107,12 @@ impl vte::Perform for Grid { _ => false, }; if first_intermediate_is_questionmark { - match params.get(0) { - Some(&25) => { + match params_iter.next().map(|param| param[0]) { + Some(25) => { self.show_cursor(); self.mark_for_rerender(); } - Some(&1049) => { + Some(1049) => { let current_lines_above = std::mem::replace( &mut self.lines_above, VecDeque::with_capacity(SCROLL_BACK), @@ -1130,99 +1124,75 @@ impl vte::Perform for Grid { Some((current_lines_above, current_viewport, current_cursor)); self.clear_viewport_before_rendering = true; } - Some(&1) => { + Some(1) => { self.cursor_key_mode = true; } - Some(&3) => { + Some(3) => { // DECCOLM - only side effects self.scroll_region = None; self.clear_all(EMPTY_TERMINAL_CHARACTER); self.cursor.x = 0; self.cursor.y = 0; } - Some(&6) => { + Some(6) => { self.erasure_mode = true; } - Some(&7) => { + Some(7) => { self.disable_linewrap = false; } _ => {} }; - } else if let Some(&4) = params.get(0) { + } else if let Some(4) = params_iter.next().map(|param| param[0]) { self.insert_mode = true; } } else if c == 'r' { if params.len() > 1 { - // minus 1 because these are 1 indexed - let top_line_index = (params[0] as usize).saturating_sub(1); - let bottom_line_index = (params[1] as usize).saturating_sub(1); - self.set_scroll_region(top_line_index, bottom_line_index); + let top = (next_param_or(1) as usize).saturating_sub(1); + let bottom = params_iter + .next() + .map(|param| param[0] as usize) + .filter(|¶m| param != 0) + .map(|bottom| bottom.saturating_sub(1)); + self.set_scroll_region(top, bottom); if self.erasure_mode { - self.move_cursor_to_line(top_line_index, EMPTY_TERMINAL_CHARACTER); + self.move_cursor_to_line(top, EMPTY_TERMINAL_CHARACTER); self.move_cursor_to_beginning_of_line(); } - self.show_cursor(); } else { self.clear_scroll_region(); } } else if c == 'M' { // delete lines if currently inside scroll region - let line_count_to_delete = if params[0] == 0 { - 1 - } else { - params[0] as usize - }; + let line_count_to_delete = next_param_or(1); let pad_character = EMPTY_TERMINAL_CHARACTER; self.delete_lines_in_scroll_region(line_count_to_delete, pad_character); } else if c == 'L' { // insert blank lines if inside scroll region - let line_count_to_add = if params[0] == 0 { - 1 - } else { - params[0] as usize - }; + let line_count_to_add = next_param_or(1); let pad_character = EMPTY_TERMINAL_CHARACTER; self.add_empty_lines_in_scroll_region(line_count_to_add, pad_character); - } else if c == 'q' { - // ignore for now to run on mac } else if c == 'G' { - let column = if params[0] == 0 { - 0 - } else { - params[0] as usize - 1 - }; + let column = next_param_or(1).saturating_sub(1); self.move_cursor_to_column(column); } else if c == 'g' { - if params[0] == 0 { + let clear_type = next_param_or(0); + if clear_type == 0 { self.clear_tabstop(self.cursor.x); - } else if params[0] == 3 { + } else if clear_type == 3 { self.clear_all_tabstops(); } } else if c == 'd' { // goto line - let line = if params[0] == 0 { - 1 - } else { - // minus 1 because this is 1 indexed - params[0] as usize - 1 - }; + let line = next_param_or(1).saturating_sub(1); let pad_character = EMPTY_TERMINAL_CHARACTER; self.move_cursor_to_line(line, pad_character); } else if c == 'P' { // erase characters - let count = if params[0] == 0 { - 1 - } else { - params[0] as usize - }; + let count = next_param_or(1); self.erase_characters(count, self.cursor.pending_styles); } else if c == 'X' { // erase characters and replace with empty characters of current style - let count = if params[0] == 0 { - 1 - } else { - params[0] as usize - }; + let count = next_param_or(1); self.replace_with_empty_chars(count, self.cursor.pending_styles); } else if c == 'T' { /* @@ -1230,32 +1200,18 @@ impl vte::Perform for Grid { * Scroll down, new lines inserted at top of screen * [4T = Scroll down 4, bring previous lines back into view */ - let line_count: i64 = *params.get(0).expect("A number of lines was expected."); - - if line_count >= 0 { - self.rotate_scroll_region_up(line_count as usize); - } else { - // TODO: can this actually happen? - self.rotate_scroll_region_down(line_count.abs() as usize); - } + let line_count = next_param_or(1); + self.rotate_scroll_region_up(line_count as usize); } else if c == 'S' { // move scroll up - let count = if params[0] == 0 { - 1 - } else { - params[0] as usize - }; + let count = next_param_or(1); self.rotate_scroll_region_down(count); } else if c == 's' { self.save_cursor_position(); } else if c == 'u' { self.restore_cursor_position(); } else if c == '@' { - let count = if params[0] == 0 { - 1 - } else { - params[0] as usize - }; + let count = next_param_or(1); for _ in 0..count { // TODO: should this be styled? self.insert_character_at_cursor_position(EMPTY_TERMINAL_CHARACTER); diff --git a/src/client/panes/terminal_character.rs b/src/client/panes/terminal_character.rs index 31e017b1..c7c616c1 100644 --- a/src/client/panes/terminal_character.rs +++ b/src/client/panes/terminal_character.rs @@ -1,8 +1,10 @@ use unicode_width::UnicodeWidthChar; use crate::utils::logging::debug_log_to_file; +use std::convert::TryFrom; use std::fmt::{self, Debug, Display, Formatter}; use std::ops::{Index, IndexMut}; +use vte::ParamsIter; pub const EMPTY_TERMINAL_CHARACTER: TerminalCharacter = TerminalCharacter { character: ' ', @@ -327,138 +329,123 @@ impl CharacterStyles { self.hidden = Some(AnsiCode::Reset); self.strike = Some(AnsiCode::Reset); } - pub fn add_style_from_ansi_params(&mut self, ansi_params: &[i64]) { - let mut params_used = 1; // if there's a parameter, it is always used - match ansi_params { - [] | [0, ..] => self.reset_all(), - [1, ..] => *self = self.bold(Some(AnsiCode::On)), - [2, ..] => *self = self.dim(Some(AnsiCode::On)), - [3, ..] => *self = self.italic(Some(AnsiCode::On)), - [4, ..] => *self = self.underline(Some(AnsiCode::On)), - [5, ..] => *self = self.blink_slow(Some(AnsiCode::On)), - [6, ..] => *self = self.blink_fast(Some(AnsiCode::On)), - [7, ..] => *self = self.reverse(Some(AnsiCode::On)), - [8, ..] => *self = self.hidden(Some(AnsiCode::On)), - [9, ..] => *self = self.strike(Some(AnsiCode::On)), - [21, ..] => *self = self.bold(Some(AnsiCode::Reset)), - [22, ..] => { - *self = self.bold(Some(AnsiCode::Reset)); - *self = self.dim(Some(AnsiCode::Reset)); - } - [23, ..] => *self = self.italic(Some(AnsiCode::Reset)), - [24, ..] => *self = self.underline(Some(AnsiCode::Reset)), - [25, ..] => { - *self = self.blink_slow(Some(AnsiCode::Reset)); - *self = self.blink_fast(Some(AnsiCode::Reset)); - } - [27, ..] => *self = self.reverse(Some(AnsiCode::Reset)), - [28, ..] => *self = self.hidden(Some(AnsiCode::Reset)), - [29, ..] => *self = self.strike(Some(AnsiCode::Reset)), - [30, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Black))), - [31, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Red))), - [32, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Green))), - [33, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Yellow))), - [34, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Blue))), - [35, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Magenta))), - [36, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Cyan))), - [37, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::White))), - [38, 2, ..] => { - let ansi_code = AnsiCode::RgbCode(( - *ansi_params.get(2).unwrap() as u8, - *ansi_params.get(3).unwrap() as u8, - *ansi_params.get(4).unwrap() as u8, - )); - *self = self.foreground(Some(ansi_code)); - params_used += 4 // one for the indicator (2 in this case) and three for the rgb code - } - [38, 5, ..] => { - let ansi_code = AnsiCode::ColorIndex(*ansi_params.get(2).unwrap() as u8); - *self = self.foreground(Some(ansi_code)); - params_used += 2 // one for the indicator (5 in this case) and one for the color index - } - [38, ..] => { - // this is a bug - // it means we got a color encoding we don't know how to handle (or is invalid) - params_used += 1; // even if it's a bug, let's not create an endless loop, eh? - } - [39, ..] => *self = self.foreground(Some(AnsiCode::Reset)), - [40, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Black))), - [41, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Red))), - [42, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Green))), - [43, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Yellow))), - [44, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Blue))), - [45, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Magenta))), - [46, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Cyan))), - [47, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::White))), - [48, 2, ..] => { - let ansi_code = AnsiCode::RgbCode(( - *ansi_params.get(2).unwrap() as u8, - *ansi_params.get(3).unwrap() as u8, - *ansi_params.get(4).unwrap() as u8, - )); - *self = self.background(Some(ansi_code)); - params_used += 4 // one for the indicator (2 in this case) and three for the rgb code - } - [48, 5, ..] => { - let ansi_code = AnsiCode::ColorIndex(*ansi_params.get(2).unwrap() as u8); - *self = self.background(Some(ansi_code)); - params_used += 2 // one for the indicator (5 in this case) and one for the color index - } - [48, ..] => { - // this is a bug - // it means we got a color encoding we don't know how to handle (or is invalid) - params_used += 1; // even if it's a bug, let's not create an endless loop, eh? - } - [49, ..] => *self = self.background(Some(AnsiCode::Reset)), - [90, ..] => { - *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightBlack))) - } - [91, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightRed))), - [92, ..] => { - *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightGreen))) - } - [93, ..] => { - *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightYellow))) - } - [94, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightBlue))), - [95, ..] => { - *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightMagenta))) - } - [96, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightCyan))), - [97, ..] => { - *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightWhite))) - } - [100, ..] => { - *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightBlack))) - } - [101, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightRed))), - [102, ..] => { - *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightGreen))) - } - [103, ..] => { - *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightYellow))) - } - [104, ..] => { - *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightBlue))) - } - [105, ..] => { - *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightMagenta))) - } - [106, ..] => { - *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightCyan))) - } - [107, ..] => { - *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightWhite))) - } - _ => { - // if this happens, it's a bug - let _ = debug_log_to_file(format!("unhandled csi m code {:?}", ansi_params)); - return; - } - } - if let Some(next_params) = ansi_params.get(params_used..) { - if !next_params.is_empty() { - self.add_style_from_ansi_params(next_params); + pub fn add_style_from_ansi_params(&mut self, params: &mut ParamsIter) { + while let Some(param) = params.next() { + match param { + [] | [0] => self.reset_all(), + [1] => *self = self.bold(Some(AnsiCode::On)), + [2] => *self = self.dim(Some(AnsiCode::On)), + [3] => *self = self.italic(Some(AnsiCode::On)), + [4] => *self = self.underline(Some(AnsiCode::On)), + [5] => *self = self.blink_slow(Some(AnsiCode::On)), + [6] => *self = self.blink_fast(Some(AnsiCode::On)), + [7] => *self = self.reverse(Some(AnsiCode::On)), + [8] => *self = self.hidden(Some(AnsiCode::On)), + [9] => *self = self.strike(Some(AnsiCode::On)), + [21] => *self = self.bold(Some(AnsiCode::Reset)), + [22] => { + *self = self.bold(Some(AnsiCode::Reset)); + *self = self.dim(Some(AnsiCode::Reset)); + } + [23] => *self = self.italic(Some(AnsiCode::Reset)), + [24] => *self = self.underline(Some(AnsiCode::Reset)), + [25] => { + *self = self.blink_slow(Some(AnsiCode::Reset)); + *self = self.blink_fast(Some(AnsiCode::Reset)); + } + [27] => *self = self.reverse(Some(AnsiCode::Reset)), + [28] => *self = self.hidden(Some(AnsiCode::Reset)), + [29] => *self = self.strike(Some(AnsiCode::Reset)), + [30] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Black))), + [31] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Red))), + [32] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Green))), + [33] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Yellow))), + [34] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Blue))), + [35] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Magenta))), + [36] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Cyan))), + [37] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::White))), + [38] => { + let mut iter = params.map(|param| param[0]); + if let Some(ansi_code) = parse_sgr_color(&mut iter) { + *self = self.foreground(Some(ansi_code)); + } + } + [38, params @ ..] => { + let rgb_start = if params.len() > 4 { 2 } else { 1 }; + let rgb_iter = params[rgb_start..].iter().copied(); + let mut iter = std::iter::once(params[0]).chain(rgb_iter); + if let Some(ansi_code) = parse_sgr_color(&mut iter) { + *self = self.foreground(Some(ansi_code)); + } + } + [39] => *self = self.foreground(Some(AnsiCode::Reset)), + [40] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Black))), + [41] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Red))), + [42] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Green))), + [43] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Yellow))), + [44] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Blue))), + [45] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Magenta))), + [46] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Cyan))), + [47] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::White))), + [48] => { + let mut iter = params.map(|param| param[0]); + if let Some(ansi_code) = parse_sgr_color(&mut iter) { + *self = self.background(Some(ansi_code)); + } + } + [48, params @ ..] => { + let rgb_start = if params.len() > 4 { 2 } else { 1 }; + let rgb_iter = params[rgb_start..].iter().copied(); + let mut iter = std::iter::once(params[0]).chain(rgb_iter); + if let Some(ansi_code) = parse_sgr_color(&mut iter) { + *self = self.background(Some(ansi_code)); + } + } + [49] => *self = self.background(Some(AnsiCode::Reset)), + [90] => { + *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightBlack))) + } + [91] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightRed))), + [92] => { + *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightGreen))) + } + [93] => { + *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightYellow))) + } + [94] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightBlue))), + [95] => { + *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightMagenta))) + } + [96] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightCyan))), + [97] => { + *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightWhite))) + } + [100] => { + *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightBlack))) + } + [101] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightRed))), + [102] => { + *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightGreen))) + } + [103] => { + *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightYellow))) + } + [104] => { + *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightBlue))) + } + [105] => { + *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightMagenta))) + } + [106] => { + *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightCyan))) + } + [107] => { + *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightWhite))) + } + _ => { + let _ = debug_log_to_file(format!("unhandled csi m code {:?}", param)); + return; + } } } } @@ -756,3 +743,15 @@ impl TerminalCharacter { self.width() > 1 } } + +fn parse_sgr_color(params: &mut dyn Iterator) -> Option { + match params.next() { + Some(2) => Some(AnsiCode::RgbCode(( + u8::try_from(params.next()?).ok()?, + u8::try_from(params.next()?).ok()?, + u8::try_from(params.next()?).ok()?, + ))), + Some(5) => Some(AnsiCode::ColorIndex(u8::try_from(params.next()?).ok()?)), + _ => None, + } +} diff --git a/src/tests/integration/snapshots/zellij__tests__integration__compatibility__htop_scrolling.snap b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__htop_scrolling.snap index 6e34a969..beffef7c 100644 --- a/src/tests/integration/snapshots/zellij__tests__integration__compatibility__htop_scrolling.snap +++ b/src/tests/integration/snapshots/zellij__tests__integration__compatibility__htop_scrolling.snap @@ -3,7 +3,7 @@ source: src/tests/integration/compatibility.rs expression: snapshot_before_quit --- -█ + 1 [||||||||||||||||||||||||||||||||||||||||||100.0%] Tasks: 79, 382 thr; 1 running 2 [ 0.0%] Load average: 1.40 1.43 1.38 3 [ 0.0%] Uptime: 2 days, 07:33:50 From ba7368667cd9a948e84c9f6a0091e4cf11414256 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Fri, 7 May 2021 16:04:42 +0200 Subject: [PATCH 11/44] docs(changelog): terminal aifx --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45ce5078..34249ef1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] * Add more functionality to unbinding the default keybindings (https://github.com/zellij-org/zellij/pull/468) +* Terminal compatibility: fix support for CSI subparameters (https://github.com/zellij-org/zellij/pull/469) ## [0.8.0] - 2021-05-07 * Terminal compatibility: pass vttest 8 (https://github.com/zellij-org/zellij/pull/461) From 543b70a2bdd343ee274f9dca65020bd6915c8b27 Mon Sep 17 00:00:00 2001 From: dantepippi <6619666+dantepippi@users.noreply.github.com> Date: Fri, 7 May 2021 11:07:23 -0300 Subject: [PATCH 12/44] Moving the sync command to the Tab mode (#412) * Moving the sync command to the Tab mode * Fixing bug related to plugin panes and sync mode When sync mode was enabled the input from a plugin pane was being incorrectly ignored. --- assets/config/default.yaml | 4 ++-- src/client/tab.rs | 27 ++++++++++----------------- src/common/errors.rs | 4 ++-- src/common/input/actions.rs | 4 ++-- src/common/input/handler.rs | 2 +- src/common/screen.rs | 2 +- src/server/mod.rs | 8 ++++---- 7 files changed, 22 insertions(+), 29 deletions(-) diff --git a/assets/config/default.yaml b/assets/config/default.yaml index 4d0bdac0..d56004ef 100644 --- a/assets/config/default.yaml +++ b/assets/config/default.yaml @@ -97,8 +97,6 @@ keybinds: key: [Char: 'r',] - action: [CloseFocus,] key: [Char: 'x',] - - action: [ToggleActiveSyncPanes] - key: [Char: 's'] - action: [ToggleFocusFullscreen,] key: [Char: 'f',] - action: [FocusPreviousPane,] @@ -130,6 +128,8 @@ keybinds: key: [ Char: 'n',] - action: [CloseTab,] key: [ Char: 'x',] + - action: [ToggleActiveSyncTab] + key: [Char: 's'] - action: [MoveFocus: Left,] key: [ Alt: 'h',] - action: [MoveFocus: Right,] diff --git a/src/client/tab.rs b/src/client/tab.rs index 0098d78b..c4dc3474 100644 --- a/src/client/tab.rs +++ b/src/client/tab.rs @@ -607,23 +607,17 @@ impl Tab { } pub fn write_to_terminals_on_current_tab(&mut self, input_bytes: Vec) { let pane_ids = self.get_pane_ids(); - pane_ids.iter().for_each(|pane_id| match pane_id { - PaneId::Terminal(pid) => { - self.write_to_pane_id(input_bytes.clone(), *pid); - } - PaneId::Plugin(_) => {} + pane_ids.iter().for_each(|&pane_id| { + self.write_to_pane_id(input_bytes.clone(), pane_id); }); } - pub fn write_to_pane_id(&mut self, mut input_bytes: Vec, pid: RawFd) { - self.os_api - .write_to_tty_stdin(pid, &mut input_bytes) - .expect("failed to write to terminal"); - self.os_api.tcdrain(pid).expect("failed to drain terminal"); - } pub fn write_to_active_terminal(&mut self, input_bytes: Vec) { - match self.get_active_pane_id() { - Some(PaneId::Terminal(active_terminal_id)) => { - let active_terminal = self.get_active_pane().unwrap(); + self.write_to_pane_id(input_bytes, self.get_active_pane_id().unwrap()); + } + pub fn write_to_pane_id(&mut self, input_bytes: Vec, pane_id: PaneId) { + match pane_id { + PaneId::Terminal(active_terminal_id) => { + let active_terminal = self.panes.get(&pane_id).unwrap(); let mut adjusted_input = active_terminal.adjust_input_to_terminal(input_bytes); self.os_api .write_to_tty_stdin(active_terminal_id, &mut adjusted_input) @@ -632,14 +626,13 @@ impl Tab { .tcdrain(active_terminal_id) .expect("failed to drain terminal"); } - Some(PaneId::Plugin(pid)) => { + PaneId::Plugin(pid) => { for key in parse_keys(&input_bytes) { self.send_plugin_instructions .send(PluginInstruction::Update(Some(pid), Event::KeyPress(key))) .unwrap() } } - _ => {} } } pub fn get_active_terminal_cursor_position(&self) -> Option<(usize, usize)> { @@ -713,7 +706,7 @@ impl Tab { pub fn is_sync_panes_active(&self) -> bool { self.synchronize_is_active } - pub fn toggle_sync_panes_is_active(&mut self) { + pub fn toggle_sync_tab_is_active(&mut self) { self.synchronize_is_active = !self.synchronize_is_active; } pub fn panes_contain_widechar(&self) -> bool { diff --git a/src/common/errors.rs b/src/common/errors.rs index 411e0225..d8327462 100644 --- a/src/common/errors.rs +++ b/src/common/errors.rs @@ -200,7 +200,7 @@ pub enum ScreenContext { PageScrollDown, ClearScroll, CloseFocusedPane, - ToggleActiveSyncPanes, + ToggleActiveSyncTab, ToggleActiveTerminalFullscreen, SetSelectable, SetInvisibleBorders, @@ -261,7 +261,7 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::UpdateTabName(_) => ScreenContext::UpdateTabName, ScreenInstruction::TerminalResize(_) => ScreenContext::TerminalResize, ScreenInstruction::ChangeMode(_) => ScreenContext::ChangeMode, - ScreenInstruction::ToggleActiveSyncPanes => ScreenContext::ToggleActiveSyncPanes, + ScreenInstruction::ToggleActiveSyncTab => ScreenContext::ToggleActiveSyncTab, } } } diff --git a/src/common/input/actions.rs b/src/common/input/actions.rs index 46a8e1d6..54cbbcdc 100644 --- a/src/common/input/actions.rs +++ b/src/common/input/actions.rs @@ -39,8 +39,8 @@ pub enum Action { PageScrollDown, /// Toggle between fullscreen focus pane and normal layout. ToggleFocusFullscreen, - /// Toggle between sending text commands to all panes and normal mode. - ToggleActiveSyncPanes, + /// 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), diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs index 75f9f8b1..a4293220 100644 --- a/src/common/input/handler.rs +++ b/src/common/input/handler.rs @@ -160,7 +160,6 @@ pub fn get_mode_info(mode: InputMode, palette: Palette) -> ModeInfo { keybinds.push(("d".to_string(), "Down split".to_string())); keybinds.push(("r".to_string(), "Right split".to_string())); keybinds.push(("x".to_string(), "Close".to_string())); - keybinds.push(("s".to_string(), "Sync".to_string())); keybinds.push(("f".to_string(), "Fullscreen".to_string())); } InputMode::Tab => { @@ -168,6 +167,7 @@ pub fn get_mode_info(mode: InputMode, palette: Palette) -> ModeInfo { keybinds.push(("n".to_string(), "New".to_string())); keybinds.push(("x".to_string(), "Close".to_string())); keybinds.push(("r".to_string(), "Rename".to_string())); + keybinds.push(("s".to_string(), "Sync".to_string())); } InputMode::Scroll => { keybinds.push(("↓↑".to_string(), "Scroll".to_string())); diff --git a/src/common/screen.rs b/src/common/screen.rs index 562b0ffe..62d87b51 100644 --- a/src/common/screen.rs +++ b/src/common/screen.rs @@ -52,7 +52,7 @@ pub enum ScreenInstruction { NewTab(RawFd), SwitchTabNext, SwitchTabPrev, - ToggleActiveSyncPanes, + ToggleActiveSyncTab, CloseTab, GoToTab(u32), UpdateTabName(Vec), diff --git a/src/server/mod.rs b/src/server/mod.rs index c0cf482c..a90831d9 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -528,11 +528,11 @@ fn init_session( ScreenInstruction::ChangeMode(mode_info) => { screen.change_mode(mode_info); } - ScreenInstruction::ToggleActiveSyncPanes => { + ScreenInstruction::ToggleActiveSyncTab => { screen .get_active_tab_mut() .unwrap() - .toggle_sync_panes_is_active(); + .toggle_sync_tab_is_active(); screen.update_tabs(); } ScreenInstruction::Exit => { @@ -781,10 +781,10 @@ fn route_action(action: Action, session: &SessionMetaData, os_input: &dyn Server .send(ScreenInstruction::SwitchTabPrev) .unwrap(); } - Action::ToggleActiveSyncPanes => { + Action::ToggleActiveSyncTab => { session .send_screen_instructions - .send(ScreenInstruction::ToggleActiveSyncPanes) + .send(ScreenInstruction::ToggleActiveSyncTab) .unwrap(); } Action::CloseTab => { From b2efb68a6e24785118f9608ab443957a3a7623bc Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Fri, 7 May 2021 16:08:03 +0200 Subject: [PATCH 13/44] docs(changelog): sync command migration --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34249ef1..29ca7a81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] * Add more functionality to unbinding the default keybindings (https://github.com/zellij-org/zellij/pull/468) * Terminal compatibility: fix support for CSI subparameters (https://github.com/zellij-org/zellij/pull/469) +* Move the sync command to tab mode (https://github.com/zellij-org/zellij/pull/412) ## [0.8.0] - 2021-05-07 * Terminal compatibility: pass vttest 8 (https://github.com/zellij-org/zellij/pull/461) From 7b5e728f9db7d82d9282265bb8fa35d7b05e5622 Mon Sep 17 00:00:00 2001 From: matu3ba Date: Fri, 7 May 2021 16:13:20 +0200 Subject: [PATCH 14/44] README: shortening + add explanation (#470) * README: shortening + add explanation explain debug build command for bug reports * docs(readme): capitalize Co-authored-by: Aram Drevekenin --- README.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 17b43ad9..3751e9ac 100644 --- a/README.md +++ b/README.md @@ -36,12 +36,12 @@ cargo install zellij Or you can download a prebuilt binary from our [Releases](https://github.com/zellij-org/zellij/releases). -## How do I hack on it? +## How do I hack on it? (Contributing) * Clone the project * Install cargo-make with `cargo install --force cargo-make` -* In the project folder, run: `cargo make run` +* In the project folder, for debug builds run: `cargo make run` -For more build commands, take a look at [`Contributing.md`](CONTRIBUTING.md). +For more build commands, see [`Contributing.md`](CONTRIBUTING.md). ## Configuration For configuring Zellij, please see the [Configuration documentation](https://zellij.dev/documentation/configuration.html). @@ -68,11 +68,6 @@ This section contains an ever-changing list of the major features that are eithe * **Support for multiple terminal windows across screens** - Transfer panes across different windows and screens by having them all belong to the same session. * **Smart layouts** - expand the current layout system so that it rearranges and hides panes intelligently when new ones are added or the window size is changed. - -## Contributing - -Take a look at [`Contributing.md`](CONTRIBUTING.md) guide. - ## License MIT From 9a3e8bcb84caa0fd103b6d9c365fb97690c1c5ea Mon Sep 17 00:00:00 2001 From: Kyle Sutherland-Cash Date: Sat, 8 May 2021 04:32:31 -0700 Subject: [PATCH 15/44] First round of merging against server-client and color stuff --- docs/ARCHITECTURE.md | 2 +- src/client/mod.rs | 2 +- src/client/panes/plugin_pane.rs | 4 +- src/client/panes/terminal_pane.rs | 2 +- src/client/tab.rs | 71 ++- src/common/errors.rs | 10 +- src/common/input/actions.rs | 2 +- src/common/input/handler.rs | 2 +- src/common/mod.rs | 65 +-- src/common/{pty_bus.rs => pty.rs} | 136 ++++-- src/common/screen.rs | 307 ++++++++++-- src/common/thread_bus.rs | 142 ++++++ src/common/wasm_vm.rs | 129 +++++- src/main.rs | 4 +- src/server/mod.rs | 747 ++++-------------------------- src/server/route.rs | 205 ++++++++ src/tests/fakes.rs | 2 +- 17 files changed, 955 insertions(+), 877 deletions(-) rename src/common/{pty_bus.rs => pty.rs} (65%) create mode 100644 src/common/thread_bus.rs create mode 100644 src/server/route.rs diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index a54585ae..9b2ee756 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -37,5 +37,5 @@ The Boundaries refer to those lines that are drawn between terminal panes. A few * The Rect trait is here so that different panes can implement it, giving boundaries a generic way to calculate the size of the pane and draw boundaries around it. * Here we use the [unicode box drawing characters](https://en.wikipedia.org/wiki/Box-drawing_character) in order to draw the borders. There's some logic here about combining them together for all possible combinations of pane locations. -## PTY Bus (src/pty_bus.rs) +## PTY Bus (src/pty.rs) The PtyBus keeps track of several asynchronous streams that read from pty sockets (eg. /dev/pts/999), parse those bytes into ANSI/VT events and send them on to the Screen so that they can be received in the relevant TerminalPane. diff --git a/src/client/mod.rs b/src/client/mod.rs index 8a5834f0..ba27bc3e 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -16,7 +16,7 @@ use crate::common::{ input::config::Config, input::handler::input_loop, os_input_output::ClientOsApi, - SenderType, SenderWithContext, SyncChannelWithContext, + thread_bus::{SenderType, SenderWithContext, SyncChannelWithContext}, }; use crate::server::ServerInstruction; diff --git a/src/client/panes/plugin_pane.rs b/src/client/panes/plugin_pane.rs index c3b633ec..cd22e934 100644 --- a/src/client/panes/plugin_pane.rs +++ b/src/client/panes/plugin_pane.rs @@ -1,4 +1,6 @@ -use crate::{common::SenderWithContext, pty_bus::VteBytes, tab::Pane, wasm_vm::PluginInstruction}; +use crate::{ + common::thread_bus::SenderWithContext, pty::VteBytes, tab::Pane, wasm_vm::PluginInstruction, +}; use crate::panes::{PaneId, PositionAndSize}; diff --git a/src/client/panes/terminal_pane.rs b/src/client/panes/terminal_pane.rs index f58c3a27..e0ce8ffe 100644 --- a/src/client/panes/terminal_pane.rs +++ b/src/client/panes/terminal_pane.rs @@ -9,7 +9,7 @@ use crate::panes::grid::Grid; use crate::panes::terminal_character::{ CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER, }; -use crate::pty_bus::VteBytes; +use crate::pty::VteBytes; #[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug, Serialize, Deserialize)] pub enum PaneId { diff --git a/src/client/tab.rs b/src/client/tab.rs index c4dc3474..bbd68a4d 100644 --- a/src/client/tab.rs +++ b/src/client/tab.rs @@ -2,11 +2,12 @@ //! as well as how they should be resized use crate::client::pane_resizer::PaneResizer; -use crate::common::{input::handler::parse_keys, SenderWithContext}; +use crate::common::input::handler::parse_keys; +use crate::common::thread_bus::ThreadSenders; use crate::layout::Layout; use crate::os_input_output::ServerOsApi; use crate::panes::{PaneId, PositionAndSize, TerminalPane}; -use crate::pty_bus::{PtyInstruction, VteBytes}; +use crate::pty::{PtyInstruction, VteBytes}; use crate::server::ServerInstruction; use crate::utils::shared::adjust_to_size; use crate::wasm_vm::PluginInstruction; @@ -69,9 +70,7 @@ pub struct Tab { full_screen_ws: PositionAndSize, fullscreen_is_active: bool, os_api: Box, - send_plugin_instructions: SenderWithContext, - send_pty_instructions: SenderWithContext, - send_server_instructions: SenderWithContext, + pub senders: ThreadSenders, synchronize_is_active: bool, should_clear_display_before_rendering: bool, pub mode_info: ModeInfo, @@ -219,7 +218,7 @@ pub trait Pane { } impl Tab { - // FIXME: Too many arguments here! Maybe bundle all of the senders for the whole program in a struct? + // FIXME: Still too many arguments for clippy to be happy... #[allow(clippy::too_many_arguments)] pub fn new( index: usize, @@ -227,9 +226,7 @@ impl Tab { name: String, full_screen_ws: &PositionAndSize, mut os_api: Box, - send_plugin_instructions: SenderWithContext, - send_pty_instructions: SenderWithContext, - send_server_instructions: SenderWithContext, + senders: ThreadSenders, max_panes: Option, pane_id: Option, mode_info: ModeInfo, @@ -261,9 +258,7 @@ impl Tab { fullscreen_is_active: false, synchronize_is_active: false, os_api, - send_plugin_instructions, - send_pty_instructions, - send_server_instructions, + senders, should_clear_display_before_rendering: false, mode_info, input_mode, @@ -315,14 +310,14 @@ impl Tab { // Just a regular terminal if let Some(plugin) = &layout.plugin { let (pid_tx, pid_rx) = channel(); - self.send_plugin_instructions - .send(PluginInstruction::Load(pid_tx, plugin.clone())) + self.senders + .send_to_plugin(PluginInstruction::Load(pid_tx, plugin.clone())) .unwrap(); let pid = pid_rx.recv().unwrap(); let mut new_plugin = PluginPane::new( pid, *position_and_size, - self.send_plugin_instructions.clone(), + self.senders.to_plugin.as_ref().unwrap().clone(), ); if let Some(max_rows) = position_and_size.max_rows { new_plugin.set_max_height(max_rows); @@ -332,8 +327,8 @@ impl Tab { } self.panes.insert(PaneId::Plugin(pid), Box::new(new_plugin)); // Send an initial mode update to the newly loaded plugin only! - self.send_plugin_instructions - .send(PluginInstruction::Update( + self.senders + .send_to_plugin(PluginInstruction::Update( Some(pid), Event::ModeUpdate(self.mode_info.clone()), )) @@ -355,8 +350,8 @@ impl Tab { // this is a bit of a hack and happens because we don't have any central location that // can query the screen as to how many panes it needs to create a layout // fixing this will require a bit of an architecture change - self.send_pty_instructions - .send(PtyInstruction::ClosePane(PaneId::Terminal(*unused_pid))) + self.senders + .send_to_pty(PtyInstruction::ClosePane(PaneId::Terminal(*unused_pid))) .unwrap(); } self.active_terminal = self.panes.iter().map(|(id, _)| id.to_owned()).next(); @@ -400,9 +395,9 @@ impl Tab { }, ); if terminal_id_to_split.is_none() { - self.send_pty_instructions - .send(PtyInstruction::ClosePane(pid)) - .unwrap(); // we can't open this pane, close the pty + self.senders + .send_to_pty(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty + .unwrap(); return; // likely no terminal large enough to split } let terminal_id_to_split = terminal_id_to_split.unwrap(); @@ -481,9 +476,9 @@ impl Tab { let active_pane_id = &self.get_active_pane_id().unwrap(); let active_pane = self.panes.get_mut(active_pane_id).unwrap(); if active_pane.rows() < MIN_TERMINAL_HEIGHT * 2 + 1 { - self.send_pty_instructions - .send(PtyInstruction::ClosePane(pid)) - .unwrap(); // we can't open this pane, close the pty + self.senders + .send_to_pty(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty + .unwrap(); return; } let terminal_ws = PositionAndSize { @@ -538,9 +533,9 @@ impl Tab { let active_pane_id = &self.get_active_pane_id().unwrap(); let active_pane = self.panes.get_mut(active_pane_id).unwrap(); if active_pane.columns() < MIN_TERMINAL_WIDTH * 2 + 1 { - self.send_pty_instructions - .send(PtyInstruction::ClosePane(pid)) - .unwrap(); // we can't open this pane, close the pty + self.senders + .send_to_pty(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty + .unwrap(); return; } let terminal_ws = PositionAndSize { @@ -597,7 +592,7 @@ impl Tab { } pub fn handle_pty_bytes(&mut self, pid: RawFd, bytes: VteBytes) { // if we don't have the terminal in self.terminals it's probably because - // of a race condition where the terminal was created in pty_bus but has not + // of a race condition where the terminal was created in pty but has not // yet been created in Screen. These events are currently not buffered, so // if you're debugging seemingly randomly missing stdout data, this is // the reason @@ -628,8 +623,8 @@ impl Tab { } PaneId::Plugin(pid) => { for key in parse_keys(&input_bytes) { - self.send_plugin_instructions - .send(PluginInstruction::Update(Some(pid), Event::KeyPress(key))) + self.senders + .send_to_plugin(PluginInstruction::Update(Some(pid), Event::KeyPress(key))) .unwrap() } } @@ -706,7 +701,7 @@ impl Tab { pub fn is_sync_panes_active(&self) -> bool { self.synchronize_is_active } - pub fn toggle_sync_tab_is_active(&mut self) { + pub fn toggle_sync_panes_is_active(&mut self) { self.synchronize_is_active = !self.synchronize_is_active; } pub fn panes_contain_widechar(&self) -> bool { @@ -781,8 +776,8 @@ impl Tab { } } - self.send_server_instructions - .send(ServerInstruction::Render(Some(output))) + self.senders + .send_to_server(ServerInstruction::Render(Some(output))) .unwrap(); } fn get_panes(&self) -> impl Iterator)> { @@ -2086,8 +2081,8 @@ impl Tab { if let Some(max_panes) = self.max_panes { let terminals = self.get_pane_ids(); for &pid in terminals.iter().skip(max_panes - 1) { - self.send_pty_instructions - .send(PtyInstruction::ClosePane(pid)) + self.senders + .send_to_pty(PtyInstruction::ClosePane(pid)) .unwrap(); self.close_pane_without_rerender(pid); } @@ -2198,8 +2193,8 @@ impl Tab { pub fn close_focused_pane(&mut self) { if let Some(active_pane_id) = self.get_active_pane_id() { self.close_pane(active_pane_id); - self.send_pty_instructions - .send(PtyInstruction::ClosePane(active_pane_id)) + self.senders + .send_to_pty(PtyInstruction::ClosePane(active_pane_id)) .unwrap(); } } diff --git a/src/common/errors.rs b/src/common/errors.rs index d8327462..46121312 100644 --- a/src/common/errors.rs +++ b/src/common/errors.rs @@ -1,9 +1,9 @@ //! Error context system based on a thread-local representation of the call stack, itself based on //! the instructions that are sent between threads. -use super::{ServerInstruction, ASYNCOPENCALLS, OPENCALLS}; +use super::{thread_bus::ASYNCOPENCALLS, thread_bus::OPENCALLS, ServerInstruction}; use crate::client::ClientInstruction; -use crate::pty_bus::PtyInstruction; +use crate::pty::PtyInstruction; use crate::screen::ScreenInstruction; use serde::{Deserialize, Serialize}; @@ -14,7 +14,7 @@ use std::fmt::{Display, Error, Formatter}; const MAX_THREAD_CALL_STACK: usize = 6; #[cfg(not(test))] -use super::SenderWithContext; +use super::thread_bus::SenderWithContext; #[cfg(not(test))] use std::panic::PanicInfo; /// Custom panic handler/hook. Prints the [`ErrorContext`]. @@ -200,7 +200,7 @@ pub enum ScreenContext { PageScrollDown, ClearScroll, CloseFocusedPane, - ToggleActiveSyncTab, + ToggleActiveSyncPanes, ToggleActiveTerminalFullscreen, SetSelectable, SetInvisibleBorders, @@ -261,7 +261,7 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::UpdateTabName(_) => ScreenContext::UpdateTabName, ScreenInstruction::TerminalResize(_) => ScreenContext::TerminalResize, ScreenInstruction::ChangeMode(_) => ScreenContext::ChangeMode, - ScreenInstruction::ToggleActiveSyncTab => ScreenContext::ToggleActiveSyncTab, + ScreenInstruction::ToggleActiveSyncPanes => ScreenContext::ToggleActiveSyncPanes, } } } diff --git a/src/common/input/actions.rs b/src/common/input/actions.rs index 54cbbcdc..f07d7513 100644 --- a/src/common/input/actions.rs +++ b/src/common/input/actions.rs @@ -40,7 +40,7 @@ pub enum Action { /// 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, + ToggleActiveSyncPanes, /// Open a new pane in the specified direction (relative to focus). /// If no direction is specified, will try to use the biggest available space. NewPane(Option), diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs index a4293220..78635237 100644 --- a/src/common/input/handler.rs +++ b/src/common/input/handler.rs @@ -4,7 +4,7 @@ use super::actions::Action; use super::keybinds::Keybinds; use crate::client::ClientInstruction; use crate::common::input::config::Config; -use crate::common::{SenderWithContext, OPENCALLS}; +use crate::common::thread_bus::{SenderWithContext, OPENCALLS}; use crate::errors::ContextType; use crate::os_input_output::ClientOsApi; use crate::server::ServerInstruction; diff --git a/src/common/mod.rs b/src/common/mod.rs index 6061f1a6..c91e1e05 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -3,73 +3,12 @@ pub mod errors; pub mod input; pub mod ipc; pub mod os_input_output; -pub mod pty_bus; +pub mod pty; pub mod screen; pub mod setup; +pub mod thread_bus; pub mod utils; pub mod wasm_vm; use crate::panes::PaneId; use crate::server::ServerInstruction; -use async_std::task_local; -use errors::{get_current_ctx, ErrorContext}; -use std::cell::RefCell; -use std::sync::mpsc; - -/// An [MPSC](mpsc) asynchronous channel with added error context. -pub type ChannelWithContext = ( - mpsc::Sender<(T, ErrorContext)>, - mpsc::Receiver<(T, ErrorContext)>, -); -/// An [MPSC](mpsc) synchronous channel with added error context. -pub type SyncChannelWithContext = ( - 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 { - /// 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 { - sender: SenderType, -} - -impl SenderWithContext { - pub fn new(sender: SenderType) -> 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 Send for SenderWithContext {} -unsafe impl Sync for SenderWithContext {} - -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 = RefCell::default() -); - -task_local! { - /// A key to some task local storage that holds a representation of the task's call - /// stack in the form of an [`ErrorContext`]. - static ASYNCOPENCALLS: RefCell = RefCell::default() -} diff --git a/src/common/pty_bus.rs b/src/common/pty.rs similarity index 65% rename from src/common/pty_bus.rs rename to src/common/pty.rs index 31696186..483aa51e 100644 --- a/src/common/pty_bus.rs +++ b/src/common/pty.rs @@ -4,20 +4,19 @@ use ::async_std::task::*; use ::std::collections::HashMap; use ::std::os::unix::io::RawFd; use ::std::pin::*; -use ::std::sync::mpsc::Receiver; use ::std::time::{Duration, Instant}; use std::path::PathBuf; -use super::SenderWithContext; -use crate::layout::Layout; +use super::{screen::ScreenInstruction, thread_bus::SenderWithContext}; use crate::os_input_output::ServerOsApi; use crate::utils::logging::debug_to_file; +use crate::wasm_vm::PluginInstruction; use crate::{ - errors::{get_current_ctx, ContextType, ErrorContext}, + common::thread_bus::Bus, + errors::{get_current_ctx, ContextType, PtyContext}, panes::PaneId, - screen::ScreenInstruction, - wasm_vm::PluginInstruction, }; +use crate::{layout::Layout, server::ServerInstruction}; pub struct ReadFromPid { pid: RawFd, @@ -79,16 +78,69 @@ pub enum PtyInstruction { Exit, } -pub struct PtyBus { - pub receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>, +pub struct Pty { + pub bus: Bus, pub id_to_child_pid: HashMap, - pub send_screen_instructions: SenderWithContext, - send_plugin_instructions: SenderWithContext, - os_input: Box, debug_to_file: bool, task_handles: HashMap>, } +pub fn pty_thread_main(mut pty: Pty, maybe_layout: Option) { + loop { + let (event, mut err_ctx) = pty.bus.recv().expect("failed to receive event on channel"); + err_ctx.add_call(ContextType::Pty(PtyContext::from(&event))); + match event { + PtyInstruction::SpawnTerminal(file_to_open) => { + let pid = pty.spawn_terminal(file_to_open); + pty.bus + .senders + .send_to_screen(ScreenInstruction::NewPane(PaneId::Terminal(pid))) + .unwrap(); + } + PtyInstruction::SpawnTerminalVertically(file_to_open) => { + let pid = pty.spawn_terminal(file_to_open); + pty.bus + .senders + .send_to_screen(ScreenInstruction::VerticalSplit(PaneId::Terminal(pid))) + .unwrap(); + } + PtyInstruction::SpawnTerminalHorizontally(file_to_open) => { + let pid = pty.spawn_terminal(file_to_open); + pty.bus + .senders + .send_to_screen(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid))) + .unwrap(); + } + PtyInstruction::NewTab => { + if let Some(layout) = maybe_layout.clone() { + pty.spawn_terminals_for_layout(layout); + } else { + let pid = pty.spawn_terminal(None); + pty.bus + .senders + .send_to_screen(ScreenInstruction::NewTab(pid)) + .unwrap(); + } + } + PtyInstruction::ClosePane(id) => { + pty.close_pane(id); + pty.bus + .senders + .send_to_server(ServerInstruction::UnblockInputThread) + .unwrap(); + } + PtyInstruction::CloseTab(ids) => { + pty.close_tab(ids); + pty.bus + .senders + .send_to_server(ServerInstruction::UnblockInputThread) + .unwrap(); + } + PtyInstruction::Exit => break, + } + } +} + fn stream_terminal_bytes( pid: RawFd, send_screen_instructions: SenderWithContext, @@ -124,9 +176,7 @@ fn stream_terminal_bytes( Some(receive_time) => { if receive_time.elapsed() > max_render_pause { pending_render = false; - send_screen_instructions - .send(ScreenInstruction::Render) - .unwrap(); + let _ = send_screen_instructions.send(ScreenInstruction::Render); last_byte_receive_time = Some(Instant::now()); } else { pending_render = true; @@ -140,9 +190,7 @@ fn stream_terminal_bytes( } else { if pending_render { pending_render = false; - send_screen_instructions - .send(ScreenInstruction::Render) - .unwrap(); + let _ = send_screen_instructions.send(ScreenInstruction::Render); } last_byte_receive_time = None; task::sleep(::std::time::Duration::from_millis(10)).await; @@ -162,31 +210,26 @@ fn stream_terminal_bytes( }) } -impl PtyBus { - pub fn new( - receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>, - send_screen_instructions: SenderWithContext, - send_plugin_instructions: SenderWithContext, - os_input: Box, - debug_to_file: bool, - ) -> Self { - PtyBus { - receive_pty_instructions, - os_input, +impl Pty { + pub fn new(bus: Bus, debug_to_file: bool) -> Self { + Pty { + bus, id_to_child_pid: HashMap::new(), - send_screen_instructions, - send_plugin_instructions, debug_to_file, task_handles: HashMap::new(), } } pub fn spawn_terminal(&mut self, file_to_open: Option) -> RawFd { - let (pid_primary, pid_secondary): (RawFd, RawFd) = - self.os_input.spawn_terminal(file_to_open); + let (pid_primary, pid_secondary): (RawFd, RawFd) = self + .bus + .os_input + .as_mut() + .unwrap() + .spawn_terminal(file_to_open); let task_handle = stream_terminal_bytes( pid_primary, - self.send_screen_instructions.clone(), - self.os_input.clone(), + self.bus.senders.to_screen.as_ref().unwrap().clone(), + self.bus.os_input.as_ref().unwrap().clone(), self.debug_to_file, ); self.task_handles.insert(pid_primary, task_handle); @@ -197,12 +240,14 @@ impl PtyBus { let total_panes = layout.total_terminal_panes(); let mut new_pane_pids = vec![]; for _ in 0..total_panes { - let (pid_primary, pid_secondary): (RawFd, RawFd) = self.os_input.spawn_terminal(None); + let (pid_primary, pid_secondary): (RawFd, RawFd) = + self.bus.os_input.as_mut().unwrap().spawn_terminal(None); self.id_to_child_pid.insert(pid_primary, pid_secondary); new_pane_pids.push(pid_primary); } - self.send_screen_instructions - .send(ScreenInstruction::ApplyLayout( + self.bus + .senders + .send_to_screen(ScreenInstruction::ApplyLayout( layout, new_pane_pids.clone(), )) @@ -210,8 +255,8 @@ impl PtyBus { for id in new_pane_pids { let task_handle = stream_terminal_bytes( id, - self.send_screen_instructions.clone(), - self.os_input.clone(), + self.bus.senders.to_screen.as_ref().unwrap().clone(), + self.bus.os_input.as_ref().unwrap().clone(), self.debug_to_file, ); self.task_handles.insert(id, task_handle); @@ -222,15 +267,16 @@ impl PtyBus { PaneId::Terminal(id) => { let child_pid = self.id_to_child_pid.remove(&id).unwrap(); let handle = self.task_handles.remove(&id).unwrap(); - self.os_input.kill(child_pid).unwrap(); + self.bus.os_input.as_mut().unwrap().kill(child_pid).unwrap(); task::block_on(async { handle.cancel().await; }); } - PaneId::Plugin(pid) => self - .send_plugin_instructions - .send(PluginInstruction::Unload(pid)) - .unwrap(), + PaneId::Plugin(pid) => drop( + self.bus + .senders + .send_to_plugin(PluginInstruction::Unload(pid)), + ), } } pub fn close_tab(&mut self, ids: Vec) { @@ -240,7 +286,7 @@ impl PtyBus { } } -impl Drop for PtyBus { +impl Drop for Pty { fn drop(&mut self) { let child_ids: Vec = self.id_to_child_pid.keys().copied().collect(); for id in child_ids { diff --git a/src/common/screen.rs b/src/common/screen.rs index 62d87b51..131b874d 100644 --- a/src/common/screen.rs +++ b/src/common/screen.rs @@ -3,15 +3,14 @@ use std::collections::BTreeMap; use std::os::unix::io::RawFd; use std::str; -use std::sync::mpsc::Receiver; -use crate::common::SenderWithContext; -use crate::os_input_output::ServerOsApi; +use crate::common::pty::{PtyInstruction, VteBytes}; +use crate::common::thread_bus::Bus; +use crate::errors::{ContextType, ScreenContext}; use crate::panes::PositionAndSize; -use crate::pty_bus::{PtyInstruction, VteBytes}; use crate::server::ServerInstruction; use crate::tab::Tab; -use crate::{errors::ErrorContext, wasm_vm::PluginInstruction}; +use crate::wasm_vm::PluginInstruction; use crate::{layout::Layout, panes::PaneId}; use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, TabInfo}; @@ -52,7 +51,7 @@ pub enum ScreenInstruction { NewTab(RawFd), SwitchTabNext, SwitchTabPrev, - ToggleActiveSyncTab, + ToggleActiveSyncPanes, CloseTab, GoToTab(u32), UpdateTabName(Vec), @@ -63,55 +62,37 @@ pub enum ScreenInstruction { /// A [`Screen`] holds multiple [`Tab`]s, each one holding multiple [`panes`](crate::client::panes). /// It only directly controls which tab is active, delegating the rest to the individual `Tab`. pub struct Screen { - /// A [`ScreenInstruction`] and [`ErrorContext`] receiver. - pub receiver: Receiver<(ScreenInstruction, ErrorContext)>, + /// A Bus for sending and receiving messages with the other threads. + pub bus: Bus, /// An optional maximal amount of panes allowed per [`Tab`] in this [`Screen`] instance. max_panes: Option, /// A map between this [`Screen`]'s tabs and their ID/key. tabs: BTreeMap, - /// A [`PluginInstruction`] and [`ErrorContext`] sender. - pub send_plugin_instructions: SenderWithContext, - /// An [`PtyInstruction`] and [`ErrorContext`] sender. - pub send_pty_instructions: SenderWithContext, - /// An [`ServerInstruction`] and [`ErrorContext`] sender. - pub send_server_instructions: SenderWithContext, /// The full size of this [`Screen`]. full_screen_ws: PositionAndSize, /// The index of this [`Screen`]'s active [`Tab`]. active_tab_index: Option, - /// The [`ServerOsApi`] this [`Screen`] uses. - os_api: Box, mode_info: ModeInfo, input_mode: InputMode, colors: Palette, } impl Screen { - // FIXME: This lint needs actual fixing! Maybe by bundling the Senders /// Creates and returns a new [`Screen`]. - #[allow(clippy::too_many_arguments)] pub fn new( - receive_screen_instructions: Receiver<(ScreenInstruction, ErrorContext)>, - send_plugin_instructions: SenderWithContext, - send_pty_instructions: SenderWithContext, - send_server_instructions: SenderWithContext, + bus: Bus, full_screen_ws: &PositionAndSize, - os_api: Box, max_panes: Option, mode_info: ModeInfo, input_mode: InputMode, colors: Palette, ) -> Self { Screen { - receiver: receive_screen_instructions, + bus, max_panes, - send_plugin_instructions, - send_pty_instructions, - send_server_instructions, full_screen_ws: *full_screen_ws, active_tab_index: None, tabs: BTreeMap::new(), - os_api, mode_info, input_mode, colors, @@ -128,10 +109,8 @@ impl Screen { position, String::new(), &self.full_screen_ws, - self.os_api.clone(), - self.send_plugin_instructions.clone(), - self.send_pty_instructions.clone(), - self.send_server_instructions.clone(), + self.bus.os_input.as_ref().unwrap().clone(), + self.bus.senders.clone(), self.max_panes, Some(PaneId::Terminal(pane_id)), self.mode_info.clone(), @@ -215,13 +194,15 @@ impl Screen { // below we don't check the result of sending the CloseTab instruction to the pty thread // because this might be happening when the app is closing, at which point the pty thread // has already closed and this would result in an error - self.send_pty_instructions - .send(PtyInstruction::CloseTab(pane_ids)) + self.bus + .senders + .send_to_pty(PtyInstruction::CloseTab(pane_ids)) .unwrap(); if self.tabs.is_empty() { self.active_tab_index = None; - self.send_server_instructions - .send(ServerInstruction::Render(None)) + self.bus + .senders + .send_to_server(ServerInstruction::Render(None)) .unwrap(); } else { for t in self.tabs.values_mut() { @@ -284,10 +265,8 @@ impl Screen { position, String::new(), &self.full_screen_ws, - self.os_api.clone(), - self.send_plugin_instructions.clone(), - self.send_pty_instructions.clone(), - self.send_server_instructions.clone(), + self.bus.os_input.as_ref().unwrap().clone(), + self.bus.senders.clone(), self.max_panes, None, self.mode_info.clone(), @@ -311,8 +290,9 @@ impl Screen { is_sync_panes_active: tab.is_sync_panes_active(), }); } - self.send_plugin_instructions - .send(PluginInstruction::Update(None, Event::TabUpdate(tab_data))) + self.bus + .senders + .send_to_plugin(PluginInstruction::Update(None, Event::TabUpdate(tab_data))) .unwrap(); } @@ -340,3 +320,246 @@ impl Screen { } } } + +pub fn screen_thread_main( + bus: Bus, + max_panes: Option, + full_screen_ws: PositionAndSize, +) { + let colors = bus.os_input.as_ref().unwrap().load_palette(); + let mut screen = Screen::new( + bus, + &full_screen_ws, + max_panes, + ModeInfo { + palette: colors, + ..ModeInfo::default() + }, + InputMode::Normal, + colors, + ); + loop { + let (event, mut err_ctx) = screen + .bus + .recv() + .expect("failed to receive event on channel"); + err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event))); + match event { + ScreenInstruction::PtyBytes(pid, vte_bytes) => { + let active_tab = screen.get_active_tab_mut().unwrap(); + if active_tab.has_terminal_pid(pid) { + // it's most likely that this event is directed at the active tab + // look there first + active_tab.handle_pty_bytes(pid, vte_bytes); + } else { + // if this event wasn't directed at the active tab, start looking + // in other tabs + let all_tabs = screen.get_tabs_mut(); + for tab in all_tabs.values_mut() { + if tab.has_terminal_pid(pid) { + tab.handle_pty_bytes(pid, vte_bytes); + break; + } + } + } + } + ScreenInstruction::Render => { + screen.render(); + } + ScreenInstruction::NewPane(pid) => { + screen.get_active_tab_mut().unwrap().new_pane(pid); + screen + .bus + .senders + .send_to_server(ServerInstruction::UnblockInputThread) + .unwrap(); + } + ScreenInstruction::HorizontalSplit(pid) => { + screen.get_active_tab_mut().unwrap().horizontal_split(pid); + screen + .bus + .senders + .send_to_server(ServerInstruction::UnblockInputThread) + .unwrap(); + } + ScreenInstruction::VerticalSplit(pid) => { + screen.get_active_tab_mut().unwrap().vertical_split(pid); + screen + .bus + .senders + .send_to_server(ServerInstruction::UnblockInputThread) + .unwrap(); + } + ScreenInstruction::WriteCharacter(bytes) => { + let active_tab = screen.get_active_tab_mut().unwrap(); + match active_tab.is_sync_panes_active() { + true => active_tab.write_to_terminals_on_current_tab(bytes), + false => active_tab.write_to_active_terminal(bytes), + } + } + ScreenInstruction::ResizeLeft => { + screen.get_active_tab_mut().unwrap().resize_left(); + } + ScreenInstruction::ResizeRight => { + screen.get_active_tab_mut().unwrap().resize_right(); + } + ScreenInstruction::ResizeDown => { + screen.get_active_tab_mut().unwrap().resize_down(); + } + ScreenInstruction::ResizeUp => { + screen.get_active_tab_mut().unwrap().resize_up(); + } + ScreenInstruction::SwitchFocus => { + screen.get_active_tab_mut().unwrap().move_focus(); + } + ScreenInstruction::FocusNextPane => { + screen.get_active_tab_mut().unwrap().focus_next_pane(); + } + ScreenInstruction::FocusPreviousPane => { + screen.get_active_tab_mut().unwrap().focus_previous_pane(); + } + ScreenInstruction::MoveFocusLeft => { + screen.get_active_tab_mut().unwrap().move_focus_left(); + } + ScreenInstruction::MoveFocusDown => { + screen.get_active_tab_mut().unwrap().move_focus_down(); + } + ScreenInstruction::MoveFocusRight => { + screen.get_active_tab_mut().unwrap().move_focus_right(); + } + 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::ToggleActiveSyncPanes => { + screen + .get_active_tab_mut() + .unwrap() + .toggle_sync_panes_is_active(); + screen.update_tabs(); + } + ScreenInstruction::Exit => { + break; + } + } + } +} diff --git a/src/common/thread_bus.rs b/src/common/thread_bus.rs new file mode 100644 index 00000000..8827d068 --- /dev/null +++ b/src/common/thread_bus.rs @@ -0,0 +1,142 @@ +//! Definitions and helpers for sending and receiving messages between threads. + +use async_std::task_local; +use std::cell::RefCell; +use std::sync::mpsc; + +use crate::common::pty::PtyInstruction; +use crate::common::ServerInstruction; +use crate::errors::{get_current_ctx, ErrorContext}; +use crate::os_input_output::ServerOsApi; +use crate::screen::ScreenInstruction; +use crate::wasm_vm::PluginInstruction; + +/// An [MPSC](mpsc) asynchronous channel with added error context. +pub type ChannelWithContext = ( + mpsc::Sender<(T, ErrorContext)>, + mpsc::Receiver<(T, ErrorContext)>, +); +/// An [MPSC](mpsc) synchronous channel with added error context. +pub type SyncChannelWithContext = ( + 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 { + /// 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 { + sender: SenderType, +} + +impl SenderWithContext { + pub fn new(sender: SenderType) -> 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 Send for SenderWithContext {} +unsafe impl Sync for SenderWithContext {} + +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 = 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 = 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>, + pub to_pty: Option>, + pub to_plugin: Option>, + pub to_server: Option>, +} + +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 { + pub receiver: mpsc::Receiver<(T, ErrorContext)>, + pub senders: ThreadSenders, + pub os_input: Option>, +} + +impl Bus { + pub fn new( + receiver: mpsc::Receiver<(T, ErrorContext)>, + to_screen: Option<&SenderWithContext>, + to_pty: Option<&SenderWithContext>, + to_plugin: Option<&SenderWithContext>, + to_server: Option<&SenderWithContext>, + os_input: Option>, + ) -> 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/wasm_vm.rs b/src/common/wasm_vm.rs index db52c2bb..8d29b6a9 100644 --- a/src/common/wasm_vm.rs +++ b/src/common/wasm_vm.rs @@ -1,17 +1,28 @@ use serde::{de::DeserializeOwned, Serialize}; use std::{ - collections::HashSet, + collections::{HashMap, HashSet}, + fs, path::PathBuf, process, + str::FromStr, sync::{mpsc::Sender, Arc, Mutex}, thread, time::{Duration, Instant}, }; -use wasmer::{imports, Function, ImportObject, Store, WasmerEnv}; -use wasmer_wasi::WasiEnv; +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 super::{pty_bus::PtyInstruction, screen::ScreenInstruction, PaneId, SenderWithContext}; +use super::{ + errors::{ContextType, PluginContext}, + pty::PtyInstruction, + screen::ScreenInstruction, + thread_bus::{Bus, ThreadSenders}, + PaneId, +}; #[derive(Clone, Debug)] pub enum PluginInstruction { @@ -25,14 +36,97 @@ pub enum PluginInstruction { #[derive(WasmerEnv, Clone)] pub struct PluginEnv { pub plugin_id: u32, - // FIXME: This should be a big bundle of all of the channels - pub send_screen_instructions: SenderWithContext, - pub send_pty_instructions: SenderWithContext, - pub send_plugin_instructions: SenderWithContext, + pub senders: ThreadSenders, pub wasi_env: WasiEnv, pub subscriptions: Arc>>, } +// Thread main -------------------------------------------------------------------------------------------------------- +pub fn wasm_thread_main(bus: Bus, store: Store, data_dir: PathBuf) { + let mut plugin_id = 0; + let mut plugin_map = HashMap::new(); + loop { + let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel"); + err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event))); + match event { + PluginInstruction::Load(pid_tx, path) => { + let plugin_dir = data_dir.join("plugins/"); + let wasm_bytes = fs::read(&path) + .or_else(|_| fs::read(&path.with_extension("wasm"))) + .or_else(|_| fs::read(&plugin_dir.join(&path).with_extension("wasm"))) + .unwrap_or_else(|_| panic!("cannot find plugin {}", &path.display())); + + // FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that + let module = Module::new(&store, &wasm_bytes).unwrap(); + + let output = Pipe::new(); + let input = Pipe::new(); + let mut wasi_env = WasiState::new("Zellij") + .env("CLICOLOR_FORCE", "1") + .preopen(|p| { + p.directory(".") // FIXME: Change this to a more meaningful dir + .alias(".") + .read(true) + .write(true) + .create(true) + }) + .unwrap() + .stdin(Box::new(input)) + .stdout(Box::new(output)) + .finalize() + .unwrap(); + + let wasi = wasi_env.import_object(&module).unwrap(); + + let plugin_env = PluginEnv { + plugin_id, + senders: bus.senders.clone(), + wasi_env, + subscriptions: Arc::new(Mutex::new(HashSet::new())), + }; + + let zellij = zellij_exports(&store, &plugin_env); + let instance = Instance::new(&module, &zellij.chain_back(wasi)).unwrap(); + + let start = instance.exports.get_function("_start").unwrap(); + + // This eventually calls the `.load()` method + start.call(&[]).unwrap(); + + plugin_map.insert(plugin_id, (instance, plugin_env)); + pid_tx.send(plugin_id).unwrap(); + plugin_id += 1; + } + PluginInstruction::Update(pid, event) => { + for (&i, (instance, plugin_env)) in &plugin_map { + let subs = plugin_env.subscriptions.lock().unwrap(); + // FIXME: This is very janky... Maybe I should write my own macro for Event -> EventType? + let event_type = EventType::from_str(&event.to_string()).unwrap(); + if (pid.is_none() || pid == Some(i)) && subs.contains(&event_type) { + let update = instance.exports.get_function("update").unwrap(); + wasi_write_object(&plugin_env.wasi_env, &event); + update.call(&[]).unwrap(); + } + } + drop(bus.senders.send_to_screen(ScreenInstruction::Render)); + } + PluginInstruction::Render(buf_tx, pid, rows, cols) => { + let (instance, plugin_env) = plugin_map.get(&pid).unwrap(); + + let render = instance.exports.get_function("render").unwrap(); + + render + .call(&[Value::I32(rows as i32), Value::I32(cols as i32)]) + .unwrap(); + + buf_tx.send(wasi_read_string(&plugin_env.wasi_env)).unwrap(); + } + PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)), + PluginInstruction::Exit => break, + } + } +} + // Plugin API --------------------------------------------------------------------------------------------------------- pub fn zellij_exports(store: &Store, plugin_env: &PluginEnv) -> ImportObject { @@ -74,8 +168,8 @@ fn host_unsubscribe(plugin_env: &PluginEnv) { fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) { let selectable = selectable != 0; plugin_env - .send_screen_instructions - .send(ScreenInstruction::SetSelectable( + .senders + .send_to_screen(ScreenInstruction::SetSelectable( PaneId::Plugin(plugin_env.plugin_id), selectable, )) @@ -85,8 +179,8 @@ fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) { fn host_set_max_height(plugin_env: &PluginEnv, max_height: i32) { let max_height = max_height as usize; plugin_env - .send_screen_instructions - .send(ScreenInstruction::SetMaxHeight( + .senders + .send_to_screen(ScreenInstruction::SetMaxHeight( PaneId::Plugin(plugin_env.plugin_id), max_height, )) @@ -96,8 +190,8 @@ fn host_set_max_height(plugin_env: &PluginEnv, max_height: i32) { fn host_set_invisible_borders(plugin_env: &PluginEnv, invisible_borders: i32) { let invisible_borders = invisible_borders != 0; plugin_env - .send_screen_instructions - .send(ScreenInstruction::SetInvisibleBorders( + .senders + .send_to_screen(ScreenInstruction::SetInvisibleBorders( PaneId::Plugin(plugin_env.plugin_id), invisible_borders, )) @@ -115,8 +209,8 @@ fn host_get_plugin_ids(plugin_env: &PluginEnv) { fn host_open_file(plugin_env: &PluginEnv) { let path: PathBuf = wasi_read_object(&plugin_env.wasi_env); plugin_env - .send_pty_instructions - .send(PtyInstruction::SpawnTerminal(Some(path))) + .senders + .send_to_pty(PtyInstruction::SpawnTerminal(Some(path))) .unwrap(); } @@ -130,7 +224,7 @@ fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) { // timers as we'd like. // // But that's a lot of code, and this is a few lines: - let send_plugin_instructions = plugin_env.send_plugin_instructions.clone(); + let send_plugin_instructions = plugin_env.senders.to_plugin.clone(); let update_target = Some(plugin_env.plugin_id); thread::spawn(move || { let start_time = Instant::now(); @@ -140,6 +234,7 @@ fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) { let elapsed_time = Instant::now().duration_since(start_time).as_secs_f64(); send_plugin_instructions + .unwrap() .send(PluginInstruction::Update( update_target, Event::Timer(elapsed_time), diff --git a/src/main.rs b/src/main.rs index 44b98549..12998d4d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,9 +6,7 @@ mod server; mod tests; use client::{boundaries, layout, panes, start_client, tab}; -use common::{ - command_is_executing, errors, os_input_output, pty_bus, screen, setup, utils, wasm_vm, -}; +use common::{command_is_executing, errors, os_input_output, pty, screen, setup, utils, wasm_vm}; use server::start_server; use structopt::StructOpt; diff --git a/src/server/mod.rs b/src/server/mod.rs index a90831d9..6c067e90 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,35 +1,29 @@ +pub mod route; + use interprocess::local_socket::LocalSocketListener; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; -use std::sync::mpsc::channel; +use std::sync::{Arc, RwLock}; use std::thread; -use std::{collections::HashMap, fs}; -use std::{ - collections::HashSet, - str::FromStr, - sync::{Arc, Mutex, RwLock}, -}; -use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value}; -use wasmer_wasi::{Pipe, WasiState}; -use zellij_tile::data::{Event, EventType, InputMode, ModeInfo}; +use std::{path::PathBuf, sync::mpsc::channel}; +use wasmer::Store; use crate::cli::CliArgs; use crate::client::ClientInstruction; +use crate::common::thread_bus::{Bus, ThreadSenders}; use crate::common::{ - errors::{ContextType, PluginContext, PtyContext, ScreenContext, ServerContext}, - input::actions::{Action, Direction}, - input::handler::get_mode_info, + errors::{ContextType, ServerContext}, + input::actions::Action, os_input_output::{set_permissions, ServerOsApi}, - pty_bus::{PtyBus, PtyInstruction}, - screen::{Screen, ScreenInstruction}, + pty::{pty_thread_main, Pty, PtyInstruction}, + screen::{screen_thread_main, ScreenInstruction}, setup::install::populate_data_dir, + thread_bus::{ChannelWithContext, SenderType, SenderWithContext}, utils::consts::{ZELLIJ_IPC_PIPE, ZELLIJ_PROJ_DIR}, - wasm_vm::{wasi_read_string, wasi_write_object, zellij_exports, PluginEnv, PluginInstruction}, - ChannelWithContext, SenderType, SenderWithContext, + wasm_vm::{wasm_thread_main, PluginInstruction}, }; use crate::layout::Layout; -use crate::panes::PaneId; use crate::panes::PositionAndSize; +use route::route_thread_main; /// Instructions related to server-side application including the /// ones sent by client to server @@ -43,10 +37,8 @@ pub enum ServerInstruction { ClientExit, } -struct SessionMetaData { - pub send_pty_instructions: SenderWithContext, - pub send_screen_instructions: SenderWithContext, - pub send_plugin_instructions: SenderWithContext, +pub struct SessionMetaData { + pub senders: ThreadSenders, screen_thread: Option>, pty_thread: Option>, wasm_thread: Option>, @@ -54,9 +46,9 @@ struct SessionMetaData { impl Drop for SessionMetaData { fn drop(&mut self) { - let _ = self.send_pty_instructions.send(PtyInstruction::Exit); - let _ = self.send_screen_instructions.send(ScreenInstruction::Exit); - let _ = self.send_plugin_instructions.send(PluginInstruction::Exit); + let _ = self.senders.send_to_pty(PtyInstruction::Exit); + let _ = self.senders.send_to_screen(ScreenInstruction::Exit); + let _ = self.senders.send_to_plugin(PluginInstruction::Exit); let _ = self.screen_thread.take().unwrap().join(); let _ = self.pty_thread.take().unwrap().join(); let _ = self.wasm_thread.take().unwrap().join(); @@ -64,26 +56,27 @@ impl Drop for SessionMetaData { } pub fn start_server(os_input: Box) -> thread::JoinHandle<()> { - let (send_server_instructions, receive_server_instructions): ChannelWithContext< - ServerInstruction, - > = channel(); - let send_server_instructions = - SenderWithContext::new(SenderType::Sender(send_server_instructions)); + let (to_server, server_receiver): ChannelWithContext = channel(); + let to_server = SenderWithContext::new(SenderType::Sender(to_server)); let sessions: Arc>> = Arc::new(RwLock::new(None)); #[cfg(test)] - handle_client( - sessions.clone(), - os_input.clone(), - send_server_instructions.clone(), - ); + thread::Builder::new() + .name("server_router".to_string()) + .spawn({ + let sessions = sessions.clone(); + let os_input = os_input.clone(); + let to_server = to_server.clone(); + + move || route_thread_main(sessions, os_input, to_server) + }); #[cfg(not(test))] let _ = thread::Builder::new() .name("server_listener".to_string()) .spawn({ let os_input = os_input.clone(); let sessions = sessions.clone(); - let send_server_instructions = send_server_instructions.clone(); + let to_server = to_server.clone(); move || { drop(std::fs::remove_file(&*ZELLIJ_IPC_PIPE)); let listener = LocalSocketListener::bind(&**ZELLIJ_IPC_PIPE).unwrap(); @@ -94,8 +87,17 @@ pub fn start_server(os_input: Box) -> thread::JoinHandle<()> { let mut os_input = os_input.clone(); os_input.update_receiver(stream); let sessions = sessions.clone(); - let send_server_instructions = send_server_instructions.clone(); - handle_client(sessions, os_input, send_server_instructions); + let to_server = to_server.clone(); + thread::Builder::new() + .name("server_router".to_string()) + .spawn({ + let sessions = sessions.clone(); + let os_input = os_input.clone(); + let to_server = to_server.clone(); + + move || route_thread_main(sessions, os_input, to_server) + }) + .unwrap(); } Err(err) => { panic!("err {:?}", err); @@ -109,24 +111,20 @@ pub fn start_server(os_input: Box) -> thread::JoinHandle<()> { .name("server_thread".to_string()) .spawn({ move || loop { - let (instruction, mut err_ctx) = receive_server_instructions.recv().unwrap(); + let (instruction, mut err_ctx) = server_receiver.recv().unwrap(); err_ctx.add_call(ContextType::IPCServer(ServerContext::from(&instruction))); match instruction { ServerInstruction::NewClient(full_screen_ws, opts) => { - let session_data = init_session( - os_input.clone(), - opts, - send_server_instructions.clone(), - full_screen_ws, - ); + let session_data = + init_session(os_input.clone(), opts, to_server.clone(), full_screen_ws); *sessions.write().unwrap() = Some(session_data); sessions .read() .unwrap() .as_ref() .unwrap() - .send_pty_instructions - .send(PtyInstruction::NewTab) + .senders + .send_to_pty(PtyInstruction::NewTab) .unwrap(); } ServerInstruction::UnblockInputThread => { @@ -148,65 +146,19 @@ pub fn start_server(os_input: Box) -> thread::JoinHandle<()> { .unwrap() } -fn handle_client( - sessions: Arc>>, - mut os_input: Box, - send_server_instructions: SenderWithContext, -) { - thread::Builder::new() - .name("server_router".to_string()) - .spawn(move || loop { - let (instruction, mut err_ctx) = os_input.recv_from_client(); - err_ctx.add_call(ContextType::IPCServer(ServerContext::from(&instruction))); - let rlocked_sessions = sessions.read().unwrap(); - match instruction { - ServerInstruction::ClientExit => { - send_server_instructions.send(instruction).unwrap(); - break; - } - ServerInstruction::Action(action) => { - route_action(action, rlocked_sessions.as_ref().unwrap(), &*os_input); - } - ServerInstruction::TerminalResize(new_size) => { - rlocked_sessions - .as_ref() - .unwrap() - .send_screen_instructions - .send(ScreenInstruction::TerminalResize(new_size)) - .unwrap(); - } - ServerInstruction::NewClient(..) => { - os_input.add_client_sender(); - send_server_instructions.send(instruction).unwrap(); - } - _ => { - send_server_instructions.send(instruction).unwrap(); - } - } - }) - .unwrap(); -} - fn init_session( os_input: Box, opts: CliArgs, - send_server_instructions: SenderWithContext, + to_server: SenderWithContext, full_screen_ws: PositionAndSize, ) -> SessionMetaData { - let (send_screen_instructions, receive_screen_instructions): ChannelWithContext< - ScreenInstruction, - > = channel(); - let send_screen_instructions = - SenderWithContext::new(SenderType::Sender(send_screen_instructions)); + let (to_screen, screen_receiver): ChannelWithContext = channel(); + let to_screen = SenderWithContext::new(SenderType::Sender(to_screen)); - let (send_plugin_instructions, receive_plugin_instructions): ChannelWithContext< - PluginInstruction, - > = channel(); - let send_plugin_instructions = - SenderWithContext::new(SenderType::Sender(send_plugin_instructions)); - let (send_pty_instructions, receive_pty_instructions): ChannelWithContext = - channel(); - let send_pty_instructions = SenderWithContext::new(SenderType::Sender(send_pty_instructions)); + let (to_plugin, plugin_receiver): ChannelWithContext = channel(); + let to_plugin = SenderWithContext::new(SenderType::Sender(to_plugin)); + let (to_pty, pty_receiver): ChannelWithContext = channel(); + let to_pty = SenderWithContext::new(SenderType::Sender(to_pty)); // Determine and initialize the data directory let data_dir = opts @@ -224,322 +176,40 @@ fn init_session( .map(|p| Layout::new(&p, &data_dir)) .or_else(|| default_layout.map(|p| Layout::from_defaults(&p, &data_dir))); - let mut pty_bus = PtyBus::new( - receive_pty_instructions, - send_screen_instructions.clone(), - send_plugin_instructions.clone(), - os_input.clone(), - opts.debug, - ); - let pty_thread = thread::Builder::new() .name("pty".to_string()) .spawn({ - let send_server_instructions = send_server_instructions.clone(); - move || loop { - let (event, mut err_ctx) = pty_bus - .receive_pty_instructions - .recv() - .expect("failed to receive event on channel"); - err_ctx.add_call(ContextType::Pty(PtyContext::from(&event))); - match event { - PtyInstruction::SpawnTerminal(file_to_open) => { - let pid = pty_bus.spawn_terminal(file_to_open); - pty_bus - .send_screen_instructions - .send(ScreenInstruction::NewPane(PaneId::Terminal(pid))) - .unwrap(); - } - PtyInstruction::SpawnTerminalVertically(file_to_open) => { - let pid = pty_bus.spawn_terminal(file_to_open); - pty_bus - .send_screen_instructions - .send(ScreenInstruction::VerticalSplit(PaneId::Terminal(pid))) - .unwrap(); - } - PtyInstruction::SpawnTerminalHorizontally(file_to_open) => { - let pid = pty_bus.spawn_terminal(file_to_open); - pty_bus - .send_screen_instructions - .send(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid))) - .unwrap(); - } - PtyInstruction::NewTab => { - if let Some(layout) = maybe_layout.clone() { - pty_bus.spawn_terminals_for_layout(layout); - } else { - let pid = pty_bus.spawn_terminal(None); - pty_bus - .send_screen_instructions - .send(ScreenInstruction::NewTab(pid)) - .unwrap(); - } - } - PtyInstruction::ClosePane(id) => { - pty_bus.close_pane(id); - send_server_instructions - .send(ServerInstruction::UnblockInputThread) - .unwrap(); - } - PtyInstruction::CloseTab(ids) => { - pty_bus.close_tab(ids); - send_server_instructions - .send(ServerInstruction::UnblockInputThread) - .unwrap(); - } - PtyInstruction::Exit => { - break; - } - } - } + let pty = Pty::new( + Bus::new( + pty_receiver, + Some(&to_screen), + None, + Some(&to_plugin), + None, + Some(os_input.clone()), + ), + opts.debug, + ); + + move || pty_thread_main(pty, maybe_layout) }) .unwrap(); let screen_thread = thread::Builder::new() .name("screen".to_string()) .spawn({ - let os_input = os_input.clone(); - let send_plugin_instructions = send_plugin_instructions.clone(); - let send_pty_instructions = send_pty_instructions.clone(); - let send_server_instructions = send_server_instructions; + let screen_bus = Bus::new( + screen_receiver, + None, + Some(&to_pty), + Some(&to_plugin), + Some(&to_server), + Some(os_input.clone()), + ); let max_panes = opts.max_panes; - let colors = os_input.load_palette(); move || { - let mut screen = Screen::new( - receive_screen_instructions, - send_plugin_instructions, - send_pty_instructions, - send_server_instructions, - &full_screen_ws, - os_input, - max_panes, - ModeInfo { - palette: colors, - ..ModeInfo::default() - }, - InputMode::Normal, - colors, - ); - loop { - let (event, mut err_ctx) = screen - .receiver - .recv() - .expect("failed to receive event on channel"); - err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event))); - match event { - ScreenInstruction::PtyBytes(pid, vte_bytes) => { - let active_tab = screen.get_active_tab_mut().unwrap(); - if active_tab.has_terminal_pid(pid) { - // it's most likely that this event is directed at the active tab - // look there first - active_tab.handle_pty_bytes(pid, vte_bytes); - } else { - // if this event wasn't directed at the active tab, start looking - // in other tabs - let all_tabs = screen.get_tabs_mut(); - for tab in all_tabs.values_mut() { - if tab.has_terminal_pid(pid) { - tab.handle_pty_bytes(pid, vte_bytes); - break; - } - } - } - } - ScreenInstruction::Render => { - screen.render(); - } - ScreenInstruction::NewPane(pid) => { - screen.get_active_tab_mut().unwrap().new_pane(pid); - screen - .send_server_instructions - .send(ServerInstruction::UnblockInputThread) - .unwrap(); - } - ScreenInstruction::HorizontalSplit(pid) => { - screen.get_active_tab_mut().unwrap().horizontal_split(pid); - screen - .send_server_instructions - .send(ServerInstruction::UnblockInputThread) - .unwrap(); - } - ScreenInstruction::VerticalSplit(pid) => { - screen.get_active_tab_mut().unwrap().vertical_split(pid); - screen - .send_server_instructions - .send(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::MoveFocusDown => { - screen.get_active_tab_mut().unwrap().move_focus_down(); - } - ScreenInstruction::MoveFocusRight => { - screen.get_active_tab_mut().unwrap().move_focus_right(); - } - 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 - .send_server_instructions - .send(ServerInstruction::UnblockInputThread) - .unwrap(); - } - ScreenInstruction::SwitchTabNext => { - screen.switch_tab_next(); - screen - .send_server_instructions - .send(ServerInstruction::UnblockInputThread) - .unwrap(); - } - ScreenInstruction::SwitchTabPrev => { - screen.switch_tab_prev(); - screen - .send_server_instructions - .send(ServerInstruction::UnblockInputThread) - .unwrap(); - } - ScreenInstruction::CloseTab => { - screen.close_tab(); - screen - .send_server_instructions - .send(ServerInstruction::UnblockInputThread) - .unwrap(); - } - ScreenInstruction::ApplyLayout(layout, new_pane_pids) => { - screen.apply_layout(layout, new_pane_pids); - screen - .send_server_instructions - .send(ServerInstruction::UnblockInputThread) - .unwrap(); - } - ScreenInstruction::GoToTab(tab_index) => { - screen.go_to_tab(tab_index as usize); - screen - .send_server_instructions - .send(ServerInstruction::UnblockInputThread) - .unwrap(); - } - ScreenInstruction::UpdateTabName(c) => { - screen.update_active_tab_name(c); - screen - .send_server_instructions - .send(ServerInstruction::UnblockInputThread) - .unwrap(); - } - 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_tab_is_active(); - screen.update_tabs(); - } - ScreenInstruction::Exit => { - break; - } - } - } + screen_thread_main(screen_bus, max_panes, full_screen_ws); } }) .unwrap(); @@ -547,265 +217,28 @@ fn init_session( let wasm_thread = thread::Builder::new() .name("wasm".to_string()) .spawn({ - let send_screen_instructions = send_screen_instructions.clone(); - let send_pty_instructions = send_pty_instructions.clone(); - let send_plugin_instructions = send_plugin_instructions.clone(); - + let plugin_bus = Bus::new( + plugin_receiver, + Some(&to_screen), + Some(&to_pty), + Some(&to_plugin), + Some(&to_server), + None, + ); let store = Store::default(); - let mut plugin_id = 0; - let mut plugin_map = HashMap::new(); - move || loop { - let (event, mut err_ctx) = receive_plugin_instructions - .recv() - .expect("failed to receive event on channel"); - err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event))); - match event { - PluginInstruction::Load(pid_tx, path) => { - let plugin_dir = data_dir.join("plugins/"); - let wasm_bytes = fs::read(&path) - .or_else(|_| fs::read(&path.with_extension("wasm"))) - .or_else(|_| fs::read(&plugin_dir.join(&path).with_extension("wasm"))) - .unwrap_or_else(|_| panic!("cannot find plugin {}", &path.display())); - // FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that - let module = Module::new(&store, &wasm_bytes).unwrap(); - - let output = Pipe::new(); - let input = Pipe::new(); - let mut wasi_env = WasiState::new("Zellij") - .env("CLICOLOR_FORCE", "1") - .preopen(|p| { - p.directory(".") // FIXME: Change this to a more meaningful dir - .alias(".") - .read(true) - .write(true) - .create(true) - }) - .unwrap() - .stdin(Box::new(input)) - .stdout(Box::new(output)) - .finalize() - .unwrap(); - - let wasi = wasi_env.import_object(&module).unwrap(); - - let plugin_env = PluginEnv { - plugin_id, - send_screen_instructions: send_screen_instructions.clone(), - send_pty_instructions: send_pty_instructions.clone(), - send_plugin_instructions: send_plugin_instructions.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 `.init()` 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(send_screen_instructions.send(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, - } - } + move || wasm_thread_main(plugin_bus, store, data_dir) }) .unwrap(); SessionMetaData { - send_plugin_instructions, - send_screen_instructions, - send_pty_instructions, + senders: ThreadSenders { + to_screen: Some(to_screen), + to_pty: Some(to_pty), + to_plugin: Some(to_plugin), + to_server: None, + }, screen_thread: Some(screen_thread), pty_thread: Some(pty_thread), wasm_thread: Some(wasm_thread), } } - -fn route_action(action: Action, session: &SessionMetaData, os_input: &dyn ServerOsApi) { - match action { - Action::Write(val) => { - session - .send_screen_instructions - .send(ScreenInstruction::ClearScroll) - .unwrap(); - session - .send_screen_instructions - .send(ScreenInstruction::WriteCharacter(val)) - .unwrap(); - } - Action::SwitchToMode(mode) => { - let palette = os_input.load_palette(); - session - .send_plugin_instructions - .send(PluginInstruction::Update( - None, - Event::ModeUpdate(get_mode_info(mode, palette)), - )) - .unwrap(); - session - .send_screen_instructions - .send(ScreenInstruction::ChangeMode(get_mode_info(mode, palette))) - .unwrap(); - session - .send_screen_instructions - .send(ScreenInstruction::Render) - .unwrap(); - } - Action::Resize(direction) => { - let screen_instr = match direction { - Direction::Left => ScreenInstruction::ResizeLeft, - Direction::Right => ScreenInstruction::ResizeRight, - Direction::Up => ScreenInstruction::ResizeUp, - Direction::Down => ScreenInstruction::ResizeDown, - }; - session.send_screen_instructions.send(screen_instr).unwrap(); - } - Action::SwitchFocus => { - session - .send_screen_instructions - .send(ScreenInstruction::SwitchFocus) - .unwrap(); - } - Action::FocusNextPane => { - session - .send_screen_instructions - .send(ScreenInstruction::FocusNextPane) - .unwrap(); - } - Action::FocusPreviousPane => { - session - .send_screen_instructions - .send(ScreenInstruction::FocusPreviousPane) - .unwrap(); - } - Action::MoveFocus(direction) => { - let screen_instr = match direction { - Direction::Left => ScreenInstruction::MoveFocusLeft, - Direction::Right => ScreenInstruction::MoveFocusRight, - Direction::Up => ScreenInstruction::MoveFocusUp, - Direction::Down => ScreenInstruction::MoveFocusDown, - }; - session.send_screen_instructions.send(screen_instr).unwrap(); - } - Action::ScrollUp => { - session - .send_screen_instructions - .send(ScreenInstruction::ScrollUp) - .unwrap(); - } - Action::ScrollDown => { - session - .send_screen_instructions - .send(ScreenInstruction::ScrollDown) - .unwrap(); - } - Action::PageScrollUp => { - session - .send_screen_instructions - .send(ScreenInstruction::PageScrollUp) - .unwrap(); - } - Action::PageScrollDown => { - session - .send_screen_instructions - .send(ScreenInstruction::PageScrollDown) - .unwrap(); - } - Action::ToggleFocusFullscreen => { - session - .send_screen_instructions - .send(ScreenInstruction::ToggleActiveTerminalFullscreen) - .unwrap(); - } - Action::NewPane(direction) => { - let pty_instr = match direction { - Some(Direction::Left) => PtyInstruction::SpawnTerminalVertically(None), - Some(Direction::Right) => PtyInstruction::SpawnTerminalVertically(None), - Some(Direction::Up) => PtyInstruction::SpawnTerminalHorizontally(None), - Some(Direction::Down) => PtyInstruction::SpawnTerminalHorizontally(None), - // No direction specified - try to put it in the biggest available spot - None => PtyInstruction::SpawnTerminal(None), - }; - session.send_pty_instructions.send(pty_instr).unwrap(); - } - Action::CloseFocus => { - session - .send_screen_instructions - .send(ScreenInstruction::CloseFocusedPane) - .unwrap(); - } - Action::NewTab => { - session - .send_pty_instructions - .send(PtyInstruction::NewTab) - .unwrap(); - } - Action::GoToNextTab => { - session - .send_screen_instructions - .send(ScreenInstruction::SwitchTabNext) - .unwrap(); - } - Action::GoToPreviousTab => { - session - .send_screen_instructions - .send(ScreenInstruction::SwitchTabPrev) - .unwrap(); - } - Action::ToggleActiveSyncTab => { - session - .send_screen_instructions - .send(ScreenInstruction::ToggleActiveSyncTab) - .unwrap(); - } - Action::CloseTab => { - session - .send_screen_instructions - .send(ScreenInstruction::CloseTab) - .unwrap(); - } - Action::GoToTab(i) => { - session - .send_screen_instructions - .send(ScreenInstruction::GoToTab(i)) - .unwrap(); - } - Action::TabNameInput(c) => { - session - .send_screen_instructions - .send(ScreenInstruction::UpdateTabName(c)) - .unwrap(); - } - Action::NoOp => {} - Action::Quit => panic!("Received unexpected action"), - } -} diff --git a/src/server/route.rs b/src/server/route.rs new file mode 100644 index 00000000..264f62bb --- /dev/null +++ b/src/server/route.rs @@ -0,0 +1,205 @@ +use std::sync::{Arc, RwLock}; + +use zellij_tile::data::Event; + +use crate::common::errors::{ContextType, ServerContext}; +use crate::common::input::actions::Action; +use crate::common::input::{actions::Direction, handler::get_mode_info}; +use crate::common::os_input_output::ServerOsApi; +use crate::common::pty::PtyInstruction; +use crate::common::screen::ScreenInstruction; +use crate::common::thread_bus::SenderWithContext; +use crate::common::wasm_vm::PluginInstruction; +use crate::server::{ServerInstruction, SessionMetaData}; + +fn route_action(action: Action, session: &SessionMetaData, os_input: &dyn ServerOsApi) { + match action { + Action::Write(val) => { + session + .senders + .send_to_screen(ScreenInstruction::ClearScroll) + .unwrap(); + session + .senders + .send_to_screen(ScreenInstruction::WriteCharacter(val)) + .unwrap(); + } + Action::SwitchToMode(mode) => { + let palette = os_input.load_palette(); + session + .senders + .send_to_plugin(PluginInstruction::Update( + None, + Event::ModeUpdate(get_mode_info(mode, palette)), + )) + .unwrap(); + session + .senders + .send_to_screen(ScreenInstruction::ChangeMode(get_mode_info(mode, palette))) + .unwrap(); + session + .senders + .send_to_screen(ScreenInstruction::Render) + .unwrap(); + } + Action::Resize(direction) => { + let screen_instr = match direction { + Direction::Left => ScreenInstruction::ResizeLeft, + Direction::Right => ScreenInstruction::ResizeRight, + Direction::Up => ScreenInstruction::ResizeUp, + Direction::Down => ScreenInstruction::ResizeDown, + }; + session.senders.send_to_screen(screen_instr).unwrap(); + } + Action::SwitchFocus => { + session + .senders + .send_to_screen(ScreenInstruction::SwitchFocus) + .unwrap(); + } + Action::FocusNextPane => { + session + .senders + .send_to_screen(ScreenInstruction::FocusNextPane) + .unwrap(); + } + Action::FocusPreviousPane => { + session + .senders + .send_to_screen(ScreenInstruction::FocusPreviousPane) + .unwrap(); + } + Action::MoveFocus(direction) => { + let screen_instr = match direction { + Direction::Left => ScreenInstruction::MoveFocusLeft, + Direction::Right => ScreenInstruction::MoveFocusRight, + Direction::Up => ScreenInstruction::MoveFocusUp, + Direction::Down => ScreenInstruction::MoveFocusDown, + }; + session.senders.send_to_screen(screen_instr).unwrap(); + } + Action::ScrollUp => { + session + .senders + .send_to_screen(ScreenInstruction::ScrollUp) + .unwrap(); + } + Action::ScrollDown => { + session + .senders + .send_to_screen(ScreenInstruction::ScrollDown) + .unwrap(); + } + Action::PageScrollUp => { + session + .senders + .send_to_screen(ScreenInstruction::PageScrollUp) + .unwrap(); + } + Action::PageScrollDown => { + session + .senders + .send_to_screen(ScreenInstruction::PageScrollDown) + .unwrap(); + } + Action::ToggleFocusFullscreen => { + session + .senders + .send_to_screen(ScreenInstruction::ToggleActiveTerminalFullscreen) + .unwrap(); + } + Action::NewPane(direction) => { + let pty_instr = match direction { + Some(Direction::Left) => PtyInstruction::SpawnTerminalVertically(None), + Some(Direction::Right) => PtyInstruction::SpawnTerminalVertically(None), + Some(Direction::Up) => PtyInstruction::SpawnTerminalHorizontally(None), + Some(Direction::Down) => PtyInstruction::SpawnTerminalHorizontally(None), + // No direction specified - try to put it in the biggest available spot + None => PtyInstruction::SpawnTerminal(None), + }; + session.senders.send_to_pty(pty_instr).unwrap(); + } + Action::CloseFocus => { + session + .senders + .send_to_screen(ScreenInstruction::CloseFocusedPane) + .unwrap(); + } + Action::NewTab => { + session.senders.send_to_pty(PtyInstruction::NewTab).unwrap(); + } + Action::GoToNextTab => { + session + .senders + .send_to_screen(ScreenInstruction::SwitchTabNext) + .unwrap(); + } + Action::GoToPreviousTab => { + session + .senders + .send_to_screen(ScreenInstruction::SwitchTabPrev) + .unwrap(); + } + Action::ToggleActiveSyncPanes => { + session + .senders + .send_to_screen(ScreenInstruction::ToggleActiveSyncPanes) + .unwrap(); + } + Action::CloseTab => { + session + .senders + .send_to_screen(ScreenInstruction::CloseTab) + .unwrap(); + } + Action::GoToTab(i) => { + session + .senders + .send_to_screen(ScreenInstruction::GoToTab(i)) + .unwrap(); + } + Action::TabNameInput(c) => { + session + .senders + .send_to_screen(ScreenInstruction::UpdateTabName(c)) + .unwrap(); + } + Action::NoOp => {} + Action::Quit => panic!("Received unexpected action"), + } +} +pub fn route_thread_main( + sessions: Arc>>, + mut os_input: Box, + to_server: SenderWithContext, +) { + loop { + let (instruction, mut err_ctx) = os_input.recv_from_client(); + err_ctx.add_call(ContextType::IPCServer(ServerContext::from(&instruction))); + let rlocked_sessions = sessions.read().unwrap(); + match instruction { + ServerInstruction::ClientExit => { + to_server.send(instruction).unwrap(); + break; + } + ServerInstruction::Action(action) => { + route_action(action, rlocked_sessions.as_ref().unwrap(), &*os_input); + } + ServerInstruction::TerminalResize(new_size) => { + rlocked_sessions + .as_ref() + .unwrap() + .senders + .send_to_screen(ScreenInstruction::TerminalResize(new_size)) + .unwrap(); + } + ServerInstruction::NewClient(..) => { + os_input.add_client_sender(); + to_server.send(instruction).unwrap(); + } + _ => { + to_server.send(instruction).unwrap(); + } + } + } +} diff --git a/src/tests/fakes.rs b/src/tests/fakes.rs index 4c780410..eb0d14e8 100644 --- a/src/tests/fakes.rs +++ b/src/tests/fakes.rs @@ -8,7 +8,7 @@ use std::sync::{mpsc, Arc, Condvar, Mutex}; use std::time::{Duration, Instant}; use crate::client::ClientInstruction; -use crate::common::{ChannelWithContext, SenderType, SenderWithContext}; +use crate::common::thread_bus::{ChannelWithContext, SenderType, SenderWithContext}; use crate::errors::ErrorContext; use crate::os_input_output::{ClientOsApi, ServerOsApi}; use crate::server::ServerInstruction; From 4fb4faa28dcbae09d60389ba1d8b062cccce8332 Mon Sep 17 00:00:00 2001 From: Kyle Sutherland-Cash Date: Sat, 8 May 2021 05:07:41 -0700 Subject: [PATCH 16/44] Markups from self-review --- src/common/pty.rs | 47 +++++++++++++++++++++---------------------- src/common/screen.rs | 3 ++- src/common/wasm_vm.rs | 31 +++++++++++++--------------- src/server/mod.rs | 8 ++++---- src/server/route.rs | 5 +++-- 5 files changed, 46 insertions(+), 48 deletions(-) diff --git a/src/common/pty.rs b/src/common/pty.rs index 483aa51e..191ab94d 100644 --- a/src/common/pty.rs +++ b/src/common/pty.rs @@ -1,22 +1,21 @@ -use ::async_std::stream::*; -use ::async_std::task; -use ::async_std::task::*; -use ::std::collections::HashMap; -use ::std::os::unix::io::RawFd; -use ::std::pin::*; -use ::std::time::{Duration, Instant}; +use async_std::stream::*; +use async_std::task; +use async_std::task::*; +use std::collections::HashMap; +use std::os::unix::io::RawFd; +use std::pin::*; +use std::time::{Duration, Instant}; use std::path::PathBuf; -use super::{screen::ScreenInstruction, thread_bus::SenderWithContext}; +use crate::client::panes::PaneId; +use crate::common::errors::{get_current_ctx, ContextType, PtyContext}; +use crate::common::screen::ScreenInstruction; +use crate::common::thread_bus::{Bus, ThreadSenders}; +use crate::layout::Layout; use crate::os_input_output::ServerOsApi; +use crate::server::ServerInstruction; use crate::utils::logging::debug_to_file; use crate::wasm_vm::PluginInstruction; -use crate::{ - common::thread_bus::Bus, - errors::{get_current_ctx, ContextType, PtyContext}, - panes::PaneId, -}; -use crate::{layout::Layout, server::ServerInstruction}; pub struct ReadFromPid { pid: RawFd, @@ -143,7 +142,7 @@ pub fn pty_thread_main(mut pty: Pty, maybe_layout: Option) { fn stream_terminal_bytes( pid: RawFd, - send_screen_instructions: SenderWithContext, + senders: ThreadSenders, os_input: Box, debug: bool, ) -> JoinHandle<()> { @@ -165,7 +164,7 @@ fn stream_terminal_bytes( } } if !bytes_is_empty { - let _ = send_screen_instructions.send(ScreenInstruction::PtyBytes(pid, bytes)); + let _ = senders.send_to_screen(ScreenInstruction::PtyBytes(pid, bytes)); // for UX reasons, if we got something on the wire, we only send the render notice if: // 1. there aren't any more bytes on the wire afterwards // 2. a certain period (currently 30ms) has elapsed since the last render @@ -176,7 +175,7 @@ fn stream_terminal_bytes( Some(receive_time) => { if receive_time.elapsed() > max_render_pause { pending_render = false; - let _ = send_screen_instructions.send(ScreenInstruction::Render); + let _ = senders.send_to_screen(ScreenInstruction::Render); last_byte_receive_time = Some(Instant::now()); } else { pending_render = true; @@ -190,21 +189,21 @@ fn stream_terminal_bytes( } else { if pending_render { pending_render = false; - let _ = send_screen_instructions.send(ScreenInstruction::Render); + let _ = senders.send_to_screen(ScreenInstruction::Render); } last_byte_receive_time = None; task::sleep(::std::time::Duration::from_millis(10)).await; } } - send_screen_instructions - .send(ScreenInstruction::Render) + senders + .send_to_screen(ScreenInstruction::Render) .unwrap(); #[cfg(not(test))] // this is a little hacky, and is because the tests end the file as soon as // we read everything, rather than hanging until there is new data // a better solution would be to fix the test fakes, but this will do for now - send_screen_instructions - .send(ScreenInstruction::ClosePane(PaneId::Terminal(pid))) + senders + .send_to_screen(ScreenInstruction::ClosePane(PaneId::Terminal(pid))) .unwrap(); } }) @@ -228,7 +227,7 @@ impl Pty { .spawn_terminal(file_to_open); let task_handle = stream_terminal_bytes( pid_primary, - self.bus.senders.to_screen.as_ref().unwrap().clone(), + self.bus.senders.clone(), self.bus.os_input.as_ref().unwrap().clone(), self.debug_to_file, ); @@ -255,7 +254,7 @@ impl Pty { for id in new_pane_pids { let task_handle = stream_terminal_bytes( id, - self.bus.senders.to_screen.as_ref().unwrap().clone(), + self.bus.senders.clone(), self.bus.os_input.as_ref().unwrap().clone(), self.debug_to_file, ); diff --git a/src/common/screen.rs b/src/common/screen.rs index 131b874d..e28694e8 100644 --- a/src/common/screen.rs +++ b/src/common/screen.rs @@ -7,11 +7,12 @@ use std::str; use crate::common::pty::{PtyInstruction, VteBytes}; use crate::common::thread_bus::Bus; use crate::errors::{ContextType, ScreenContext}; +use crate::layout::Layout; +use crate::panes::PaneId; use crate::panes::PositionAndSize; use crate::server::ServerInstruction; use crate::tab::Tab; use crate::wasm_vm::PluginInstruction; -use crate::{layout::Layout, panes::PaneId}; use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, TabInfo}; diff --git a/src/common/wasm_vm.rs b/src/common/wasm_vm.rs index 8d29b6a9..e5567285 100644 --- a/src/common/wasm_vm.rs +++ b/src/common/wasm_vm.rs @@ -1,14 +1,13 @@ +use std::collections::{HashMap, HashSet}; +use std::fs; +use std::path::PathBuf; +use std::process; +use std::str::FromStr; +use std::sync::{mpsc::Sender, Arc, Mutex}; +use std::thread; +use std::time::{Duration, Instant}; + use serde::{de::DeserializeOwned, Serialize}; -use std::{ - collections::{HashMap, HashSet}, - fs, - path::PathBuf, - process, - str::FromStr, - sync::{mpsc::Sender, Arc, Mutex}, - thread, - time::{Duration, Instant}, -}; use wasmer::{ imports, ChainableNamedResolver, Function, ImportObject, Instance, Module, Store, Value, WasmerEnv, @@ -16,13 +15,11 @@ use wasmer::{ use wasmer_wasi::{Pipe, WasiEnv, WasiState}; use zellij_tile::data::{Event, EventType, PluginIds}; -use super::{ - errors::{ContextType, PluginContext}, - pty::PtyInstruction, - screen::ScreenInstruction, - thread_bus::{Bus, ThreadSenders}, - PaneId, -}; +use crate::common::errors::{ContextType, PluginContext}; +use crate::common::pty::PtyInstruction; +use crate::common::screen::ScreenInstruction; +use crate::common::thread_bus::{Bus, ThreadSenders}; +use crate::common::PaneId; #[derive(Clone, Debug)] pub enum PluginInstruction { diff --git a/src/server/mod.rs b/src/server/mod.rs index 6c067e90..96638de8 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -69,7 +69,7 @@ pub fn start_server(os_input: Box) -> thread::JoinHandle<()> { let to_server = to_server.clone(); move || route_thread_main(sessions, os_input, to_server) - }); + }).unwrap(); #[cfg(not(test))] let _ = thread::Builder::new() .name("server_listener".to_string()) @@ -185,7 +185,7 @@ fn init_session( Some(&to_screen), None, Some(&to_plugin), - None, + Some(&to_server), Some(os_input.clone()), ), opts.debug, @@ -221,8 +221,8 @@ fn init_session( plugin_receiver, Some(&to_screen), Some(&to_pty), - Some(&to_plugin), - Some(&to_server), + None, + None, None, ); let store = Store::default(); diff --git a/src/server/route.rs b/src/server/route.rs index 264f62bb..753a1e12 100644 --- a/src/server/route.rs +++ b/src/server/route.rs @@ -3,8 +3,8 @@ use std::sync::{Arc, RwLock}; use zellij_tile::data::Event; use crate::common::errors::{ContextType, ServerContext}; -use crate::common::input::actions::Action; -use crate::common::input::{actions::Direction, handler::get_mode_info}; +use crate::common::input::actions::{Action, Direction}; +use crate::common::input::handler::get_mode_info; use crate::common::os_input_output::ServerOsApi; use crate::common::pty::PtyInstruction; use crate::common::screen::ScreenInstruction; @@ -168,6 +168,7 @@ fn route_action(action: Action, session: &SessionMetaData, os_input: &dyn Server Action::Quit => panic!("Received unexpected action"), } } + pub fn route_thread_main( sessions: Arc>>, mut os_input: Box, From ce92374f8ebb3543cb5805ec81e6605e23c6ef46 Mon Sep 17 00:00:00 2001 From: Kyle Sutherland-Cash Date: Sat, 8 May 2021 10:55:13 -0700 Subject: [PATCH 17/44] cargo fmt --- src/common/pty.rs | 6 ++---- src/server/mod.rs | 3 ++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/common/pty.rs b/src/common/pty.rs index 191ab94d..3c471d6a 100644 --- a/src/common/pty.rs +++ b/src/common/pty.rs @@ -3,9 +3,9 @@ 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 std::path::PathBuf; use crate::client::panes::PaneId; use crate::common::errors::{get_current_ctx, ContextType, PtyContext}; @@ -195,9 +195,7 @@ fn stream_terminal_bytes( task::sleep(::std::time::Duration::from_millis(10)).await; } } - senders - .send_to_screen(ScreenInstruction::Render) - .unwrap(); + senders.send_to_screen(ScreenInstruction::Render).unwrap(); #[cfg(not(test))] // this is a little hacky, and is because the tests end the file as soon as // we read everything, rather than hanging until there is new data diff --git a/src/server/mod.rs b/src/server/mod.rs index 96638de8..3655ed19 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -69,7 +69,8 @@ pub fn start_server(os_input: Box) -> thread::JoinHandle<()> { let to_server = to_server.clone(); move || route_thread_main(sessions, os_input, to_server) - }).unwrap(); + }) + .unwrap(); #[cfg(not(test))] let _ = thread::Builder::new() .name("server_listener".to_string()) From 74b54796298fea06fbbb2a070bd786e2628f5559 Mon Sep 17 00:00:00 2001 From: Kyle Sutherland-Cash Date: Sun, 9 May 2021 01:37:27 -0700 Subject: [PATCH 18/44] Revert accidental rename of ToggleActiveSyncTab --- src/common/errors.rs | 4 ++-- src/common/input/actions.rs | 2 +- src/common/screen.rs | 4 ++-- src/server/route.rs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/common/errors.rs b/src/common/errors.rs index 46121312..2a3c665b 100644 --- a/src/common/errors.rs +++ b/src/common/errors.rs @@ -200,7 +200,7 @@ pub enum ScreenContext { PageScrollDown, ClearScroll, CloseFocusedPane, - ToggleActiveSyncPanes, + ToggleActiveSyncTab, ToggleActiveTerminalFullscreen, SetSelectable, SetInvisibleBorders, @@ -261,7 +261,7 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::UpdateTabName(_) => ScreenContext::UpdateTabName, ScreenInstruction::TerminalResize(_) => ScreenContext::TerminalResize, ScreenInstruction::ChangeMode(_) => ScreenContext::ChangeMode, - ScreenInstruction::ToggleActiveSyncPanes => ScreenContext::ToggleActiveSyncPanes, + ScreenInstruction::ToggleActiveSyncTab => ScreenContext::ToggleActiveSyncTab, } } } diff --git a/src/common/input/actions.rs b/src/common/input/actions.rs index f07d7513..54cbbcdc 100644 --- a/src/common/input/actions.rs +++ b/src/common/input/actions.rs @@ -40,7 +40,7 @@ pub enum Action { /// Toggle between fullscreen focus pane and normal layout. ToggleFocusFullscreen, /// Toggle between sending text commands to all panes on the current tab and normal mode. - ToggleActiveSyncPanes, + 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), diff --git a/src/common/screen.rs b/src/common/screen.rs index e28694e8..b12578a7 100644 --- a/src/common/screen.rs +++ b/src/common/screen.rs @@ -52,7 +52,7 @@ pub enum ScreenInstruction { NewTab(RawFd), SwitchTabNext, SwitchTabPrev, - ToggleActiveSyncPanes, + ToggleActiveSyncTab, CloseTab, GoToTab(u32), UpdateTabName(Vec), @@ -551,7 +551,7 @@ pub fn screen_thread_main( ScreenInstruction::ChangeMode(mode_info) => { screen.change_mode(mode_info); } - ScreenInstruction::ToggleActiveSyncPanes => { + ScreenInstruction::ToggleActiveSyncTab => { screen .get_active_tab_mut() .unwrap() diff --git a/src/server/route.rs b/src/server/route.rs index 753a1e12..1fa5825a 100644 --- a/src/server/route.rs +++ b/src/server/route.rs @@ -140,10 +140,10 @@ fn route_action(action: Action, session: &SessionMetaData, os_input: &dyn Server .send_to_screen(ScreenInstruction::SwitchTabPrev) .unwrap(); } - Action::ToggleActiveSyncPanes => { + Action::ToggleActiveSyncTab => { session .senders - .send_to_screen(ScreenInstruction::ToggleActiveSyncPanes) + .send_to_screen(ScreenInstruction::ToggleActiveSyncTab) .unwrap(); } Action::CloseTab => { From 0a17323bc4a87efff51134ee9222aa16ce346a4b Mon Sep 17 00:00:00 2001 From: Kyle Sutherland-Cash Date: Sun, 9 May 2021 01:48:21 -0700 Subject: [PATCH 19/44] Bit more module tidy up --- src/client/panes/plugin_pane.rs | 15 ++++++++------- src/client/panes/terminal_pane.rs | 9 +++++---- src/common/errors.rs | 10 ++++++---- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/client/panes/plugin_pane.rs b/src/client/panes/plugin_pane.rs index cd22e934..dde3cd9b 100644 --- a/src/client/panes/plugin_pane.rs +++ b/src/client/panes/plugin_pane.rs @@ -1,11 +1,12 @@ -use crate::{ - common::thread_bus::SenderWithContext, pty::VteBytes, tab::Pane, wasm_vm::PluginInstruction, -}; - -use crate::panes::{PaneId, PositionAndSize}; - +use std::sync::mpsc::channel; use std::time::Instant; -use std::{sync::mpsc::channel, unimplemented}; +use std::unimplemented; + +use crate::common::thread_bus::SenderWithContext; +use crate::panes::{PaneId, PositionAndSize}; +use crate::pty::VteBytes; +use crate::tab::Pane; +use crate::wasm_vm::PluginInstruction; pub struct PluginPane { pub pid: u32, diff --git a/src/client/panes/terminal_pane.rs b/src/client/panes/terminal_pane.rs index e0ce8ffe..5792bba9 100644 --- a/src/client/panes/terminal_pane.rs +++ b/src/client/panes/terminal_pane.rs @@ -1,15 +1,16 @@ -use crate::tab::Pane; -use ::nix::pty::Winsize; -use ::std::os::unix::io::RawFd; -use serde::{Deserialize, Serialize}; use std::fmt::Debug; +use std::os::unix::io::RawFd; use std::time::Instant; +use nix::pty::Winsize; +use serde::{Deserialize, Serialize}; + use crate::panes::grid::Grid; use crate::panes::terminal_character::{ CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER, }; use crate::pty::VteBytes; +use crate::tab::Pane; #[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug, Serialize, Deserialize)] pub enum PaneId { diff --git a/src/common/errors.rs b/src/common/errors.rs index 2a3c665b..311e8d8e 100644 --- a/src/common/errors.rs +++ b/src/common/errors.rs @@ -1,14 +1,16 @@ //! Error context system based on a thread-local representation of the call stack, itself based on //! the instructions that are sent between threads. -use super::{thread_bus::ASYNCOPENCALLS, thread_bus::OPENCALLS, ServerInstruction}; -use crate::client::ClientInstruction; -use crate::pty::PtyInstruction; -use crate::screen::ScreenInstruction; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Error, Formatter}; +use crate::server::ServerInstruction; +use crate::common::thread_bus::{ASYNCOPENCALLS, OPENCALLS}; +use crate::client::ClientInstruction; +use crate::pty::PtyInstruction; +use crate::screen::ScreenInstruction; + /// 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; From 3689d652ef3813f05b892078f226de0afd21e405 Mon Sep 17 00:00:00 2001 From: Kyle Sutherland-Cash Date: Sun, 9 May 2021 01:52:21 -0700 Subject: [PATCH 20/44] And format... --- src/common/errors.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/errors.rs b/src/common/errors.rs index 311e8d8e..148a3a9e 100644 --- a/src/common/errors.rs +++ b/src/common/errors.rs @@ -5,11 +5,11 @@ use serde::{Deserialize, Serialize}; use std::fmt::{Display, Error, Formatter}; -use crate::server::ServerInstruction; -use crate::common::thread_bus::{ASYNCOPENCALLS, OPENCALLS}; 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. From 9c09cf16587a2b97c63c05d94ff4f18fe557cbf5 Mon Sep 17 00:00:00 2001 From: Dante Pippi <6619666+dantepippi@users.noreply.github.com> Date: Fri, 7 May 2021 18:19:23 -0300 Subject: [PATCH 21/44] New behavior for h and l New behavior for h and l, will change tabs if there are no available panes in the direction. cargo clippy Ensuring atomicity for the MoveFocusOrTab - Blocking the input thread for the action MoveFocusOrTab - Using "unreachable" macro in match arm to make it clear that the directions Up and Down should not be used with the action MoveFocusOrTab Adding tests --- assets/config/default.yaml | 4 +- example/default.yaml | 4 +- example/tmux-overview.yaml | 4 +- src/client/tab.rs | 22 ++++++---- src/common/errors.rs | 6 +++ src/common/input/actions.rs | 3 ++ src/common/input/handler.rs | 3 +- src/common/screen.rs | 2 + src/tests/integration/move_focus_left.rs | 43 ++++++++++++++++++- src/tests/integration/move_focus_right.rs | 43 ++++++++++++++++++- ...cus_left__move_focus_left_changes_tab.snap | 25 +++++++++++ ...s_right__move_focus_right_changes_tab.snap | 25 +++++++++++ src/tests/utils.rs | 4 ++ 13 files changed, 169 insertions(+), 19 deletions(-) create mode 100644 src/tests/integration/snapshots/zellij__tests__integration__move_focus_left__move_focus_left_changes_tab.snap create mode 100644 src/tests/integration/snapshots/zellij__tests__integration__move_focus_right__move_focus_right_changes_tab.snap diff --git a/assets/config/default.yaml b/assets/config/default.yaml index d56004ef..cb6aa248 100644 --- a/assets/config/default.yaml +++ b/assets/config/default.yaml @@ -16,9 +16,9 @@ keybinds: key: [Ctrl: 'q',] - action: [NewPane: ] key: [ Alt: 'n',] - - action: [MoveFocus: Left,] + - action: [MoveFocusOrTab: Left,] key: [ Alt: 'h',] - - action: [MoveFocus: Right,] + - action: [MoveFocusOrTab: Right,] key: [ Alt: 'l',] - action: [MoveFocus: Down,] key: [ Alt: 'j',] diff --git a/example/default.yaml b/example/default.yaml index 578f4ebf..cdddf1d3 100644 --- a/example/default.yaml +++ b/example/default.yaml @@ -16,9 +16,9 @@ keybinds: key: [Ctrl: 'q',] - action: [NewPane: ] key: [ Alt: 'n',] - - action: [MoveFocus: Left,] + - action: [MoveFocusOrTab: Left,] key: [ Alt: 'h',] - - action: [MoveFocus: Right,] + - action: [MoveFocusOrTab: Right,] key: [ Alt: 'l',] - action: [MoveFocus: Down,] key: [ Alt: 'j',] diff --git a/example/tmux-overview.yaml b/example/tmux-overview.yaml index 887a84ad..c40bdb77 100644 --- a/example/tmux-overview.yaml +++ b/example/tmux-overview.yaml @@ -17,9 +17,9 @@ keybinds: key: [Ctrl: 'q',] - action: [NewPane: ] key: [ Alt: 'n',] - - action: [MoveFocus: Left,] + - action: [MoveFocusOrTab: Left,] key: [ Alt: 'h',] - - action: [MoveFocus: Right,] + - action: [MoveFocusOrTab: Right,] key: [ Alt: 'l',] - action: [MoveFocus: Down,] key: [ Alt: 'j',] diff --git a/src/client/tab.rs b/src/client/tab.rs index bbd68a4d..7636be39 100644 --- a/src/client/tab.rs +++ b/src/client/tab.rs @@ -1848,12 +1848,13 @@ impl Tab { } self.render(); } - pub fn move_focus_left(&mut self) { + // returns a boolean to allow the caller to know if the move happened or not + pub fn move_focus_left(&mut self) -> bool { if !self.has_selectable_panes() { - return; + return false; } if self.fullscreen_is_active { - return; + return false; } let active_terminal = self.get_active_pane(); if let Some(active) = active_terminal { @@ -1868,6 +1869,8 @@ impl Tab { match next_index { Some(&p) => { self.active_terminal = Some(p); + self.render(); + return true; } None => { self.active_terminal = Some(active.pid()); @@ -1876,7 +1879,7 @@ impl Tab { } else { self.active_terminal = Some(active_terminal.unwrap().pid()); } - self.render(); + false } pub fn move_focus_down(&mut self) { if !self.has_selectable_panes() { @@ -1938,12 +1941,13 @@ impl Tab { } self.render(); } - pub fn move_focus_right(&mut self) { + // returns a boolean to allow the caller to know if the move happened or not + pub fn move_focus_right(&mut self) -> bool { if !self.has_selectable_panes() { - return; + return false; } if self.fullscreen_is_active { - return; + return false; } let active_terminal = self.get_active_pane(); if let Some(active) = active_terminal { @@ -1958,6 +1962,8 @@ impl Tab { match next_index { Some(&p) => { self.active_terminal = Some(p); + self.render(); + return true; } None => { self.active_terminal = Some(active.pid()); @@ -1966,7 +1972,7 @@ impl Tab { } else { self.active_terminal = Some(active_terminal.unwrap().pid()); } - self.render(); + false } fn horizontal_borders(&self, terminals: &[PaneId]) -> HashSet { terminals.iter().fold(HashSet::new(), |mut borders, t| { diff --git a/src/common/errors.rs b/src/common/errors.rs index 148a3a9e..c2caf19d 100644 --- a/src/common/errors.rs +++ b/src/common/errors.rs @@ -192,9 +192,11 @@ pub enum ScreenContext { FocusNextPane, FocusPreviousPane, MoveFocusLeft, + MoveFocusLeftOrPreviousTab, MoveFocusDown, MoveFocusUp, MoveFocusRight, + MoveFocusRightOrNextTab, Exit, ScrollUp, ScrollDown, @@ -237,9 +239,13 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::FocusNextPane => ScreenContext::FocusNextPane, ScreenInstruction::FocusPreviousPane => ScreenContext::FocusPreviousPane, ScreenInstruction::MoveFocusLeft => ScreenContext::MoveFocusLeft, + ScreenInstruction::MoveFocusLeftOrPreviousTab => { + ScreenContext::MoveFocusLeftOrPreviousTab + } ScreenInstruction::MoveFocusDown => ScreenContext::MoveFocusDown, ScreenInstruction::MoveFocusUp => ScreenContext::MoveFocusUp, ScreenInstruction::MoveFocusRight => ScreenContext::MoveFocusRight, + ScreenInstruction::MoveFocusRightOrNextTab => ScreenContext::MoveFocusRightOrNextTab, ScreenInstruction::Exit => ScreenContext::Exit, ScreenInstruction::ScrollUp => ScreenContext::ScrollUp, ScreenInstruction::ScrollDown => ScreenContext::ScrollDown, diff --git a/src/common/input/actions.rs b/src/common/input/actions.rs index 54cbbcdc..b806aa3f 100644 --- a/src/common/input/actions.rs +++ b/src/common/input/actions.rs @@ -29,6 +29,9 @@ pub enum Action { /// Move the focus pane in specified direction. SwitchFocus, MoveFocus(Direction), + /// Tries to move the focus pane in specified direction. + /// If there is no pane in the direction, move to previous/next Tab. + MoveFocusOrTab(Direction), /// Scroll up in focus pane. ScrollUp, /// Scroll down in focus pane. diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs index 78635237..aa204850 100644 --- a/src/common/input/handler.rs +++ b/src/common/input/handler.rs @@ -119,7 +119,8 @@ impl InputHandler { | Action::GoToNextTab | Action::GoToPreviousTab | Action::CloseTab - | Action::GoToTab(_) => { + | Action::GoToTab(_) + | Action::MoveFocusOrTab(_) => { self.command_is_executing.blocking_input_thread(); self.os_input .send_to_server(ServerInstruction::Action(action)); diff --git a/src/common/screen.rs b/src/common/screen.rs index b12578a7..525575bf 100644 --- a/src/common/screen.rs +++ b/src/common/screen.rs @@ -33,9 +33,11 @@ pub enum ScreenInstruction { FocusNextPane, FocusPreviousPane, MoveFocusLeft, + MoveFocusLeftOrPreviousTab, MoveFocusDown, MoveFocusUp, MoveFocusRight, + MoveFocusRightOrNextTab, Exit, ScrollUp, ScrollDown, diff --git a/src/tests/integration/move_focus_left.rs b/src/tests/integration/move_focus_left.rs index 3b87bb95..5b6e0504 100644 --- a/src/tests/integration/move_focus_left.rs +++ b/src/tests/integration/move_focus_left.rs @@ -7,8 +7,9 @@ use crate::{start, CliArgs}; use crate::common::input::config::Config; use crate::tests::utils::commands::{ - MOVE_FOCUS_LEFT_IN_PANE_MODE, MOVE_FOCUS_RIGHT_IN_PANE_MODE, PANE_MODE, QUIT, - SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, + ENTER, MOVE_FOCUS_LEFT_IN_NORMAL_MODE, MOVE_FOCUS_LEFT_IN_PANE_MODE, + MOVE_FOCUS_RIGHT_IN_PANE_MODE, NEW_TAB_IN_TAB_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE, + SPLIT_RIGHT_IN_PANE_MODE, TAB_MODE, }; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { @@ -86,3 +87,41 @@ pub fn move_focus_left_to_the_most_recently_used_pane() { get_next_to_last_snapshot(snapshots).expect("could not find snapshot"); assert_snapshot!(snapshot_before_quit); } + +#[test] +pub fn move_focus_left_changes_tab() { + let fake_win_size = PositionAndSize { + columns: 121, + rows: 20, + x: 0, + y: 0, + ..Default::default() + }; + let mut fake_input_output = get_fake_os_input(&fake_win_size); + fake_input_output.add_terminal_input(&[ + &PANE_MODE, + &SPLIT_DOWN_IN_PANE_MODE, + &ENTER, + &TAB_MODE, + &NEW_TAB_IN_TAB_MODE, + &ENTER, + &MOVE_FOCUS_LEFT_IN_NORMAL_MODE, + &QUIT, + ]); + start( + Box::new(fake_input_output.clone()), + CliArgs::default(), + Box::new(fake_input_output.clone()), + Config::default(), + ); + + let output_frames = fake_input_output + .stdout_writer + .output_frames + .lock() + .unwrap(); + let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size); + let snapshot_before_quit = + get_next_to_last_snapshot(snapshots).expect("could not find snapshot"); + assert_snapshot!(snapshot_before_quit); +} diff --git a/src/tests/integration/move_focus_right.rs b/src/tests/integration/move_focus_right.rs index c6c17a75..e5d5ef3e 100644 --- a/src/tests/integration/move_focus_right.rs +++ b/src/tests/integration/move_focus_right.rs @@ -7,8 +7,9 @@ use crate::{start, CliArgs}; use crate::common::input::config::Config; use crate::tests::utils::commands::{ - MOVE_FOCUS_LEFT_IN_PANE_MODE, MOVE_FOCUS_RIGHT_IN_PANE_MODE, PANE_MODE, QUIT, - SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, + ENTER, MOVE_FOCUS_LEFT_IN_PANE_MODE, MOVE_FOCUS_RIGHT_IN_NORMAL_MODE, + MOVE_FOCUS_RIGHT_IN_PANE_MODE, NEW_TAB_IN_TAB_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE, + SPLIT_RIGHT_IN_PANE_MODE, TAB_MODE, }; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { @@ -86,3 +87,41 @@ pub fn move_focus_right_to_the_most_recently_used_pane() { get_next_to_last_snapshot(snapshots).expect("could not find snapshot"); assert_snapshot!(snapshot_before_quit); } + +#[test] +pub fn move_focus_right_changes_tab() { + let fake_win_size = PositionAndSize { + columns: 121, + rows: 20, + x: 0, + y: 0, + ..Default::default() + }; + let mut fake_input_output = get_fake_os_input(&fake_win_size); + fake_input_output.add_terminal_input(&[ + &PANE_MODE, + &SPLIT_DOWN_IN_PANE_MODE, + &ENTER, + &TAB_MODE, + &NEW_TAB_IN_TAB_MODE, + &ENTER, + &MOVE_FOCUS_RIGHT_IN_NORMAL_MODE, + &QUIT, + ]); + start( + Box::new(fake_input_output.clone()), + CliArgs::default(), + Box::new(fake_input_output.clone()), + Config::default(), + ); + + let output_frames = fake_input_output + .stdout_writer + .output_frames + .lock() + .unwrap(); + let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size); + let snapshot_before_quit = + get_next_to_last_snapshot(snapshots).expect("could not find snapshot"); + assert_snapshot!(snapshot_before_quit); +} diff --git a/src/tests/integration/snapshots/zellij__tests__integration__move_focus_left__move_focus_left_changes_tab.snap b/src/tests/integration/snapshots/zellij__tests__integration__move_focus_left__move_focus_left_changes_tab.snap new file mode 100644 index 00000000..3eb21e65 --- /dev/null +++ b/src/tests/integration/snapshots/zellij__tests__integration__move_focus_left__move_focus_left_changes_tab.snap @@ -0,0 +1,25 @@ +--- +source: src/tests/integration/move_focus_left.rs +expression: snapshot_before_quit + +--- +line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +prompt $ +───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── +line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +prompt $ █ diff --git a/src/tests/integration/snapshots/zellij__tests__integration__move_focus_right__move_focus_right_changes_tab.snap b/src/tests/integration/snapshots/zellij__tests__integration__move_focus_right__move_focus_right_changes_tab.snap new file mode 100644 index 00000000..cefab8e1 --- /dev/null +++ b/src/tests/integration/snapshots/zellij__tests__integration__move_focus_right__move_focus_right_changes_tab.snap @@ -0,0 +1,25 @@ +--- +source: src/tests/integration/move_focus_right.rs +expression: snapshot_before_quit + +--- +line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +prompt $ +───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── +line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +prompt $ █ diff --git a/src/tests/utils.rs b/src/tests/utils.rs index aa622fe7..10b243a7 100644 --- a/src/tests/utils.rs +++ b/src/tests/utils.rs @@ -47,6 +47,10 @@ pub fn get_next_to_last_snapshot(mut snapshots: Vec) -> Option { pub mod commands { pub const QUIT: [u8; 1] = [17]; // ctrl-q pub const ESC: [u8; 1] = [27]; + pub const ENTER: [u8; 1] = [10]; // char '\n' + + pub const MOVE_FOCUS_LEFT_IN_NORMAL_MODE: [u8; 2] = [27, 104]; // alt-h + pub const MOVE_FOCUS_RIGHT_IN_NORMAL_MODE: [u8; 2] = [27, 108]; // alt-l pub const PANE_MODE: [u8; 1] = [16]; // ctrl-p pub const SPAWN_TERMINAL_IN_PANE_MODE: [u8; 1] = [110]; // n From 62796c1a31a8babd2edf16054852f00bab0557df Mon Sep 17 00:00:00 2001 From: Dante Pippi <6619666+dantepippi@users.noreply.github.com> Date: Sun, 9 May 2021 17:24:15 -0300 Subject: [PATCH 22/44] After rebase --- src/client/tab.rs | 4 ++-- src/common/screen.rs | 20 ++++++++++++++++++++ src/server/route.rs | 8 ++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/client/tab.rs b/src/client/tab.rs index 7636be39..f0ec0869 100644 --- a/src/client/tab.rs +++ b/src/client/tab.rs @@ -1848,7 +1848,7 @@ impl Tab { } self.render(); } - // returns a boolean to allow the caller to know if the move happened or not + // returns a boolean that indicates whether the focus moved pub fn move_focus_left(&mut self) -> bool { if !self.has_selectable_panes() { return false; @@ -1941,7 +1941,7 @@ impl Tab { } self.render(); } - // returns a boolean to allow the caller to know if the move happened or not + // returns a boolean that indicates whether the focus moved pub fn move_focus_right(&mut self) -> bool { if !self.has_selectable_panes() { return false; diff --git a/src/common/screen.rs b/src/common/screen.rs index 525575bf..44cfe3ba 100644 --- a/src/common/screen.rs +++ b/src/common/screen.rs @@ -424,12 +424,32 @@ pub fn screen_thread_main( 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(); } diff --git a/src/server/route.rs b/src/server/route.rs index 1fa5825a..668cdfbd 100644 --- a/src/server/route.rs +++ b/src/server/route.rs @@ -78,6 +78,14 @@ fn route_action(action: Action, session: &SessionMetaData, os_input: &dyn Server }; session.senders.send_to_screen(screen_instr).unwrap(); } + Action::MoveFocusOrTab(direction) => { + let screen_instr = match direction { + Direction::Left => ScreenInstruction::MoveFocusLeftOrPreviousTab, + Direction::Right => ScreenInstruction::MoveFocusRightOrNextTab, + _ => unreachable!(), + }; + session.senders.send_to_screen(screen_instr).unwrap(); + } Action::ScrollUp => { session .senders From 2f1bba753a6b5a4f5850c5dc45dd8ba6c79dc8ed Mon Sep 17 00:00:00 2001 From: matu3ba Date: Mon, 10 May 2021 14:13:26 +0200 Subject: [PATCH 23/44] improve bug report template (#466) Give copy-paste commands and give example of typical information and density. Refer to common sense of the issue author. --- .github/ISSUE_TEMPLATE/bug_report.md | 34 ++++++++++++++-------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d3b68127..fd34b4f9 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -3,27 +3,27 @@ name: "\U0001F41B Bug Report" about: "If something isn't working as expected." labels: bug --- -Thank you for taking the time to file an issue! -You can erase any parts of this template not applicable to your issue. +Thank you for taking the time to file this issue! Please follow the instructions and fill the missing parts below the instructions, if it is meaningful. Try to be brief and concise. -## In Case of Graphical, or Performance Issues +**In Case of Graphical or Performance Issues** -Please: -1. Delete the contents of `/tmp/zellij-1000/zellij-log`. -2. Run `zellij --debug` and then recreate your issue. +1. Delete the contents of `/tmp/zellij-1000/zellij-log`, ie with `cd /tmp/zellij-1000/` and `rm -fr zellij-log/` +2. Run `zellij --debug` +3. Recreate your issue. 3. Quit Zellij immediately with ctrl-q (your bug should ideally still be visible on screen) -Please attach the files that were created in +Please attach the files that were created in `/tmp/zellij-1000/zellij-log/` to the extent you are comfortable with. -`/tmp/zellij-1000/zellij-log/` - -To the extent you are comfortable with. - -Also please add the size in columns/lines of the terminal in which the bug happened. You can usually find these out with `tput lines` and `tput cols`. - -And the name and version of progams you interacted with as well as -the operating system. - -## Information +**Basic information** `zellij --version`: +`tput lines`: +`tput cols`: +`uname -av` or `ver`(Windows): + +List of programs you interact with as, `PROGRAM --version`: output cropped meaningful, for example: +`nvim --version`: NVIM v0.5.0-dev+1299-g1c2e504d5 (used the appimage release) +`alacritty --version`: alacritty 0.7.2 (5ac8060b) + +**Further information** +Reproduction steps, noticable behavior, related issues etc From 088bcf3dd8aab4e522cface84c0a260b5b37a87c Mon Sep 17 00:00:00 2001 From: a-kenji Date: Sun, 9 May 2021 16:44:05 +0200 Subject: [PATCH 24/44] Add Option for Simplified Layout * the simplified layout omits the Nerdfonts characters that are not found in every Fontset * add an optional config option to opt into the simplified ui `simplified_ui: bool` * add a config flag that allows opting into the simplified ui `zellij options --simplified-ui` * move the `clean` flag to the `setup` subcommand --- default-plugins/status-bar/src/first_line.rs | 138 +++++++++++++------ default-plugins/status-bar/src/main.rs | 10 +- default-plugins/tab-bar/src/line.rs | 37 +++-- default-plugins/tab-bar/src/main.rs | 9 +- default-plugins/tab-bar/src/tab.rs | 20 +-- example/config.yaml | 1 + example/default.yaml | 1 + src/cli.rs | 17 ++- src/client/mod.rs | 6 +- src/common/input/config.rs | 16 ++- src/common/input/handler.rs | 4 +- src/common/input/keybinds.rs | 6 +- src/common/input/mod.rs | 1 + src/common/input/options.rs | 19 +++ src/server/mod.rs | 19 ++- zellij-tile/src/data.rs | 6 + 16 files changed, 225 insertions(+), 85 deletions(-) create mode 100644 src/common/input/options.rs diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index f679e863..37175e3f 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -2,7 +2,7 @@ use ansi_term::ANSIStrings; use zellij_tile::prelude::*; use crate::color_elements; -use crate::{ColoredElements, LinePart, ARROW_SEPARATOR}; +use crate::{ColoredElements, LinePart}; struct CtrlKeyShortcut { mode: CtrlKeyMode, @@ -63,13 +63,18 @@ impl CtrlKeyShortcut { } } -fn unselected_mode_shortcut(letter: char, text: &str, palette: ColoredElements) -> LinePart { - let prefix_separator = palette.unselected_prefix_separator.paint(ARROW_SEPARATOR); +fn unselected_mode_shortcut( + letter: char, + text: &str, + palette: ColoredElements, + separator: &str, +) -> LinePart { + let prefix_separator = palette.unselected_prefix_separator.paint(separator); let char_left_separator = palette.unselected_char_left_separator.paint(" <"); let char_shortcut = palette.unselected_char_shortcut.paint(letter.to_string()); let char_right_separator = palette.unselected_char_right_separator.paint(">"); let styled_text = palette.unselected_styled_text.paint(format!("{} ", text)); - let suffix_separator = palette.unselected_suffix_separator.paint(ARROW_SEPARATOR); + let suffix_separator = palette.unselected_suffix_separator.paint(separator); LinePart { part: ANSIStrings(&[ prefix_separator, @@ -84,13 +89,18 @@ fn unselected_mode_shortcut(letter: char, text: &str, palette: ColoredElements) } } -fn selected_mode_shortcut(letter: char, text: &str, palette: ColoredElements) -> LinePart { - let prefix_separator = palette.selected_prefix_separator.paint(ARROW_SEPARATOR); +fn selected_mode_shortcut( + letter: char, + text: &str, + palette: ColoredElements, + separator: &str, +) -> LinePart { + let prefix_separator = palette.selected_prefix_separator.paint(separator); let char_left_separator = palette.selected_char_left_separator.paint(" <".to_string()); let char_shortcut = palette.selected_char_shortcut.paint(format!("{}", letter)); let char_right_separator = palette.selected_char_right_separator.paint(">".to_string()); let styled_text = palette.selected_styled_text.paint(format!("{} ", text)); - let suffix_separator = palette.selected_suffix_separator.paint(ARROW_SEPARATOR); + let suffix_separator = palette.selected_suffix_separator.paint(separator); LinePart { part: ANSIStrings(&[ prefix_separator, @@ -105,69 +115,89 @@ fn selected_mode_shortcut(letter: char, text: &str, palette: ColoredElements) -> } } -fn disabled_mode_shortcut(text: &str, palette: ColoredElements) -> LinePart { - let prefix_separator = palette.disabled_prefix_separator.paint(ARROW_SEPARATOR); +fn disabled_mode_shortcut(text: &str, palette: ColoredElements, separator: &str) -> LinePart { + let prefix_separator = palette.disabled_prefix_separator.paint(separator); let styled_text = palette.disabled_styled_text.paint(format!("{} ", text)); - let suffix_separator = palette.disabled_suffix_separator.paint(ARROW_SEPARATOR); + let suffix_separator = palette.disabled_suffix_separator.paint(separator); LinePart { part: format!("{}{}{}", prefix_separator, styled_text, suffix_separator), len: text.chars().count() + 2 + 1, // 2 for the arrows, 1 for the padding in the end } } -fn selected_mode_shortcut_single_letter(letter: char, palette: ColoredElements) -> LinePart { +fn selected_mode_shortcut_single_letter( + letter: char, + palette: ColoredElements, + separator: &str, +) -> LinePart { let char_shortcut_text = format!(" {} ", letter); let len = char_shortcut_text.chars().count() + 4; // 2 for the arrows, 2 for the padding let prefix_separator = palette .selected_single_letter_prefix_separator - .paint(ARROW_SEPARATOR); + .paint(separator); let char_shortcut = palette .selected_single_letter_char_shortcut .paint(char_shortcut_text); let suffix_separator = palette .selected_single_letter_suffix_separator - .paint(ARROW_SEPARATOR); + .paint(separator); LinePart { part: ANSIStrings(&[prefix_separator, char_shortcut, suffix_separator]).to_string(), len, } } -fn unselected_mode_shortcut_single_letter(letter: char, palette: ColoredElements) -> LinePart { +fn unselected_mode_shortcut_single_letter( + letter: char, + palette: ColoredElements, + separator: &str, +) -> LinePart { let char_shortcut_text = format!(" {} ", letter); let len = char_shortcut_text.chars().count() + 4; // 2 for the arrows, 2 for the padding let prefix_separator = palette .unselected_single_letter_prefix_separator - .paint(ARROW_SEPARATOR); + .paint(separator); let char_shortcut = palette .unselected_single_letter_char_shortcut .paint(char_shortcut_text); let suffix_separator = palette .unselected_single_letter_suffix_separator - .paint(ARROW_SEPARATOR); + .paint(separator); LinePart { part: ANSIStrings(&[prefix_separator, char_shortcut, suffix_separator]).to_string(), len, } } -fn full_ctrl_key(key: &CtrlKeyShortcut, palette: ColoredElements) -> LinePart { +fn full_ctrl_key(key: &CtrlKeyShortcut, palette: ColoredElements, separator: &str) -> LinePart { let full_text = key.full_text(); let letter_shortcut = key.letter_shortcut(); match key.mode { - CtrlKeyMode::Unselected => { - unselected_mode_shortcut(letter_shortcut, &format!(" {}", full_text), palette) - } - CtrlKeyMode::Selected => { - selected_mode_shortcut(letter_shortcut, &format!(" {}", full_text), palette) - } - CtrlKeyMode::Disabled => { - disabled_mode_shortcut(&format!(" <{}> {}", letter_shortcut, full_text), palette) - } + CtrlKeyMode::Unselected => unselected_mode_shortcut( + letter_shortcut, + &format!(" {}", full_text), + palette, + separator, + ), + CtrlKeyMode::Selected => selected_mode_shortcut( + letter_shortcut, + &format!(" {}", full_text), + palette, + separator, + ), + CtrlKeyMode::Disabled => disabled_mode_shortcut( + &format!(" <{}> {}", letter_shortcut, full_text), + palette, + separator, + ), } } -fn shortened_ctrl_key(key: &CtrlKeyShortcut, palette: ColoredElements) -> LinePart { +fn shortened_ctrl_key( + key: &CtrlKeyShortcut, + palette: ColoredElements, + separator: &str, +) -> LinePart { let shortened_text = key.shortened_text(); let letter_shortcut = key.letter_shortcut(); let shortened_text = match key.action { @@ -176,33 +206,47 @@ fn shortened_ctrl_key(key: &CtrlKeyShortcut, palette: ColoredElements) -> LinePa }; match key.mode { CtrlKeyMode::Unselected => { - unselected_mode_shortcut(letter_shortcut, &shortened_text, palette) + unselected_mode_shortcut(letter_shortcut, &shortened_text, palette, separator) + } + CtrlKeyMode::Selected => { + selected_mode_shortcut(letter_shortcut, &shortened_text, palette, separator) } - CtrlKeyMode::Selected => selected_mode_shortcut(letter_shortcut, &shortened_text, palette), - CtrlKeyMode::Disabled => disabled_mode_shortcut( - &format!(" <{}>{}", letter_shortcut, shortened_text), - palette, - ), CtrlKeyMode::Disabled => disabled_mode_shortcut( &format!(" <{}>{}", letter_shortcut, shortened_text), palette, + separator, ), } } -fn single_letter_ctrl_key(key: &CtrlKeyShortcut, palette: ColoredElements) -> LinePart { +fn single_letter_ctrl_key( + key: &CtrlKeyShortcut, + palette: ColoredElements, + separator: &str, +) -> LinePart { let letter_shortcut = key.letter_shortcut(); match key.mode { - CtrlKeyMode::Unselected => unselected_mode_shortcut_single_letter(letter_shortcut, palette), - CtrlKeyMode::Selected => selected_mode_shortcut_single_letter(letter_shortcut, palette), - CtrlKeyMode::Disabled => disabled_mode_shortcut(&format!(" {}", letter_shortcut), palette), + CtrlKeyMode::Unselected => { + unselected_mode_shortcut_single_letter(letter_shortcut, palette, separator) + } + CtrlKeyMode::Selected => { + selected_mode_shortcut_single_letter(letter_shortcut, palette, separator) + } + CtrlKeyMode::Disabled => { + disabled_mode_shortcut(&format!(" {}", letter_shortcut), palette, separator) + } } } -fn key_indicators(max_len: usize, keys: &[CtrlKeyShortcut], palette: ColoredElements) -> LinePart { +fn key_indicators( + max_len: usize, + keys: &[CtrlKeyShortcut], + palette: ColoredElements, + separator: &str, +) -> LinePart { let mut line_part = LinePart::default(); for ctrl_key in keys { - let key = full_ctrl_key(ctrl_key, palette); + let key = full_ctrl_key(ctrl_key, palette, separator); line_part.part = format!("{}{}", line_part.part, key.part); line_part.len += key.len; } @@ -211,7 +255,7 @@ fn key_indicators(max_len: usize, keys: &[CtrlKeyShortcut], palette: ColoredElem } line_part = LinePart::default(); for ctrl_key in keys { - let key = shortened_ctrl_key(ctrl_key, palette); + let key = shortened_ctrl_key(ctrl_key, palette, separator); line_part.part = format!("{}{}", line_part.part, key.part); line_part.len += key.len; } @@ -220,7 +264,7 @@ fn key_indicators(max_len: usize, keys: &[CtrlKeyShortcut], palette: ColoredElem } line_part = LinePart::default(); for ctrl_key in keys { - let key = single_letter_ctrl_key(ctrl_key, palette); + let key = single_letter_ctrl_key(ctrl_key, palette, separator); line_part.part = format!("{}{}", line_part.part, key.part); line_part.len += key.len; } @@ -231,17 +275,17 @@ fn key_indicators(max_len: usize, keys: &[CtrlKeyShortcut], palette: ColoredElem line_part } -pub fn superkey(palette: ColoredElements) -> LinePart { +pub fn superkey(palette: ColoredElements, separator: &str) -> LinePart { let prefix_text = " Ctrl +"; let prefix = palette.superkey_prefix.paint(prefix_text); - let suffix_separator = palette.superkey_suffix_separator.paint(ARROW_SEPARATOR); + let suffix_separator = palette.superkey_suffix_separator.paint(separator); LinePart { part: ANSIStrings(&[prefix, suffix_separator]).to_string(), len: prefix_text.chars().count(), } } -pub fn ctrl_keys(help: &ModeInfo, max_len: usize) -> LinePart { +pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { let colored_elements = color_elements(help.palette); match &help.mode { InputMode::Locked => key_indicators( @@ -255,6 +299,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Quit), ], colored_elements, + separator, ), InputMode::Resize => key_indicators( max_len, @@ -267,6 +312,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), ], colored_elements, + separator, ), InputMode::Pane => key_indicators( max_len, @@ -279,6 +325,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), ], colored_elements, + separator, ), InputMode::Tab | InputMode::RenameTab => key_indicators( max_len, @@ -291,6 +338,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), ], colored_elements, + separator, ), InputMode::Scroll => key_indicators( max_len, @@ -303,6 +351,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), ], colored_elements, + separator, ), InputMode::Normal => key_indicators( max_len, @@ -315,6 +364,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), ], colored_elements, + separator, ), } } diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index e1a79d49..bb56c279 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -140,9 +140,15 @@ impl ZellijPlugin for State { } fn render(&mut self, _rows: usize, cols: usize) { + let separator = if !self.mode_info.capabilities.arrow_fonts { + ARROW_SEPARATOR + } else { + &"" + }; + let colored_elements = color_elements(self.mode_info.palette); - let superkey = superkey(colored_elements); - let ctrl_keys = ctrl_keys(&self.mode_info, cols - superkey.len); + let superkey = superkey(colored_elements, separator); + let ctrl_keys = ctrl_keys(&self.mode_info, cols - superkey.len, separator); let first_line = format!("{}{}", superkey, ctrl_keys); let second_line = keybinds(&self.mode_info, cols); diff --git a/default-plugins/tab-bar/src/line.rs b/default-plugins/tab-bar/src/line.rs index 89a7aa1b..574edb78 100644 --- a/default-plugins/tab-bar/src/line.rs +++ b/default-plugins/tab-bar/src/line.rs @@ -48,7 +48,7 @@ fn populate_tabs_in_tab_line( } } -fn left_more_message(tab_count_to_the_left: usize, palette: Palette) -> LinePart { +fn left_more_message(tab_count_to_the_left: usize, palette: Palette, separator: &str) -> LinePart { if tab_count_to_the_left == 0 { return LinePart { part: String::new(), @@ -62,11 +62,11 @@ fn left_more_message(tab_count_to_the_left: usize, palette: Palette) -> LinePart }; // 238 let more_text_len = more_text.chars().count() + 2; // 2 for the arrows - let left_separator = style!(palette.bg, palette.orange).paint(ARROW_SEPARATOR); + let left_separator = style!(palette.bg, palette.orange).paint(separator); let more_styled_text = style!(palette.black, palette.orange) .bold() .paint(more_text); - let right_separator = style!(palette.orange, palette.bg).paint(ARROW_SEPARATOR); + let right_separator = style!(palette.orange, palette.bg).paint(separator); let more_styled_text = format!( "{}", ANSIStrings(&[left_separator, more_styled_text, right_separator,]) @@ -77,7 +77,11 @@ fn left_more_message(tab_count_to_the_left: usize, palette: Palette) -> LinePart } } -fn right_more_message(tab_count_to_the_right: usize, palette: Palette) -> LinePart { +fn right_more_message( + tab_count_to_the_right: usize, + palette: Palette, + separator: &str, +) -> LinePart { if tab_count_to_the_right == 0 { return LinePart { part: String::new(), @@ -90,11 +94,11 @@ fn right_more_message(tab_count_to_the_right: usize, palette: Palette) -> LinePa " +many → ".to_string() }; let more_text_len = more_text.chars().count() + 1; // 2 for the arrow - let left_separator = style!(palette.bg, palette.orange).paint(ARROW_SEPARATOR); + let left_separator = style!(palette.bg, palette.orange).paint(separator); let more_styled_text = style!(palette.black, palette.orange) .bold() .paint(more_text); - let right_separator = style!(palette.orange, palette.bg).paint(ARROW_SEPARATOR); + let right_separator = style!(palette.orange, palette.bg).paint(separator); let more_styled_text = format!( "{}", ANSIStrings(&[left_separator, more_styled_text, right_separator,]) @@ -111,14 +115,15 @@ fn add_previous_tabs_msg( title_bar: &mut Vec, cols: usize, palette: Palette, + separator: &str, ) { while get_current_title_len(&tabs_to_render) - + left_more_message(tabs_before_active.len(), palette).len + + left_more_message(tabs_before_active.len(), palette, separator).len >= cols { tabs_before_active.push(tabs_to_render.remove(0)); } - let left_more_message = left_more_message(tabs_before_active.len(), palette); + let left_more_message = left_more_message(tabs_before_active.len(), palette, separator); title_bar.push(left_more_message); } @@ -127,14 +132,15 @@ fn add_next_tabs_msg( title_bar: &mut Vec, cols: usize, palette: Palette, + separator: &str, ) { while get_current_title_len(&title_bar) - + right_more_message(tabs_after_active.len(), palette).len + + right_more_message(tabs_after_active.len(), palette, separator).len >= cols { tabs_after_active.insert(0, title_bar.pop().unwrap()); } - let right_more_message = right_more_message(tabs_after_active.len(), palette); + let right_more_message = right_more_message(tabs_after_active.len(), palette, separator); title_bar.push(right_more_message); } @@ -148,11 +154,20 @@ fn tab_line_prefix(palette: Palette) -> LinePart { } } +pub fn tab_separator(capabilities: PluginCapabilities) -> &'static str { + if !capabilities.arrow_fonts { + ARROW_SEPARATOR + } else { + &"" + } +} + pub fn tab_line( mut all_tabs: Vec, active_tab_index: usize, cols: usize, palette: Palette, + capabilities: PluginCapabilities, ) -> Vec { let mut tabs_to_render: Vec = vec![]; let mut tabs_after_active = all_tabs.split_off(active_tab_index); @@ -180,6 +195,7 @@ pub fn tab_line( &mut tab_line, cols - prefix.len, palette, + tab_separator(capabilities), ); } tab_line.append(&mut tabs_to_render); @@ -189,6 +205,7 @@ pub fn tab_line( &mut tab_line, cols - prefix.len, palette, + tab_separator(capabilities), ); } tab_line.insert(0, prefix); diff --git a/default-plugins/tab-bar/src/main.rs b/default-plugins/tab-bar/src/main.rs index c6277013..00ff4bd9 100644 --- a/default-plugins/tab-bar/src/main.rs +++ b/default-plugins/tab-bar/src/main.rs @@ -60,10 +60,17 @@ impl ZellijPlugin for State { t.position, t.is_sync_panes_active, self.mode_info.palette, + self.mode_info.capabilities, ); all_tabs.push(tab); } - let tab_line = tab_line(all_tabs, active_tab_index, cols, self.mode_info.palette); + let tab_line = tab_line( + all_tabs, + active_tab_index, + cols, + self.mode_info.palette, + self.mode_info.capabilities, + ); let mut s = String::new(); for bar_part in tab_line { s = format!("{}{}", s, bar_part.part); diff --git a/default-plugins/tab-bar/src/tab.rs b/default-plugins/tab-bar/src/tab.rs index 7c1fe6b3..85e2c378 100644 --- a/default-plugins/tab-bar/src/tab.rs +++ b/default-plugins/tab-bar/src/tab.rs @@ -1,15 +1,15 @@ -use crate::{LinePart, ARROW_SEPARATOR}; +use crate::{line::tab_separator, LinePart}; use ansi_term::ANSIStrings; use zellij_tile::prelude::*; use zellij_tile_utils::style; -pub fn active_tab(text: String, palette: Palette) -> LinePart { - let left_separator = style!(palette.bg, palette.green).paint(ARROW_SEPARATOR); +pub fn active_tab(text: String, palette: Palette, separator: &str) -> LinePart { + let left_separator = style!(palette.bg, palette.green).paint(separator); let tab_text_len = text.chars().count() + 4; // 2 for left and right separators, 2 for the text padding let tab_styled_text = style!(palette.black, palette.green) .bold() .paint(format!(" {} ", text)); - let right_separator = style!(palette.green, palette.bg).paint(ARROW_SEPARATOR); + let right_separator = style!(palette.green, palette.bg).paint(separator); let tab_styled_text = format!( "{}", ANSIStrings(&[left_separator, tab_styled_text, right_separator,]) @@ -20,13 +20,13 @@ pub fn active_tab(text: String, palette: Palette) -> LinePart { } } -pub fn non_active_tab(text: String, palette: Palette) -> LinePart { - let left_separator = style!(palette.bg, palette.fg).paint(ARROW_SEPARATOR); +pub fn non_active_tab(text: String, palette: Palette, separator: &str) -> LinePart { + let left_separator = style!(palette.bg, palette.fg).paint(separator); let tab_text_len = text.chars().count() + 4; // 2 for left and right separators, 2 for the padding let tab_styled_text = style!(palette.black, palette.fg) .bold() .paint(format!(" {} ", text)); - let right_separator = style!(palette.fg, palette.bg).paint(ARROW_SEPARATOR); + let right_separator = style!(palette.fg, palette.bg).paint(separator); let tab_styled_text = format!( "{}", ANSIStrings(&[left_separator, tab_styled_text, right_separator,]) @@ -43,7 +43,9 @@ pub fn tab_style( position: usize, is_sync_panes_active: bool, palette: Palette, + capabilities: PluginCapabilities, ) -> LinePart { + let separator = tab_separator(capabilities); let mut tab_text = if text.is_empty() { format!("Tab #{}", position + 1) } else { @@ -53,8 +55,8 @@ pub fn tab_style( tab_text.push_str(" (Sync)"); } if is_active_tab { - active_tab(tab_text, palette) + active_tab(tab_text, palette, separator) } else { - non_active_tab(tab_text, palette) + non_active_tab(tab_text, palette, separator) } } diff --git a/example/config.yaml b/example/config.yaml index 3bb4fbfa..962822db 100644 --- a/example/config.yaml +++ b/example/config.yaml @@ -1,6 +1,7 @@ --- keybinds: normal: + - unbind : true - action: [GoToTab: 1,] key: [F: 1,] - action: [GoToTab: 2,] diff --git a/example/default.yaml b/example/default.yaml index 578f4ebf..032ddac9 100644 --- a/example/default.yaml +++ b/example/default.yaml @@ -1,4 +1,5 @@ --- +simplified_ui: false keybinds: unbind: true normal: diff --git a/src/cli.rs b/src/cli.rs index 91b0ed39..870af1d1 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -10,6 +10,10 @@ pub struct CliArgs { #[structopt(long)] pub max_panes: Option, + /// Speficy, if a simplified layout should be used that is compatible with more fonts + #[structopt(long)] + pub simplified: bool, + /// Change where zellij looks for layouts and plugins #[structopt(long)] pub data_dir: Option, @@ -36,11 +40,11 @@ pub struct CliArgs { #[derive(Debug, StructOpt, Clone, Serialize, Deserialize)] pub enum ConfigCli { /// Change the behaviour of zellij - #[structopt(name = "option")] - Config { - /// Disables loading of configuration file at default location + #[structopt(name = "options")] + Options { #[structopt(long)] - clean: bool, + /// Allow plugins to use a more compatible font type + simplified_ui: bool, }, #[structopt(name = "generate-completion")] @@ -48,9 +52,12 @@ pub enum ConfigCli { #[structopt(name = "setup")] Setup { - /// Disables loading of configuration file at default location /// Dump the default configuration file to stdout #[structopt(long)] dump_config: bool, + /// Disables loading of configuration file at default location, + /// loads the defaults that zellij ships with + #[structopt(long)] + clean: bool, }, } diff --git a/src/client/mod.rs b/src/client/mod.rs index 8a5834f0..5ebf8cec 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -42,7 +42,11 @@ pub fn start_client(mut os_input: Box, opts: CliArgs, config: C let full_screen_ws = os_input.get_terminal_size_using_fd(0); os_input.connect_to_server(); - os_input.send_to_server(ServerInstruction::NewClient(full_screen_ws, opts)); + os_input.send_to_server(ServerInstruction::NewClient( + full_screen_ws, + opts, + config.options.clone(), + )); os_input.set_raw_mode(0); let (send_client_instructions, receive_client_instructions): SyncChannelWithContext< diff --git a/src/common/input/config.rs b/src/common/input/config.rs index 3263b571..019d1c4c 100644 --- a/src/common/input/config.rs +++ b/src/common/input/config.rs @@ -6,10 +6,11 @@ use std::io::{self, Read}; use std::path::{Path, PathBuf}; use super::keybinds::{Keybinds, KeybindsFromYaml}; +use super::options::Options; use crate::cli::{CliArgs, ConfigCli}; use crate::common::setup; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::convert::TryFrom; const DEFAULT_CONFIG_FILE_NAME: &str = "config.yaml"; @@ -19,13 +20,16 @@ type ConfigResult = Result; /// Intermediate deserialization config struct #[derive(Debug, Deserialize)] pub struct ConfigFromYaml { + #[serde(flatten)] + pub options: Option, pub keybinds: Option, } /// Main configuration. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct Config { pub keybinds: Keybinds, + pub options: Options, } #[derive(Debug)] @@ -43,7 +47,8 @@ pub enum ConfigError { impl Default for Config { fn default() -> Self { let keybinds = Keybinds::default(); - Config { keybinds } + let options = Options::default(); + Config { keybinds, options } } } @@ -55,7 +60,7 @@ impl TryFrom<&CliArgs> for Config { return Config::new(&path); } - if let Some(ConfigCli::Config { clean, .. }) = opts.option { + if let Some(ConfigCli::Setup { clean, .. }) = opts.option { if clean { return Config::from_default_assets(); } @@ -84,7 +89,8 @@ impl Config { pub fn from_yaml(yaml_config: &str) -> ConfigResult { let config_from_yaml: ConfigFromYaml = serde_yaml::from_str(&yaml_config)?; let keybinds = Keybinds::get_default_keybinds_with_config(config_from_yaml.keybinds); - Ok(Config { keybinds }) + let options = Options::from_yaml(config_from_yaml.options); + Ok(Config { keybinds, options }) } /// Deserializes from given path. diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs index a4293220..6238a3ee 100644 --- a/src/common/input/handler.rs +++ b/src/common/input/handler.rs @@ -11,7 +11,7 @@ use crate::server::ServerInstruction; use crate::CommandIsExecuting; use termion::input::{TermRead, TermReadEventsAndRaw}; -use zellij_tile::data::{InputMode, Key, ModeInfo, Palette}; +use zellij_tile::data::{InputMode, Key, ModeInfo, Palette, PluginCapabilities}; /// Handles the dispatching of [`Action`]s according to the current /// [`InputMode`], and keep tracks of the current [`InputMode`]. @@ -148,6 +148,7 @@ impl InputHandler { // TODO this should probably be automatically generated in some way pub fn get_mode_info(mode: InputMode, palette: Palette) -> ModeInfo { let mut keybinds: Vec<(String, String)> = vec![]; + let capabilities = PluginCapabilities { arrow_fonts: true }; match mode { InputMode::Normal | InputMode::Locked => {} InputMode::Resize => { @@ -181,6 +182,7 @@ pub fn get_mode_info(mode: InputMode, palette: Palette) -> ModeInfo { mode, keybinds, palette, + capabilities, } } diff --git a/src/common/input/keybinds.rs b/src/common/input/keybinds.rs index 823ca264..8bef47fd 100644 --- a/src/common/input/keybinds.rs +++ b/src/common/input/keybinds.rs @@ -4,14 +4,14 @@ use std::collections::HashMap; use super::actions::Action; use super::config; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use strum::IntoEnumIterator; use zellij_tile::data::*; /// Used in the config struct -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct Keybinds(HashMap); -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct ModeKeybinds(HashMap>); /// Intermediate struct used for deserialisation diff --git a/src/common/input/mod.rs b/src/common/input/mod.rs index 6589cac2..f30ccf23 100644 --- a/src/common/input/mod.rs +++ b/src/common/input/mod.rs @@ -4,3 +4,4 @@ pub mod actions; pub mod config; pub mod handler; pub mod keybinds; +pub mod options; diff --git a/src/common/input/options.rs b/src/common/input/options.rs new file mode 100644 index 00000000..4c2dd861 --- /dev/null +++ b/src/common/input/options.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Deserialize, Serialize)] +/// Options that can be set either through the config file, +/// or cli flags +pub struct Options { + /// Allow plugins to use a more compatible font type + pub simplified_ui: bool, +} + +impl Options { + pub fn from_yaml(from_yaml: Option) -> Options { + if let Some(opts) = from_yaml { + opts + } else { + Options::default() + } + } +} diff --git a/src/server/mod.rs b/src/server/mod.rs index a90831d9..6b9c52b5 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -11,13 +11,14 @@ use std::{ }; use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value}; use wasmer_wasi::{Pipe, WasiState}; -use zellij_tile::data::{Event, EventType, InputMode, ModeInfo}; +use zellij_tile::data::{Event, EventType, InputMode, ModeInfo, PluginCapabilities}; -use crate::cli::CliArgs; +use crate::cli::{CliArgs, ConfigCli}; use crate::client::ClientInstruction; use crate::common::{ errors::{ContextType, PluginContext, PtyContext, ScreenContext, ServerContext}, input::actions::{Action, Direction}, + input::options::Options, input::handler::get_mode_info, os_input_output::{set_permissions, ServerOsApi}, pty_bus::{PtyBus, PtyInstruction}, @@ -36,7 +37,7 @@ use crate::panes::PositionAndSize; #[derive(Serialize, Deserialize, Debug, Clone)] pub enum ServerInstruction { TerminalResize(PositionAndSize), - NewClient(PositionAndSize, CliArgs), + NewClient(PositionAndSize, CliArgs, Options), Action(Action), Render(Option), UnblockInputThread, @@ -112,10 +113,11 @@ pub fn start_server(os_input: Box) -> thread::JoinHandle<()> { let (instruction, mut err_ctx) = receive_server_instructions.recv().unwrap(); err_ctx.add_call(ContextType::IPCServer(ServerContext::from(&instruction))); match instruction { - ServerInstruction::NewClient(full_screen_ws, opts) => { + ServerInstruction::NewClient(full_screen_ws, opts, config_options) => { let session_data = init_session( os_input.clone(), opts, + config_options, send_server_instructions.clone(), full_screen_ws, ); @@ -190,6 +192,7 @@ fn handle_client( fn init_session( os_input: Box, opts: CliArgs, + config_options: Options, send_server_instructions: SenderWithContext, full_screen_ws: PositionAndSize, ) -> SessionMetaData { @@ -303,6 +306,11 @@ fn init_session( let send_pty_instructions = send_pty_instructions.clone(); let send_server_instructions = send_server_instructions; let max_panes = opts.max_panes; + let capabilities = if let Some(ConfigCli::Options { simplified_ui }) = opts.option { + simplified_ui + } else { + config_options.simplified_ui + }; let colors = os_input.load_palette(); move || { @@ -316,6 +324,9 @@ fn init_session( max_panes, ModeInfo { palette: colors, + capabilities: PluginCapabilities { + arrow_fonts: capabilities, + }, ..ModeInfo::default() }, InputMode::Normal, diff --git a/zellij-tile/src/data.rs b/zellij-tile/src/data.rs index 63c031ab..4ff44eac 100644 --- a/zellij-tile/src/data.rs +++ b/zellij-tile/src/data.rs @@ -125,6 +125,7 @@ pub struct ModeInfo { // FIXME: This should probably return Keys and Actions, then sort out strings plugin-side pub keybinds: Vec<(String, String)>, // => pub palette: Palette, + pub capabilities: PluginCapabilities, } #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] @@ -141,3 +142,8 @@ pub struct PluginIds { pub plugin_id: u32, pub zellij_pid: u32, } + +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] +pub struct PluginCapabilities { + pub arrow_fonts: bool, +} From 84e48768137b1a02ffdd33d4a18d9ec578852d2c Mon Sep 17 00:00:00 2001 From: a-kenji Date: Mon, 10 May 2021 16:51:06 +0200 Subject: [PATCH 25/44] fixup! Add Option for Simplified Layout --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 3751e9ac..a1a41fe6 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,11 @@ cargo install zellij Or you can download a prebuilt binary from our [Releases](https://github.com/zellij-org/zellij/releases). +As the default plugins make use of characters that are mostly only found in [nerdfonts](https://www.nerdfonts.com/), +you get the best experience either with installing nerdfonts, or telling the plugins that you request a ui, that +does not rely on such characters with `zellij options --simplified-ui`, or putting `simplified_ui: true` in the +config file. + ## How do I hack on it? (Contributing) * Clone the project * Install cargo-make with `cargo install --force cargo-make` From 3c161a9de3985522fb35176368af97d132cf5cde Mon Sep 17 00:00:00 2001 From: a-kenji Date: Mon, 10 May 2021 16:55:26 +0200 Subject: [PATCH 26/44] fixup! Add Option for Simplified Layout * Add to Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29ca7a81..fb319f11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Add more functionality to unbinding the default keybindings (https://github.com/zellij-org/zellij/pull/468) * Terminal compatibility: fix support for CSI subparameters (https://github.com/zellij-org/zellij/pull/469) * Move the sync command to tab mode (https://github.com/zellij-org/zellij/pull/412) +* Add support for requesting a simpler layout from plugins, move `clean` flag from `options` to `setup` (https://github.com/zellij-org/zellij/pull/479) ## [0.8.0] - 2021-05-07 * Terminal compatibility: pass vttest 8 (https://github.com/zellij-org/zellij/pull/461) From 9c5f0a05c199952732d8e3325fc7d207095381c2 Mon Sep 17 00:00:00 2001 From: a-kenji Date: Mon, 10 May 2021 17:08:30 +0200 Subject: [PATCH 27/44] Poke --- src/cli.rs | 7 ++----- src/common/input/options.rs | 4 +++- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 870af1d1..6ab33d45 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,4 +1,5 @@ use super::common::utils::consts::{ZELLIJ_CONFIG_DIR_ENV, ZELLIJ_CONFIG_FILE_ENV}; +use crate::common::input::options::Options; use serde::{Deserialize, Serialize}; use std::path::PathBuf; use structopt::StructOpt; @@ -41,11 +42,7 @@ pub struct CliArgs { pub enum ConfigCli { /// Change the behaviour of zellij #[structopt(name = "options")] - Options { - #[structopt(long)] - /// Allow plugins to use a more compatible font type - simplified_ui: bool, - }, + Options(Options), #[structopt(name = "generate-completion")] GenerateCompletion { shell: String }, diff --git a/src/common/input/options.rs b/src/common/input/options.rs index 4c2dd861..3ff2fd8e 100644 --- a/src/common/input/options.rs +++ b/src/common/input/options.rs @@ -1,10 +1,12 @@ use serde::{Deserialize, Serialize}; +use structopt::StructOpt; -#[derive(Clone, Default, Debug, PartialEq, Deserialize, Serialize)] +#[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 compatible font type + #[structopt(long)] pub simplified_ui: bool, } From 0abb165aec8823aea8220fe13fd3079b3a0edada Mon Sep 17 00:00:00 2001 From: a-kenji Date: Mon, 10 May 2021 17:12:02 +0200 Subject: [PATCH 28/44] Fix exit code on `dump-default-config` --- CHANGELOG.md | 1 + src/main.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29ca7a81..ae8c122f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Add more functionality to unbinding the default keybindings (https://github.com/zellij-org/zellij/pull/468) * Terminal compatibility: fix support for CSI subparameters (https://github.com/zellij-org/zellij/pull/469) * Move the sync command to tab mode (https://github.com/zellij-org/zellij/pull/412) +* Fix exit code of `dump-default-config` (https://github.com/zellij-org/zellij/pull/480) ## [0.8.0] - 2021-05-07 * Terminal compatibility: pass vttest 8 (https://github.com/zellij-org/zellij/pull/461) diff --git a/src/main.rs b/src/main.rs index 12998d4d..ffc2373e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,7 +45,7 @@ pub fn main() { CliArgs::clap().gen_completions_to("zellij", shell, &mut out); } else if let Some(crate::cli::ConfigCli::Setup { .. }) = opts.option { setup::dump_default_config().expect("Failed to print to stdout"); - std::process::exit(1); + std::process::exit(0); } else { atomic_create_dir(&*ZELLIJ_TMP_DIR).unwrap(); atomic_create_dir(&*ZELLIJ_TMP_LOG_DIR).unwrap(); From afcc990e696fbcfef61addc576b2e5485fe7e27f Mon Sep 17 00:00:00 2001 From: Kunal Mohan Date: Mon, 10 May 2021 20:49:01 +0530 Subject: [PATCH 29/44] docs(changelog): Switch tabs in normal mode #471 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29ca7a81..90205cdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Add more functionality to unbinding the default keybindings (https://github.com/zellij-org/zellij/pull/468) * Terminal compatibility: fix support for CSI subparameters (https://github.com/zellij-org/zellij/pull/469) * Move the sync command to tab mode (https://github.com/zellij-org/zellij/pull/412) +* Feature: Switch tabs using `Alt + h/l` in normal mode if there are no panes in the direction (https://github.com/zellij-org/zellij/pull/471) ## [0.8.0] - 2021-05-07 * Terminal compatibility: pass vttest 8 (https://github.com/zellij-org/zellij/pull/461) From ef69644f1b0b63e68e1363eddebcea70e50a5e2d Mon Sep 17 00:00:00 2001 From: a-kenji Date: Mon, 10 May 2021 17:42:59 +0200 Subject: [PATCH 30/44] fixup! Add Option for Simplified Layout * merge structop and serde structs --- example/default.yaml | 2 +- src/server/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/example/default.yaml b/example/default.yaml index 032ddac9..a113c8ee 100644 --- a/example/default.yaml +++ b/example/default.yaml @@ -1,5 +1,5 @@ --- -simplified_ui: false +simplified_ui: true keybinds: unbind: true normal: diff --git a/src/server/mod.rs b/src/server/mod.rs index 6b9c52b5..58a92c55 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -306,8 +306,8 @@ fn init_session( let send_pty_instructions = send_pty_instructions.clone(); let send_server_instructions = send_server_instructions; let max_panes = opts.max_panes; - let capabilities = if let Some(ConfigCli::Options { simplified_ui }) = opts.option { - simplified_ui + let capabilities = if let Some(ConfigCli::Options(options)) = opts.option { + options.simplified_ui } else { config_options.simplified_ui }; From 5f2c5d2bda972de532fb222d7938e91580a2bcc2 Mon Sep 17 00:00:00 2001 From: a-kenji Date: Mon, 10 May 2021 21:21:19 +0200 Subject: [PATCH 31/44] !fixup Add Option for Simplified Layout * fix test --- src/common/input/config.rs | 5 ++++- src/common/screen.rs | 6 +++--- src/server/mod.rs | 8 +++++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/common/input/config.rs b/src/common/input/config.rs index 019d1c4c..5f2656f3 100644 --- a/src/common/input/config.rs +++ b/src/common/input/config.rs @@ -178,7 +178,10 @@ mod config_test { #[test] fn try_from_cli_args_with_option_clean() { let mut opts = CliArgs::default(); - opts.option = Some(ConfigCli::Config { clean: true }); + opts.option = Some(ConfigCli::Setup { + clean: true, + dump_config: false, + }); let result = Config::try_from(&opts); assert!(result.is_ok()); } diff --git a/src/common/screen.rs b/src/common/screen.rs index 82f63537..6f5a4cb3 100644 --- a/src/common/screen.rs +++ b/src/common/screen.rs @@ -4,10 +4,10 @@ use std::collections::BTreeMap; use std::os::unix::io::RawFd; use std::str; +use crate::cli::ConfigCli; +use crate::common::input::options::Options; use crate::common::pty::{PtyInstruction, VteBytes}; use crate::common::thread_bus::Bus; -use crate::common::input::options::Options; -use crate::cli::ConfigCli; use crate::errors::{ContextType, ScreenContext}; use crate::layout::Layout; use crate::panes::PaneId; @@ -16,7 +16,7 @@ use crate::server::ServerInstruction; use crate::tab::Tab; use crate::wasm_vm::PluginInstruction; -use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, TabInfo, PluginCapabilities}; +use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, PluginCapabilities, TabInfo}; /// Instructions that can be sent to the [`Screen`]. #[derive(Debug, Clone)] diff --git a/src/server/mod.rs b/src/server/mod.rs index 1b08f95f..d9875727 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -217,7 +217,13 @@ fn init_session( let options = opts.option; move || { - screen_thread_main(screen_bus, max_panes, full_screen_ws, options, config_options); + screen_thread_main( + screen_bus, + max_panes, + full_screen_ws, + options, + config_options, + ); } }) .unwrap(); From 073d39705ede937250948892a775865b4cc47e37 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Tue, 11 May 2021 11:06:19 +0200 Subject: [PATCH 32/44] Terminal compatibility: various behaviour fixes (#486) * fix(compatibility): various behaviour fixes * style(fmt): rustfmt * style(fmt): thanks clippy for teaching me about matches! --- src/client/panes/grid.rs | 86 +++++++++++++++++-- src/client/panes/terminal_character.rs | 18 ++++ src/client/panes/terminal_pane.rs | 12 ++- src/client/panes/unit/grid_tests.rs | 36 ++++++++ ...lient__panes__grid__grid_tests__csi_b.snap | 8 ++ ...anes__grid__grid_tests__csi_capital_i.snap | 8 ++ ...anes__grid__grid_tests__csi_capital_z.snap | 8 ++ src/client/tab.rs | 9 +- src/tests/fixtures/csi-b | 1 + src/tests/fixtures/csi-capital-i | 1 + src/tests/fixtures/csi-capital-z | 1 + 11 files changed, 176 insertions(+), 12 deletions(-) create mode 100644 src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_b.snap create mode 100644 src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_i.snap create mode 100644 src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_z.snap create mode 100644 src/tests/fixtures/csi-b create mode 100644 src/tests/fixtures/csi-capital-i create mode 100644 src/tests/fixtures/csi-capital-z diff --git a/src/client/panes/grid.rs b/src/client/panes/grid.rs index 824c8384..0c873be8 100644 --- a/src/client/panes/grid.rs +++ b/src/client/panes/grid.rs @@ -12,7 +12,7 @@ const SCROLL_BACK: usize = 10_000; use crate::utils::logging::debug_log_to_file; use crate::panes::terminal_character::{ - CharacterStyles, CharsetIndex, Cursor, StandardCharset, TerminalCharacter, + CharacterStyles, CharsetIndex, Cursor, CursorShape, StandardCharset, TerminalCharacter, EMPTY_TERMINAL_CHARACTER, }; @@ -177,6 +177,7 @@ pub struct Grid { saved_cursor_position: Option, scroll_region: Option<(usize, usize)>, active_charset: CharsetIndex, + preceding_char: Option, pub should_render: bool, pub cursor_key_mode: bool, // DECCKM - when set, cursor keys should send ANSI direction codes (eg. "OD") instead of the arrow keys (eg. "") pub erasure_mode: bool, // ERM @@ -210,6 +211,7 @@ impl Grid { cursor: Cursor::new(0, 0), saved_cursor_position: None, scroll_region: None, + preceding_char: None, width: columns, height: rows, should_render: true, @@ -245,6 +247,26 @@ impl Grid { empty_character.styles = styles; self.pad_current_line_until(self.cursor.x); } + pub fn move_to_previous_tabstop(&mut self) { + let mut previous_tabstop = None; + for tabstop in self.horizontal_tabstops.iter() { + if *tabstop >= self.cursor.x { + break; + } + previous_tabstop = Some(tabstop); + } + match previous_tabstop { + Some(tabstop) => { + self.cursor.x = *tabstop; + } + None => { + self.cursor.x = 0; + } + } + } + pub fn cursor_shape(&self) -> CursorShape { + self.cursor.get_shape() + } fn set_horizontal_tabstop(&mut self) { self.horizontal_tabstops.insert(self.cursor.x); } @@ -925,6 +947,10 @@ impl Grid { self.active_charset = Default::default(); self.erasure_mode = false; self.disable_linewrap = false; + self.cursor.change_shape(CursorShape::Block); + } + fn set_preceding_character(&mut self, terminal_character: TerminalCharacter) { + self.preceding_char = Some(terminal_character); } } @@ -937,6 +963,7 @@ impl Perform for Grid { character: c, styles: self.cursor.pending_styles, }; + self.set_preceding_character(terminal_character); self.add_character(terminal_character); } @@ -950,9 +977,10 @@ impl Perform for Grid { // tab self.advance_to_next_tabstop(self.cursor.pending_styles); } - 10 | 11 => { + 10 | 11 | 12 => { // 0a, newline // 0b, vertical tabulation + // 0c, form feed self.add_newline(); } 13 => { @@ -985,7 +1013,7 @@ impl Perform for Grid { // TBD } - fn csi_dispatch(&mut self, params: &Params, _intermediates: &[u8], _ignore: bool, c: char) { + fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], _ignore: bool, c: char) { let mut params_iter = params.iter(); let mut next_param_or = |default: u16| { params_iter @@ -998,7 +1026,7 @@ impl Perform for Grid { self.cursor .pending_styles .add_style_from_ansi_params(&mut params_iter); - } else if c == 'C' { + } else if c == 'C' || c == 'a' { // move cursor forward let move_by = next_param_or(1); self.move_cursor_forward_until_edge(move_by); @@ -1031,7 +1059,6 @@ impl Perform for Grid { self.clear_all(char_to_replace); } }; - // TODO: implement 1 } else if c == 'H' || c == 'f' { // goto row/col // we subtract 1 from the row/column because these are 1 indexed @@ -1043,7 +1070,7 @@ impl Perform for Grid { // move cursor up until edge of screen let move_up_count = next_param_or(1); self.move_cursor_up(move_up_count as usize); - } else if c == 'B' { + } else if c == 'B' || c == 'e' { // move cursor down until edge of screen let move_down_count = next_param_or(1); let pad_character = EMPTY_TERMINAL_CHARACTER; @@ -1052,7 +1079,7 @@ impl Perform for Grid { let move_back_count = next_param_or(1); self.move_cursor_back(move_back_count); } else if c == 'l' { - let first_intermediate_is_questionmark = match _intermediates.get(0) { + let first_intermediate_is_questionmark = match intermediates.get(0) { Some(b'?') => true, None => false, _ => false, @@ -1101,7 +1128,7 @@ impl Perform for Grid { self.insert_mode = false; } } else if c == 'h' { - let first_intermediate_is_questionmark = match _intermediates.get(0) { + let first_intermediate_is_questionmark = match intermediates.get(0) { Some(b'?') => true, None => false, _ => false, @@ -1171,7 +1198,7 @@ impl Perform for Grid { let line_count_to_add = next_param_or(1); let pad_character = EMPTY_TERMINAL_CHARACTER; self.add_empty_lines_in_scroll_region(line_count_to_add, pad_character); - } else if c == 'G' { + } else if c == 'G' || c == '`' { let column = next_param_or(1).saturating_sub(1); self.move_cursor_to_column(column); } else if c == 'g' { @@ -1216,6 +1243,46 @@ impl Perform for Grid { // TODO: should this be styled? self.insert_character_at_cursor_position(EMPTY_TERMINAL_CHARACTER); } + } else if c == 'b' { + if let Some(c) = self.preceding_char { + for _ in 0..next_param_or(1) { + self.add_character(c); + } + } + } else if c == 'E' { + let count = next_param_or(1); + let pad_character = EMPTY_TERMINAL_CHARACTER; + self.move_cursor_down(count, pad_character); + } else if c == 'F' { + let count = next_param_or(1); + self.move_cursor_up(count); + self.move_cursor_to_beginning_of_line(); + } else if c == 'I' { + for _ in 0..next_param_or(1) { + self.advance_to_next_tabstop(self.cursor.pending_styles); + } + } else if c == 'q' { + let first_intermediate_is_space = matches!(intermediates.get(0), Some(b' ')); + if first_intermediate_is_space { + // DECSCUSR (CSI Ps SP q) -- Set Cursor Style. + let cursor_style_id = next_param_or(0); + let shape = match cursor_style_id { + 0 | 2 => Some(CursorShape::Block), + 1 => Some(CursorShape::BlinkingBlock), + 3 => Some(CursorShape::BlinkingUnderline), + 4 => Some(CursorShape::Underline), + 5 => Some(CursorShape::BlinkingBeam), + 6 => Some(CursorShape::Beam), + _ => None, + }; + if let Some(cursor_shape) = shape { + self.cursor.change_shape(cursor_shape); + } + } + } else if c == 'Z' { + for _ in 0..next_param_or(1) { + self.move_to_previous_tabstop(); + } } else { let result = debug_log_to_file(format!("Unhandled csi: {}->{:?}", c, params)); #[cfg(not(test))] @@ -1262,6 +1329,7 @@ impl Perform for Grid { self.move_cursor_to_beginning_of_line(); } (b'M', None) => { + // TODO: if cursor is at the top, it should go down one self.move_cursor_up_with_scrolling(1); } (b'c', None) => { diff --git a/src/client/panes/terminal_character.rs b/src/client/panes/terminal_character.rs index c7c616c1..d2340e2e 100644 --- a/src/client/panes/terminal_character.rs +++ b/src/client/panes/terminal_character.rs @@ -701,6 +701,16 @@ impl IndexMut for Charsets { } } +#[derive(Clone, Copy, Debug)] +pub enum CursorShape { + Block, + BlinkingBlock, + Underline, + BlinkingUnderline, + Beam, + BlinkingBeam, +} + #[derive(Clone, Debug)] pub struct Cursor { pub x: usize, @@ -708,6 +718,7 @@ pub struct Cursor { pub is_hidden: bool, pub pending_styles: CharacterStyles, pub charsets: Charsets, + shape: CursorShape, } impl Cursor { @@ -718,8 +729,15 @@ impl Cursor { is_hidden: false, pending_styles: CharacterStyles::new(), charsets: Default::default(), + shape: CursorShape::Block, } } + pub fn change_shape(&mut self, shape: CursorShape) { + self.shape = shape; + } + pub fn get_shape(&self) -> CursorShape { + self.shape + } } #[derive(Clone, Copy)] diff --git a/src/client/panes/terminal_pane.rs b/src/client/panes/terminal_pane.rs index 5792bba9..98021110 100644 --- a/src/client/panes/terminal_pane.rs +++ b/src/client/panes/terminal_pane.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use crate::panes::grid::Grid; use crate::panes::terminal_character::{ - CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER, + CharacterStyles, CursorShape, TerminalCharacter, EMPTY_TERMINAL_CHARACTER, }; use crate::pty::VteBytes; use crate::tab::Pane; @@ -291,6 +291,16 @@ impl Pane for TerminalPane { fn set_active_at(&mut self, time: Instant) { self.active_at = time; } + fn cursor_shape_csi(&self) -> String { + match self.grid.cursor_shape() { + CursorShape::Block => "\u{1b}[0 q".to_string(), + CursorShape::BlinkingBlock => "\u{1b}[1 q".to_string(), + CursorShape::Underline => "\u{1b}[4 q".to_string(), + CursorShape::BlinkingUnderline => "\u{1b}[3 q".to_string(), + CursorShape::Beam => "\u{1b}[6 q".to_string(), + CursorShape::BlinkingBeam => "\u{1b}[5 q".to_string(), + } + } } impl TerminalPane { diff --git a/src/client/panes/unit/grid_tests.rs b/src/client/panes/unit/grid_tests.rs index b590f404..b6a2ec6c 100644 --- a/src/client/panes/unit/grid_tests.rs +++ b/src/client/panes/unit/grid_tests.rs @@ -347,3 +347,39 @@ fn vttest8_5() { } assert_snapshot!(format!("{:?}", grid)); } + +#[test] +fn csi_b() { + let mut vte_parser = vte::Parser::new(); + let mut grid = Grid::new(51, 97); + let fixture_name = "csi-b"; + let content = read_fixture(fixture_name); + for byte in content { + vte_parser.advance(&mut grid, byte); + } + assert_snapshot!(format!("{:?}", grid)); +} + +#[test] +fn csi_capital_i() { + let mut vte_parser = vte::Parser::new(); + let mut grid = Grid::new(51, 97); + let fixture_name = "csi-capital-i"; + let content = read_fixture(fixture_name); + for byte in content { + vte_parser.advance(&mut grid, byte); + } + assert_snapshot!(format!("{:?}", grid)); +} + +#[test] +fn csi_capital_z() { + let mut vte_parser = vte::Parser::new(); + let mut grid = Grid::new(51, 97); + let fixture_name = "csi-capital-z"; + let content = read_fixture(fixture_name); + for byte in content { + vte_parser.advance(&mut grid, byte); + } + assert_snapshot!(format!("{:?}", grid)); +} diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_b.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_b.snap new file mode 100644 index 00000000..1d0c34e5 --- /dev/null +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_b.snap @@ -0,0 +1,8 @@ +--- +source: src/client/panes/./unit/grid_tests.rs +expression: "format!(\"{:?}\", grid)" + +--- +00 (C): ffffff +01 (C): + diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_i.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_i.snap new file mode 100644 index 00000000..11f7b8f8 --- /dev/null +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_i.snap @@ -0,0 +1,8 @@ +--- +source: src/client/panes/./unit/grid_tests.rs +expression: "format!(\"{:?}\", grid)" + +--- +00 (C): foo +01 (C): + diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_z.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_z.snap new file mode 100644 index 00000000..04f492b8 --- /dev/null +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_z.snap @@ -0,0 +1,8 @@ +--- +source: src/client/panes/./unit/grid_tests.rs +expression: "format!(\"{:?}\", grid)" + +--- +00 (C): 12345678foo234567890 +01 (C): + diff --git a/src/client/tab.rs b/src/client/tab.rs index f0ec0869..785a650e 100644 --- a/src/client/tab.rs +++ b/src/client/tab.rs @@ -132,6 +132,9 @@ pub trait Pane { fn clear_scroll(&mut self); fn active_at(&self) -> Instant; fn set_active_at(&mut self, instant: Instant); + fn cursor_shape_csi(&self) -> String { + "\u{1b}[0 q".to_string() // default to non blinking block + } fn right_boundary_x_coords(&self) -> usize { self.x() + self.columns() @@ -762,10 +765,12 @@ impl Tab { match self.get_active_terminal_cursor_position() { Some((cursor_position_x, cursor_position_y)) => { let show_cursor = "\u{1b}[?25h"; + let change_cursor_shape = self.get_active_pane().unwrap().cursor_shape_csi(); let goto_cursor_position = &format!( - "\u{1b}[{};{}H\u{1b}[m", + "\u{1b}[{};{}H\u{1b}[m{}", cursor_position_y + 1, - cursor_position_x + 1 + cursor_position_x + 1, + change_cursor_shape ); // goto row/col output.push_str(show_cursor); output.push_str(goto_cursor_position); diff --git a/src/tests/fixtures/csi-b b/src/tests/fixtures/csi-b new file mode 100644 index 00000000..e3fe9997 --- /dev/null +++ b/src/tests/fixtures/csi-b @@ -0,0 +1 @@ +f diff --git a/src/tests/fixtures/csi-capital-i b/src/tests/fixtures/csi-capital-i new file mode 100644 index 00000000..9d658f62 --- /dev/null +++ b/src/tests/fixtures/csi-capital-i @@ -0,0 +1 @@ +foo diff --git a/src/tests/fixtures/csi-capital-z b/src/tests/fixtures/csi-capital-z new file mode 100644 index 00000000..649ca645 --- /dev/null +++ b/src/tests/fixtures/csi-capital-z @@ -0,0 +1 @@ +12345678901234567890foo From 20dcb3f4ca0d18a44cf3fb19cc19e130d8c2f988 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Tue, 11 May 2021 11:08:14 +0200 Subject: [PATCH 33/44] docs(changelog): update compatibility fixes --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19105b76..8fdf304f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Terminal compatibility: fix support for CSI subparameters (https://github.com/zellij-org/zellij/pull/469) * Move the sync command to tab mode (https://github.com/zellij-org/zellij/pull/412) * Fix exit code of `dump-default-config` (https://github.com/zellij-org/zellij/pull/480) -* Feature: Switch tabs using `Alt + h/l` in normal mode if there are no panes in the direction (https://github.com/zellij-org/zellij/pull/471) +* Feature: Switch tabs using `Alt + h/l` in normal mode if there are no panes in the direction (https://github.com/zellij-org/zellij/pull/471) +* Terminal Compatibility: various behaviour fixes (https://github.com/zellij-org/zellij/pull/486) ## [0.8.0] - 2021-05-07 * Terminal compatibility: pass vttest 8 (https://github.com/zellij-org/zellij/pull/461) From a116f6e177b3676e7f348844f796facad6810d0f Mon Sep 17 00:00:00 2001 From: a-kenji Date: Tue, 11 May 2021 11:05:17 +0200 Subject: [PATCH 34/44] Fix Max Config Directory closes #485 * fixes a regression on the config directory handling on mac --- CHANGELOG.md | 1 + src/common/setup.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19105b76..e66a748d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Move the sync command to tab mode (https://github.com/zellij-org/zellij/pull/412) * Fix exit code of `dump-default-config` (https://github.com/zellij-org/zellij/pull/480) * Feature: Switch tabs using `Alt + h/l` in normal mode if there are no panes in the direction (https://github.com/zellij-org/zellij/pull/471) +* Fix handling of $HOME `config` direcotry, especially relevant for darwin systems (https://github.com/zellij-org/zellij/pull/487) ## [0.8.0] - 2021-05-07 * Terminal compatibility: pass vttest 8 (https://github.com/zellij-org/zellij/pull/461) diff --git a/src/common/setup.rs b/src/common/setup.rs index 2eceba18..eb3fdb8e 100644 --- a/src/common/setup.rs +++ b/src/common/setup.rs @@ -4,7 +4,7 @@ use directories_next::{BaseDirs, ProjectDirs}; use std::io::Write; use std::{fs, path::Path, path::PathBuf}; -const CONFIG_LOCATION: &str = "/.config/zellij"; +const CONFIG_LOCATION: &str = ".config/zellij"; #[macro_export] macro_rules! asset_map { From ce0ca2f99792d0ef9d81c1408e78e30c625b7e35 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Tue, 11 May 2021 11:45:21 +0200 Subject: [PATCH 35/44] chore(version): bump development version --- CHANGELOG.md | 3 ++- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 082bf7b5..2c47adf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] + +## [0.9.0] - 2021-05-11 * Add more functionality to unbinding the default keybindings (https://github.com/zellij-org/zellij/pull/468) * Terminal compatibility: fix support for CSI subparameters (https://github.com/zellij-org/zellij/pull/469) * Move the sync command to tab mode (https://github.com/zellij-org/zellij/pull/412) @@ -13,7 +15,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Terminal Compatibility: various behaviour fixes (https://github.com/zellij-org/zellij/pull/486) * Fix handling of `$HOME` `config` directory, especially relevant for darwin systems (https://github.com/zellij-org/zellij/pull/487) - ## [0.8.0] - 2021-05-07 * Terminal compatibility: pass vttest 8 (https://github.com/zellij-org/zellij/pull/461) * Add a Manpage (https://github.com/zellij-org/zellij/pull/455) diff --git a/Cargo.lock b/Cargo.lock index 4f7255df..314e083a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2254,7 +2254,7 @@ dependencies = [ [[package]] name = "zellij" -version = "0.9.0" +version = "0.10.0" dependencies = [ "ansi_term 0.12.1", "async-std", diff --git a/Cargo.toml b/Cargo.toml index 0e9136c8..2018a681 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zellij" -version = "0.9.0" +version = "0.10.0" authors = ["Aram Drevekenin "] edition = "2018" description = "A terminal workspace with batteries included" From 1859abbb236090843ab6299d7c11284f925422b9 Mon Sep 17 00:00:00 2001 From: a-kenji Date: Tue, 11 May 2021 13:10:31 +0200 Subject: [PATCH 36/44] Change Ordering of the Default Config Directories * since darwin doesn't have distinct data and config directories, assumptions that we had were invalid * now the `HOME` config directory is loaded before the system config directory, in this case populating the data directory doesn't invalidate possible user configuration, because the configuration directory now exists --- CHANGELOG.md | 1 + src/common/setup.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c47adf5..77287236 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] +* Change Switch default config loading order of `HOME` and system (https://github.com/zellij-org/zellij/pull/488) ## [0.9.0] - 2021-05-11 * Add more functionality to unbinding the default keybindings (https://github.com/zellij-org/zellij/pull/468) diff --git a/src/common/setup.rs b/src/common/setup.rs index eb3fdb8e..37bf02da 100644 --- a/src/common/setup.rs +++ b/src/common/setup.rs @@ -53,8 +53,8 @@ pub mod install { #[cfg(not(test))] pub fn find_default_config_dir() -> Option { vec![ - Some(xdg_config_dir()), home_config_dir(), + Some(xdg_config_dir()), Some(Path::new(SYSTEM_DEFAULT_CONFIG_DIR).to_path_buf()), ] .into_iter() From 2c2ab15ee7b4c748c183d649749b3c568d5f4dba Mon Sep 17 00:00:00 2001 From: a-kenji Date: Tue, 11 May 2021 22:13:15 +0200 Subject: [PATCH 37/44] docs(changelog): Add Option for Simplified Layout --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62d0e87a..3b014f1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] * Change Switch default config loading order of `HOME` and system (https://github.com/zellij-org/zellij/pull/488) +* Add support for requesting a simpler layout from plugins, move `clean` flag from `options` to `setup` (https://github.com/zellij-org/zellij/pull/479) ## [0.9.0] - 2021-05-11 * Add more functionality to unbinding the default keybindings (https://github.com/zellij-org/zellij/pull/468) * Terminal compatibility: fix support for CSI subparameters (https://github.com/zellij-org/zellij/pull/469) * Move the sync command to tab mode (https://github.com/zellij-org/zellij/pull/412) -* Add support for requesting a simpler layout from plugins, move `clean` flag from `options` to `setup` (https://github.com/zellij-org/zellij/pull/479) * Fix exit code of `dump-default-config` (https://github.com/zellij-org/zellij/pull/480) * Feature: Switch tabs using `Alt + h/l` in normal mode if there are no panes in the direction (https://github.com/zellij-org/zellij/pull/471) * Terminal Compatibility: various behaviour fixes (https://github.com/zellij-org/zellij/pull/486) From 6ffd698d0a1a974f0cb80cb256f4d58e2c3d97f6 Mon Sep 17 00:00:00 2001 From: a-kenji Date: Wed, 12 May 2021 09:42:05 +0200 Subject: [PATCH 38/44] Slight rework of config loading. Merges the cli and file configuration --- CHANGELOG.md | 1 + src/cli.rs | 4 ---- src/client/mod.rs | 6 ++++- src/common/input/options.rs | 44 ++++++++++++++++++++++++++++++++++++- src/common/screen.rs | 13 ++++------- src/server/mod.rs | 15 ++++--------- 6 files changed, 57 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b014f1a..8d5c9ff5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] * Change Switch default config loading order of `HOME` and system (https://github.com/zellij-org/zellij/pull/488) * Add support for requesting a simpler layout from plugins, move `clean` flag from `options` to `setup` (https://github.com/zellij-org/zellij/pull/479) +* Improve config loading slightly (https://github.com/zellij-org/zellij/pull/492) ## [0.9.0] - 2021-05-11 * Add more functionality to unbinding the default keybindings (https://github.com/zellij-org/zellij/pull/468) diff --git a/src/cli.rs b/src/cli.rs index 6ab33d45..b95b5770 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -11,10 +11,6 @@ pub struct CliArgs { #[structopt(long)] pub max_panes: Option, - /// Speficy, if a simplified layout should be used that is compatible with more fonts - #[structopt(long)] - pub simplified: bool, - /// Change where zellij looks for layouts and plugins #[structopt(long)] pub data_dir: Option, diff --git a/src/client/mod.rs b/src/client/mod.rs index 8fcbc070..154f1437 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -15,6 +15,7 @@ use crate::common::{ errors::{ClientContext, ContextType}, input::config::Config, input::handler::input_loop, + input::options::{ConfigOptions, Options}, os_input_output::ClientOsApi, thread_bus::{SenderType, SenderWithContext, SyncChannelWithContext}, }; @@ -40,12 +41,15 @@ pub fn start_client(mut os_input: Box, opts: CliArgs, config: C let mut command_is_executing = CommandIsExecuting::new(); + let config_options: ConfigOptions = + Options::from_cli(&config.options, opts.option.clone()).into(); + let full_screen_ws = os_input.get_terminal_size_using_fd(0); os_input.connect_to_server(); os_input.send_to_server(ServerInstruction::NewClient( full_screen_ws, opts, - config.options.clone(), + config_options, )); os_input.set_raw_mode(0); diff --git a/src/common/input/options.rs b/src/common/input/options.rs index 3ff2fd8e..3bd3fc31 100644 --- a/src/common/input/options.rs +++ b/src/common/input/options.rs @@ -1,12 +1,23 @@ +//! 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 +/// intermediate struct pub struct Options { - /// Allow plugins to use a more compatible font type + /// Allow plugins to use a more simplified layout + /// that is compatible with more fonts #[structopt(long)] + pub simplified_ui: Option, +} + +#[derive(Clone, Default, Debug, PartialEq, Deserialize, Serialize)] +/// Merged version of the [`Options`] struct +// TODO: Maybe a good candidate for a macro? +pub struct ConfigOptions { pub simplified_ui: bool, } @@ -18,4 +29,35 @@ impl Options { 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 let Some(bool) = other.simplified_ui { + Some(bool) + } else { + self.simplified_ui + }; + + Options { simplified_ui } + } + + pub fn from_cli(&self, other: Option) -> Options { + if let Some(ConfigCli::Options(options)) = other { + Options::merge(&self, options) + } else { + self.to_owned() + } + } +} + +impl From for ConfigOptions { + fn from(options: Options) -> ConfigOptions { + let simplified_ui = options.simplified_ui; + + ConfigOptions { + simplified_ui: simplified_ui.unwrap_or_default(), + } + } } diff --git a/src/common/screen.rs b/src/common/screen.rs index 6f5a4cb3..804730d0 100644 --- a/src/common/screen.rs +++ b/src/common/screen.rs @@ -4,8 +4,7 @@ use std::collections::BTreeMap; use std::os::unix::io::RawFd; use std::str; -use crate::cli::ConfigCli; -use crate::common::input::options::Options; +use crate::common::input::options::ConfigOptions; use crate::common::pty::{PtyInstruction, VteBytes}; use crate::common::thread_bus::Bus; use crate::errors::{ContextType, ScreenContext}; @@ -330,15 +329,11 @@ pub fn screen_thread_main( bus: Bus, max_panes: Option, full_screen_ws: PositionAndSize, - options: Option, - config_options: Options, + config_options: ConfigOptions, ) { let colors = bus.os_input.as_ref().unwrap().load_palette(); - let capabilities = if let Some(ConfigCli::Options(options)) = options { - options.simplified_ui - } else { - config_options.simplified_ui - }; + let capabilities = config_options.simplified_ui; + let mut screen = Screen::new( bus, &full_screen_ws, diff --git a/src/server/mod.rs b/src/server/mod.rs index d9875727..4ef30ce2 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -12,7 +12,7 @@ use crate::client::ClientInstruction; use crate::common::thread_bus::{Bus, ThreadSenders}; use crate::common::{ errors::{ContextType, ServerContext}, - input::{actions::Action, options::Options}, + input::{actions::Action, options::ConfigOptions}, os_input_output::{set_permissions, ServerOsApi}, pty::{pty_thread_main, Pty, PtyInstruction}, screen::{screen_thread_main, ScreenInstruction}, @@ -30,7 +30,7 @@ use route::route_thread_main; #[derive(Serialize, Deserialize, Debug, Clone)] pub enum ServerInstruction { TerminalResize(PositionAndSize), - NewClient(PositionAndSize, CliArgs, Options), + NewClient(PositionAndSize, CliArgs, ConfigOptions), Action(Action), Render(Option), UnblockInputThread, @@ -155,7 +155,7 @@ pub fn start_server(os_input: Box) -> thread::JoinHandle<()> { fn init_session( os_input: Box, opts: CliArgs, - config_options: Options, + config_options: ConfigOptions, to_server: SenderWithContext, full_screen_ws: PositionAndSize, ) -> SessionMetaData { @@ -214,16 +214,9 @@ fn init_session( Some(os_input.clone()), ); let max_panes = opts.max_panes; - let options = opts.option; move || { - screen_thread_main( - screen_bus, - max_panes, - full_screen_ws, - options, - config_options, - ); + screen_thread_main(screen_bus, max_panes, full_screen_ws, config_options); } }) .unwrap(); From 9bb300e1c018c6939ac938c52330a51a3b7e9131 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Wed, 12 May 2021 11:27:53 +0200 Subject: [PATCH 39/44] fix(compatibility): properly fill viewport with styles when clearing it (#493) --- src/client/mod.rs | 5 ++ src/client/panes/grid.rs | 2 +- ...t__panes__grid__grid_tests__vttest2_0.snap | 19 ++++++ ...t__panes__grid__grid_tests__vttest2_1.snap | 19 ++++++ ...__panes__grid__grid_tests__vttest2_10.snap | 65 ++++++++++++------- ...__panes__grid__grid_tests__vttest2_11.snap | 65 ++++++++++++------- ...__panes__grid__grid_tests__vttest2_12.snap | 65 ++++++++++++------- ...__panes__grid__grid_tests__vttest2_13.snap | 65 ++++++++++++------- ...__panes__grid__grid_tests__vttest2_14.snap | 65 ++++++++++++------- ...t__panes__grid__grid_tests__vttest2_2.snap | 19 ++++++ ...t__panes__grid__grid_tests__vttest2_3.snap | 19 ++++++ ...t__panes__grid__grid_tests__vttest2_4.snap | 19 ++++++ ...t__panes__grid__grid_tests__vttest2_5.snap | 19 ++++++ ...t__panes__grid__grid_tests__vttest2_6.snap | 19 ++++++ ...t__panes__grid__grid_tests__vttest2_7.snap | 17 +++++ ...t__panes__grid__grid_tests__vttest2_8.snap | 61 ++++++++++------- ...t__panes__grid__grid_tests__vttest2_9.snap | 17 +++++ ...t__panes__grid__grid_tests__vttest3_0.snap | 17 +++++ ...t__panes__grid__grid_tests__vttest8_0.snap | 27 ++++++++ ...t__panes__grid__grid_tests__vttest8_1.snap | 27 ++++++++ ...t__panes__grid__grid_tests__vttest8_2.snap | 27 ++++++++ ...t__panes__grid__grid_tests__vttest8_3.snap | 27 ++++++++ ...t__panes__grid__grid_tests__vttest8_4.snap | 27 ++++++++ ...t__panes__grid__grid_tests__vttest8_5.snap | 27 ++++++++ 24 files changed, 596 insertions(+), 143 deletions(-) diff --git a/src/client/mod.rs b/src/client/mod.rs index 154f1437..5fb40096 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -31,12 +31,17 @@ pub enum ClientInstruction { } pub fn start_client(mut os_input: Box, opts: CliArgs, config: Config) { + let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}12l\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l"; let take_snapshot = "\u{1b}[?1049h"; os_input.unset_raw_mode(0); let _ = os_input .get_stdout_writer() .write(take_snapshot.as_bytes()) .unwrap(); + let _ = os_input + .get_stdout_writer() + .write(clear_client_terminal_attributes.as_bytes()) + .unwrap(); std::env::set_var(&"ZELLIJ", "0"); let mut command_is_executing = CommandIsExecuting::new(); diff --git a/src/client/panes/grid.rs b/src/client/panes/grid.rs index 0c873be8..c59e279b 100644 --- a/src/client/panes/grid.rs +++ b/src/client/panes/grid.rs @@ -1056,7 +1056,7 @@ impl Perform for Grid { } else if clear_type == 1 { self.clear_all_before_cursor(char_to_replace); } else if clear_type == 2 { - self.clear_all(char_to_replace); + self.fill_viewport(char_to_replace); } }; } else if c == 'H' || c == 'f' { diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_0.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_0.snap index 506c2226..f5bd11a5 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_0.snap +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_0.snap @@ -25,4 +25,23 @@ expression: "format!(\"{:?}\", grid)" 19 (C): 20 (C): 21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_1.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_1.snap index 48e47687..148b8732 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_1.snap +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_1.snap @@ -25,4 +25,23 @@ expression: "format!(\"{:?}\", grid)" 19 (C): 20 (C): 21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_10.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_10.snap index dbacac54..7cbb8545 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_10.snap +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_10.snap @@ -3,28 +3,45 @@ source: src/client/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- -00 (W): -01 (W): -02 (W): -03 (W): -04 (W): -05 (W): -06 (W): -07 (W): -08 (W): -09 (W): -10 (W): -11 (W): -12 (W): -13 (W): -14 (W): -15 (W): -16 (W): -17 (W): -18 (W): -19 (W): -20 (W): -21 (W): -22 (W): This line should be the one above the bottom of the screen. Push -23 (W): Origin mode test. This line should be at the bottom of the screen. +00 (C): +01 (C): +02 (C): +03 (C): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): +21 (C): +22 (C): This line should be the one above the bottom of the screen. Push +23 (C): Origin mode test. This line should be at the bottom of the screen. +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_11.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_11.snap index 3c424d45..1201290b 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_11.snap +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_11.snap @@ -3,28 +3,45 @@ source: src/client/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- -00 (W): This line should be at the top of the screen. Push -01 (W): -02 (W): -03 (W): -04 (W): -05 (W): -06 (W): -07 (W): -08 (W): -09 (W): -10 (W): -11 (W): -12 (W): -13 (W): -14 (W): -15 (W): -16 (W): -17 (W): -18 (W): -19 (W): -20 (W): -21 (W): -22 (W): -23 (W): Origin mode test. This line should be at the bottom of the screen. +00 (C): This line should be at the top of the screen. Push +01 (C): +02 (C): +03 (C): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): +11 (C): +12 (C): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): +21 (C): +22 (C): +23 (C): Origin mode test. This line should be at the bottom of the screen. +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_12.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_12.snap index fc2bd2de..fc15da63 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_12.snap +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_12.snap @@ -3,28 +3,45 @@ source: src/client/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- -00 (W): Graphic rendition test pattern: -01 (W): -02 (W): -03 (W): vanilla bold -04 (W): -05 (W): underline bold underline -06 (W): -07 (W): blink bold blink -08 (W): -09 (W): underline blink bold underline blink -10 (W): -11 (W): negative bold negative -12 (W): -13 (W): underline negative bold underline negative -14 (W): -15 (W): blink negative bold blink negative -16 (W): -17 (W): underline blink negative bold underline blink negative -18 (W): -19 (W): -20 (W): -21 (W): -22 (W): Dark background. Push -23 (W): +00 (C): Graphic rendition test pattern: +01 (C): +02 (C): +03 (C): vanilla bold +04 (C): +05 (C): underline bold underline +06 (C): +07 (C): blink bold blink +08 (C): +09 (C): underline blink bold underline blink +10 (C): +11 (C): negative bold negative +12 (C): +13 (C): underline negative bold underline negative +14 (C): +15 (C): blink negative bold blink negative +16 (C): +17 (C): underline blink negative bold underline blink negative +18 (C): +19 (C): +20 (C): +21 (C): +22 (C): Dark background. Push +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_13.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_13.snap index a79bc0cd..ee8e30fb 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_13.snap +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_13.snap @@ -3,28 +3,45 @@ source: src/client/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- -00 (W): Graphic rendition test pattern: -01 (W): -02 (W): -03 (W): vanilla bold -04 (W): -05 (W): underline bold underline -06 (W): -07 (W): blink bold blink -08 (W): -09 (W): underline blink bold underline blink -10 (W): -11 (W): negative bold negative -12 (W): -13 (W): underline negative bold underline negative -14 (W): -15 (W): blink negative bold blink negative -16 (W): -17 (W): underline blink negative bold underline blink negative -18 (W): -19 (W): -20 (W): -21 (W): -22 (W): Light background. Push -23 (W): +00 (C): Graphic rendition test pattern: +01 (C): +02 (C): +03 (C): vanilla bold +04 (C): +05 (C): underline bold underline +06 (C): +07 (C): blink bold blink +08 (C): +09 (C): underline blink bold underline blink +10 (C): +11 (C): negative bold negative +12 (C): +13 (C): underline negative bold underline negative +14 (C): +15 (C): blink negative bold blink negative +16 (C): +17 (C): underline blink negative bold underline blink negative +18 (C): +19 (C): +20 (C): +21 (C): +22 (C): Light background. Push +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_14.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_14.snap index dbdc3dc1..2bdf1956 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_14.snap +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_14.snap @@ -3,28 +3,45 @@ source: src/client/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- -00 (W): AAAAA -01 (W): AAAAA -02 (W): AAAAA -03 (W): AAAAA -04 (W): -05 (W): -06 (W): -07 (W): normal bold underscored blinking reversed -08 (W): -09 (W): stars: ********** ********** ********** ********** ********** -10 (W): -11 (W): line: ────────── ────────── ────────── ────────── ────────── -12 (W): -13 (W): x'es: xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx -14 (W): -15 (W): diamonds: ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆ -16 (W): -17 (W): -18 (W): -19 (W): -20 (W): Test of the SAVE/RESTORE CURSOR feature. There should -21 (W): be ten characters of each flavour, and a rectangle -22 (W): of 5 x 4 A's filling the top left of the screen. -23 (W): Push +00 (C): AAAAA +01 (C): AAAAA +02 (C): AAAAA +03 (C): AAAAA +04 (C): +05 (C): +06 (C): +07 (C): normal bold underscored blinking reversed +08 (C): +09 (C): stars: ********** ********** ********** ********** ********** +10 (C): +11 (C): line: ────────── ────────── ────────── ────────── ────────── +12 (C): +13 (C): x'es: xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx +14 (C): +15 (C): diamonds: ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆ +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): Test of the SAVE/RESTORE CURSOR feature. There should +21 (C): be ten characters of each flavour, and a rectangle +22 (C): of 5 x 4 A's filling the top left of the screen. +23 (C): Push +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_2.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_2.snap index 7b03c147..91a9a2e6 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_2.snap +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_2.snap @@ -25,4 +25,23 @@ expression: "format!(\"{:?}\", grid)" 19 (C): This is 132 column mode, light background.Push 20 (C): 21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_3.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_3.snap index 98620a5c..c3881efb 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_3.snap +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_3.snap @@ -25,4 +25,23 @@ expression: "format!(\"{:?}\", grid)" 19 (C): This is 80 column mode, light background.Push 20 (C): 21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_4.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_4.snap index 35802e63..089f3d7d 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_4.snap +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_4.snap @@ -25,4 +25,23 @@ expression: "format!(\"{:?}\", grid)" 19 (C): This is 132 column mode, dark background.Push 20 (C): 21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_5.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_5.snap index 2bcc2870..6cf729c1 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_5.snap +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_5.snap @@ -25,4 +25,23 @@ expression: "format!(\"{:?}\", grid)" 19 (C): This is 80 column mode, dark background.Push 20 (C): 21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_6.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_6.snap index 360dc423..3c6a1c77 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_6.snap +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_6.snap @@ -25,4 +25,23 @@ expression: "format!(\"{:?}\", grid)" 19 (C): 20 (C): 21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_7.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_7.snap index 467ae258..0c4c5cac 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_7.snap +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_7.snap @@ -27,4 +27,21 @@ expression: "format!(\"{:?}\", grid)" 21 (W): Soft scroll down region [1..24] size 24 Line 9 22 (W): Soft scroll down region [1..24] size 24 Line 8 23 (W): Soft scroll down region [1..24] size 24 Line 7 +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_8.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_8.snap index f892edd2..9e9ac891 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_8.snap +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_8.snap @@ -3,28 +3,45 @@ source: src/client/panes/./unit/grid_tests.rs expression: "format!(\"{:?}\", grid)" --- -00 (W): -01 (W): -02 (W): -03 (W): -04 (W): -05 (W): -06 (W): -07 (W): -08 (W): -09 (W): -10 (W): +00 (C): +01 (C): +02 (C): +03 (C): +04 (C): +05 (C): +06 (C): +07 (C): +08 (C): +09 (C): +10 (C): 11 (W): Push 12 (W): Jump scroll down region [12..13] size 2 Line 29 -13 (W): -14 (W): -15 (W): -16 (W): -17 (W): -18 (W): -19 (W): -20 (W): -21 (W): -22 (W): -23 (W): +13 (C): +14 (C): +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): +20 (C): +21 (C): +22 (C): +23 (C): +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_9.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_9.snap index 63e46c04..852203c8 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_9.snap +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_9.snap @@ -27,4 +27,21 @@ expression: "format!(\"{:?}\", grid)" 21 (W): Jump scroll down region [1..24] size 24 Line 9 22 (W): Jump scroll down region [1..24] size 24 Line 8 23 (W): Jump scroll down region [1..24] size 24 Line 7 +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest3_0.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest3_0.snap index 42c6f634..6902cf25 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest3_0.snap +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest3_0.snap @@ -27,4 +27,21 @@ expression: "format!(\"{:?}\", grid)" 21 (C): `abcdefghijklmnopqrstuvwxyz{|}~ `abcdefghijklmnopqrstuvwxyz{|}~ 22 (C): 23 (C): These are the installed character sets. Push +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_0.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_0.snap index 97beab77..18cdd01b 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_0.snap +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_0.snap @@ -27,4 +27,31 @@ expression: "format!(\"{:?}\", grid)" 21 (C): 22 (C): 23 (C): XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): +41 (C): +42 (C): +43 (C): +44 (C): +45 (C): +46 (C): +47 (C): +48 (C): +49 (C): +50 (C): diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_1.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_1.snap index 7490db07..7618e82b 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_1.snap +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_1.snap @@ -27,4 +27,31 @@ expression: "format!(\"{:?}\", grid)" 21 (C): 22 (C): 23 (C): +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): +41 (C): +42 (C): +43 (C): +44 (C): +45 (C): +46 (C): +47 (C): +48 (C): +49 (C): +50 (C): diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_2.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_2.snap index 7d5a4c68..0873ec23 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_2.snap +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_2.snap @@ -27,4 +27,31 @@ expression: "format!(\"{:?}\", grid)" 21 (C): 22 (C): 23 (C): +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): +41 (C): +42 (C): +43 (C): +44 (C): +45 (C): +46 (C): +47 (C): +48 (C): +49 (C): +50 (C): diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_3.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_3.snap index 3bfd896e..049f5083 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_3.snap +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_3.snap @@ -27,4 +27,31 @@ expression: "format!(\"{:?}\", grid)" 21 (C): VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV 22 (C): WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW 23 (C): XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): +41 (C): +42 (C): +43 (C): +44 (C): +45 (C): +46 (C): +47 (C): +48 (C): +49 (C): +50 (C): diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_4.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_4.snap index 8a7e8b35..e7eb978f 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_4.snap +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_4.snap @@ -27,4 +27,31 @@ expression: "format!(\"{:?}\", grid)" 21 (C): VVVVVVVVVVVVVVVVVV 22 (C): WWWWWWWWWWWWWWWWW 23 (C): XXXXXXXXXXXXXXXX +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): +41 (C): +42 (C): +43 (C): +44 (C): +45 (C): +46 (C): +47 (C): +48 (C): +49 (C): +50 (C): diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_5.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_5.snap index 34354a91..9b54cbc8 100644 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_5.snap +++ b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_5.snap @@ -27,4 +27,31 @@ expression: "format!(\"{:?}\", grid)" 21 (C): 22 (C): 23 (C): +24 (C): +25 (C): +26 (C): +27 (C): +28 (C): +29 (C): +30 (C): +31 (C): +32 (C): +33 (C): +34 (C): +35 (C): +36 (C): +37 (C): +38 (C): +39 (C): +40 (C): +41 (C): +42 (C): +43 (C): +44 (C): +45 (C): +46 (C): +47 (C): +48 (C): +49 (C): +50 (C): From ce50d6e4061a3c24ec0f0d4644037f95fcda94d7 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Wed, 12 May 2021 11:29:27 +0200 Subject: [PATCH 40/44] docs(changelog): compatibility update --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d5c9ff5..4c31aaeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Change Switch default config loading order of `HOME` and system (https://github.com/zellij-org/zellij/pull/488) * Add support for requesting a simpler layout from plugins, move `clean` flag from `options` to `setup` (https://github.com/zellij-org/zellij/pull/479) * Improve config loading slightly (https://github.com/zellij-org/zellij/pull/492) +* Terminal compatibility: preserve current style when clearing viewport (https://github.com/zellij-org/zellij/pull/493) ## [0.9.0] - 2021-05-11 * Add more functionality to unbinding the default keybindings (https://github.com/zellij-org/zellij/pull/468) From 3f1ec7c29590fea0805e7effb605548606b58306 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Wed, 12 May 2021 14:22:15 +0200 Subject: [PATCH 41/44] fix(ui): handle pasted text properly (#494) * fix(ui): handle pasted text properly * style(fmt): rustfmt --- src/client/mod.rs | 7 ++- src/common/input/handler.rs | 26 ++++++++-- src/tests/integration/basic.rs | 48 +++++++++++++++++-- ...__integration__basic__bracketed_paste.snap | 25 ++++++++++ src/tests/utils.rs | 3 ++ 5 files changed, 100 insertions(+), 9 deletions(-) create mode 100644 src/tests/integration/snapshots/zellij__tests__integration__basic__bracketed_paste.snap diff --git a/src/client/mod.rs b/src/client/mod.rs index 5fb40096..ae60fc87 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -31,8 +31,9 @@ pub enum ClientInstruction { } pub fn start_client(mut os_input: Box, opts: CliArgs, config: Config) { - let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}12l\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l"; + let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}12l\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l\u{1b}[?12l"; let take_snapshot = "\u{1b}[?1049h"; + let bracketed_paste = "\u{1b}[?2004h"; os_input.unset_raw_mode(0); let _ = os_input .get_stdout_writer() @@ -57,6 +58,10 @@ pub fn start_client(mut os_input: Box, opts: CliArgs, config: C config_options, )); os_input.set_raw_mode(0); + let _ = os_input + .get_stdout_writer() + .write(bracketed_paste.as_bytes()) + .unwrap(); let (send_client_instructions, receive_client_instructions): SyncChannelWithContext< ClientInstruction, diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs index 68a75448..62e2e909 100644 --- a/src/common/input/handler.rs +++ b/src/common/input/handler.rs @@ -23,6 +23,7 @@ struct InputHandler { command_is_executing: CommandIsExecuting, send_client_instructions: SenderWithContext, should_exit: bool, + pasting: bool, } impl InputHandler { @@ -40,6 +41,7 @@ impl InputHandler { command_is_executing, send_client_instructions, should_exit: false, + pasting: false, } } @@ -49,6 +51,8 @@ impl InputHandler { let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow()); err_ctx.add_call(ContextType::StdinHandler); let alt_left_bracket = vec![27, 91]; + let bracketed_paste_start = vec![27, 91, 50, 48, 48, 126]; // \u{1b}[200~ + let bracketed_paste_end = vec![27, 91, 50, 48, 49, 126]; // \u{1b}[201 loop { if self.should_exit { break; @@ -67,6 +71,10 @@ impl InputHandler { if unsupported_key == alt_left_bracket { let key = Key::Alt('['); self.handle_key(&key, raw_bytes); + } else if unsupported_key == bracketed_paste_start { + self.pasting = true; + } else if unsupported_key == bracketed_paste_end { + self.pasting = false; } } termion::event::Event::Mouse(_) => { @@ -81,10 +89,20 @@ impl InputHandler { } fn handle_key(&mut self, key: &Key, raw_bytes: Vec) { let keybinds = &self.config.keybinds; - for action in Keybinds::key_to_actions(&key, raw_bytes, &self.mode, keybinds) { - let should_exit = self.dispatch_action(action); - if should_exit { - self.should_exit = true; + if self.pasting { + // we're inside a paste block, if we're in a mode that allows sending text to the + // terminal, send all text directly without interpreting it + // otherwise, just discard the input + if self.mode == InputMode::Normal || self.mode == InputMode::Locked { + let action = Action::Write(raw_bytes); + self.dispatch_action(action); + } + } else { + for action in Keybinds::key_to_actions(&key, raw_bytes, &self.mode, keybinds) { + let should_exit = self.dispatch_action(action); + if should_exit { + self.should_exit = true; + } } } } diff --git a/src/tests/integration/basic.rs b/src/tests/integration/basic.rs index c8305cab..5b72dbfe 100644 --- a/src/tests/integration/basic.rs +++ b/src/tests/integration/basic.rs @@ -4,10 +4,10 @@ use ::insta::assert_snapshot; use crate::common::input::config::Config; use crate::tests::fakes::FakeInputOutput; use crate::tests::utils::commands::{ - PANE_MODE, QUIT, SCROLL_DOWN_IN_SCROLL_MODE, SCROLL_MODE, SCROLL_PAGE_DOWN_IN_SCROLL_MODE, - SCROLL_PAGE_UP_IN_SCROLL_MODE, SCROLL_UP_IN_SCROLL_MODE, SPAWN_TERMINAL_IN_PANE_MODE, - SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, - TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE, + BRACKETED_PASTE_END, BRACKETED_PASTE_START, PANE_MODE, QUIT, SCROLL_DOWN_IN_SCROLL_MODE, + SCROLL_MODE, SCROLL_PAGE_DOWN_IN_SCROLL_MODE, SCROLL_PAGE_UP_IN_SCROLL_MODE, + SCROLL_UP_IN_SCROLL_MODE, SPAWN_TERMINAL_IN_PANE_MODE, SPLIT_DOWN_IN_PANE_MODE, + SPLIT_RIGHT_IN_PANE_MODE, TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE, }; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::{start, CliArgs}; @@ -441,3 +441,43 @@ pub fn toggle_focused_pane_fullscreen() { get_next_to_last_snapshot(snapshots).expect("could not find snapshot"); assert_snapshot!(snapshot_before_quit); } + +#[test] +pub fn bracketed_paste() { + // bracketed paste (https://xfree86.org/current/ctlseqs.html#Bracketed%20Paste%20Mode) + // makes sure that text the user pastes is not interpreted as commands by the running program + // (zellij in this case) + // this tests makes sure the "SPLIT_RIGHT_IN_PANE_MODE" command is not interpreted as Zellij, + // since it's inside a bracketed paste block, while the "QUIT" command is, since it is already + // past the block + let fake_win_size = PositionAndSize { + columns: 121, + rows: 20, + x: 0, + y: 0, + ..Default::default() + }; + let mut fake_input_output = get_fake_os_input(&fake_win_size); + fake_input_output.add_terminal_input(&[ + &PANE_MODE, + &BRACKETED_PASTE_START, + &SPLIT_RIGHT_IN_PANE_MODE, + &BRACKETED_PASTE_END, + &QUIT, + ]); + start( + Box::new(fake_input_output.clone()), + CliArgs::default(), + Box::new(fake_input_output.clone()), + Config::default(), + ); + let output_frames = fake_input_output + .stdout_writer + .output_frames + .lock() + .unwrap(); + let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size); + let snapshot_before_quit = + get_next_to_last_snapshot(snapshots).expect("could not find snapshot"); + assert_snapshot!(snapshot_before_quit); +} diff --git a/src/tests/integration/snapshots/zellij__tests__integration__basic__bracketed_paste.snap b/src/tests/integration/snapshots/zellij__tests__integration__basic__bracketed_paste.snap new file mode 100644 index 00000000..1eec02f4 --- /dev/null +++ b/src/tests/integration/snapshots/zellij__tests__integration__basic__bracketed_paste.snap @@ -0,0 +1,25 @@ +--- +source: src/tests/integration/basic.rs +expression: snapshot_before_quit + +--- +line1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line2-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line3-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line4-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line5-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line6-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line7-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line8-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line9-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line10-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +prompt $ █ diff --git a/src/tests/utils.rs b/src/tests/utils.rs index 10b243a7..f01c1ac5 100644 --- a/src/tests/utils.rs +++ b/src/tests/utils.rs @@ -81,5 +81,8 @@ pub mod commands { pub const SWITCH_NEXT_TAB_IN_TAB_MODE: [u8; 1] = [108]; // l pub const SWITCH_PREV_TAB_IN_TAB_MODE: [u8; 1] = [104]; // h pub const CLOSE_TAB_IN_TAB_MODE: [u8; 1] = [120]; // x + + pub const BRACKETED_PASTE_START: [u8; 6] = [27, 91, 50, 48, 48, 126]; // \u{1b}[200~ + pub const BRACKETED_PASTE_END: [u8; 6] = [27, 91, 50, 48, 49, 126]; // \u{1b}[201 pub const SLEEP: [u8; 0] = []; } From ce00837ca248dcf8c4df8da942379ecc1ca462bc Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Wed, 12 May 2021 14:22:59 +0200 Subject: [PATCH 42/44] docs(changelog): pasted text fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c31aaeb..d7722b92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Add support for requesting a simpler layout from plugins, move `clean` flag from `options` to `setup` (https://github.com/zellij-org/zellij/pull/479) * Improve config loading slightly (https://github.com/zellij-org/zellij/pull/492) * Terminal compatibility: preserve current style when clearing viewport (https://github.com/zellij-org/zellij/pull/493) +* Handle pasted text properly (https://github.com/zellij-org/zellij/pull/494) ## [0.9.0] - 2021-05-11 * Add more functionality to unbinding the default keybindings (https://github.com/zellij-org/zellij/pull/468) From 776a2c5151f768bfa40fda7471258527c106dd85 Mon Sep 17 00:00:00 2001 From: a-kenji Date: Wed, 12 May 2021 13:32:39 +0200 Subject: [PATCH 43/44] Fix Simplified Ui Propagate the config now, in all the places, as opposed to fallback to a default. --- CHANGELOG.md | 1 + src/client/mod.rs | 5 ++-- src/common/input/handler.rs | 7 +++-- src/common/input/options.rs | 22 ++------------ src/common/screen.rs | 4 +-- src/main.rs | 15 ++++++++-- src/server/mod.rs | 30 +++++++++++++++---- src/server/route.rs | 25 ++++++++++++---- src/tests/integration/basic.rs | 15 +++++++++- src/tests/integration/close_pane.rs | 15 +++++++++- src/tests/integration/compatibility.rs | 22 +++++++++++++- src/tests/integration/layouts.rs | 3 +- src/tests/integration/move_focus_down.rs | 4 ++- src/tests/integration/move_focus_left.rs | 5 +++- src/tests/integration/move_focus_right.rs | 5 +++- src/tests/integration/move_focus_up.rs | 4 ++- src/tests/integration/resize_down.rs | 15 +++++++++- src/tests/integration/resize_left.rs | 15 +++++++++- src/tests/integration/resize_right.rs | 15 +++++++++- src/tests/integration/resize_up.rs | 15 +++++++++- src/tests/integration/tabs.rs | 10 ++++++- .../integration/terminal_window_resize.rs | 6 +++- src/tests/integration/toggle_fullscreen.rs | 4 ++- zellij-tile/src/data.rs | 8 ++++- 24 files changed, 213 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c31aaeb..9cf212d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Add support for requesting a simpler layout from plugins, move `clean` flag from `options` to `setup` (https://github.com/zellij-org/zellij/pull/479) * Improve config loading slightly (https://github.com/zellij-org/zellij/pull/492) * Terminal compatibility: preserve current style when clearing viewport (https://github.com/zellij-org/zellij/pull/493) +* Fix propagation of plugin ui request (https://github.com/zellij-org/zellij/pull/495) ## [0.9.0] - 2021-05-11 * Add more functionality to unbinding the default keybindings (https://github.com/zellij-org/zellij/pull/468) diff --git a/src/client/mod.rs b/src/client/mod.rs index 5fb40096..3dfb24aa 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -15,7 +15,7 @@ use crate::common::{ errors::{ClientContext, ContextType}, input::config::Config, input::handler::input_loop, - input::options::{ConfigOptions, Options}, + input::options::Options, os_input_output::ClientOsApi, thread_bus::{SenderType, SenderWithContext, SyncChannelWithContext}, }; @@ -46,8 +46,7 @@ pub fn start_client(mut os_input: Box, opts: CliArgs, config: C let mut command_is_executing = CommandIsExecuting::new(); - let config_options: ConfigOptions = - Options::from_cli(&config.options, opts.option.clone()).into(); + let config_options = Options::from_cli(&config.options, opts.option.clone()); let full_screen_ws = os_input.get_terminal_size_using_fd(0); os_input.connect_to_server(); diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs index 68a75448..b3bbbd0c 100644 --- a/src/common/input/handler.rs +++ b/src/common/input/handler.rs @@ -147,9 +147,12 @@ impl InputHandler { /// Creates a [`Help`] struct indicating the current [`InputMode`] and its keybinds /// (as pairs of [`String`]s). // TODO this should probably be automatically generated in some way -pub fn get_mode_info(mode: InputMode, palette: Palette) -> ModeInfo { +pub fn get_mode_info( + mode: InputMode, + palette: Palette, + capabilities: PluginCapabilities, +) -> ModeInfo { let mut keybinds: Vec<(String, String)> = vec![]; - let capabilities = PluginCapabilities { arrow_fonts: true }; match mode { InputMode::Normal | InputMode::Locked => {} InputMode::Resize => { diff --git a/src/common/input/options.rs b/src/common/input/options.rs index 3bd3fc31..33625b65 100644 --- a/src/common/input/options.rs +++ b/src/common/input/options.rs @@ -6,18 +6,10 @@ use structopt::StructOpt; #[derive(Clone, Default, Debug, PartialEq, Deserialize, Serialize, StructOpt)] /// Options that can be set either through the config file, /// or cli flags -/// intermediate struct pub struct Options { /// Allow plugins to use a more simplified layout /// that is compatible with more fonts #[structopt(long)] - pub simplified_ui: Option, -} - -#[derive(Clone, Default, Debug, PartialEq, Deserialize, Serialize)] -/// Merged version of the [`Options`] struct -// TODO: Maybe a good candidate for a macro? -pub struct ConfigOptions { pub simplified_ui: bool, } @@ -34,8 +26,8 @@ impl Options { /// 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 let Some(bool) = other.simplified_ui { - Some(bool) + let simplified_ui = if other.simplified_ui { + true } else { self.simplified_ui }; @@ -51,13 +43,3 @@ impl Options { } } } - -impl From for ConfigOptions { - fn from(options: Options) -> ConfigOptions { - let simplified_ui = options.simplified_ui; - - ConfigOptions { - simplified_ui: simplified_ui.unwrap_or_default(), - } - } -} diff --git a/src/common/screen.rs b/src/common/screen.rs index 804730d0..bc99f953 100644 --- a/src/common/screen.rs +++ b/src/common/screen.rs @@ -4,7 +4,7 @@ use std::collections::BTreeMap; use std::os::unix::io::RawFd; use std::str; -use crate::common::input::options::ConfigOptions; +use crate::common::input::options::Options; use crate::common::pty::{PtyInstruction, VteBytes}; use crate::common::thread_bus::Bus; use crate::errors::{ContextType, ScreenContext}; @@ -329,7 +329,7 @@ pub fn screen_thread_main( bus: Bus, max_panes: Option, full_screen_ws: PositionAndSize, - config_options: ConfigOptions, + config_options: Options, ) { let colors = bus.os_input.as_ref().unwrap().load_palette(); let capabilities = config_options.simplified_ui; diff --git a/src/main.rs b/src/main.rs index ffc2373e..cb016067 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,7 @@ use structopt::StructOpt; use crate::cli::CliArgs; use crate::command_is_executing::CommandIsExecuting; -use crate::common::input::config::Config; +use crate::common::input::{config::Config, options::Options}; use crate::os_input_output::{get_client_os_input, get_server_os_input, ClientOsApi, ServerOsApi}; use crate::utils::{ consts::{ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR}, @@ -29,6 +29,8 @@ pub fn main() { std::process::exit(1); } }; + let config_options = Options::from_cli(&config.options, opts.option.clone()); + if let Some(crate::cli::ConfigCli::GenerateCompletion { shell }) = opts.option { let shell = match shell.as_ref() { "bash" => structopt::clap::Shell::Bash, @@ -51,7 +53,13 @@ pub fn main() { atomic_create_dir(&*ZELLIJ_TMP_LOG_DIR).unwrap(); let server_os_input = get_server_os_input(); let os_input = get_client_os_input(); - start(Box::new(os_input), opts, Box::new(server_os_input), config); + start( + Box::new(os_input), + opts, + Box::new(server_os_input), + config, + config_options, + ); } } pub fn start( @@ -59,8 +67,9 @@ pub fn start( opts: CliArgs, server_os_input: Box, config: Config, + config_options: Options, ) { - let ipc_thread = start_server(server_os_input); + let ipc_thread = start_server(server_os_input, config_options); start_client(client_os_input, opts, config); drop(ipc_thread.join()); } diff --git a/src/server/mod.rs b/src/server/mod.rs index 4ef30ce2..622a3257 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -6,13 +6,14 @@ use std::sync::{Arc, RwLock}; use std::thread; use std::{path::PathBuf, sync::mpsc::channel}; use wasmer::Store; +use zellij_tile::data::PluginCapabilities; use crate::cli::CliArgs; use crate::client::ClientInstruction; use crate::common::thread_bus::{Bus, ThreadSenders}; use crate::common::{ errors::{ContextType, ServerContext}, - input::{actions::Action, options::ConfigOptions}, + input::{actions::Action, options::Options}, os_input_output::{set_permissions, ServerOsApi}, pty::{pty_thread_main, Pty, PtyInstruction}, screen::{screen_thread_main, ScreenInstruction}, @@ -30,7 +31,7 @@ use route::route_thread_main; #[derive(Serialize, Deserialize, Debug, Clone)] pub enum ServerInstruction { TerminalResize(PositionAndSize), - NewClient(PositionAndSize, CliArgs, ConfigOptions), + NewClient(PositionAndSize, CliArgs, Options), Action(Action), Render(Option), UnblockInputThread, @@ -55,7 +56,10 @@ impl Drop for SessionMetaData { } } -pub fn start_server(os_input: Box) -> thread::JoinHandle<()> { +pub fn start_server( + os_input: Box, + config_options: Options, +) -> thread::JoinHandle<()> { let (to_server, server_receiver): ChannelWithContext = channel(); let to_server = SenderWithContext::new(SenderType::Sender(to_server)); let sessions: Arc>> = Arc::new(RwLock::new(None)); @@ -67,8 +71,11 @@ pub fn start_server(os_input: Box) -> thread::JoinHandle<()> { let sessions = sessions.clone(); let os_input = os_input.clone(); let to_server = to_server.clone(); + let capabilities = PluginCapabilities { + arrow_fonts: !config_options.simplified_ui, + }; - move || route_thread_main(sessions, os_input, to_server) + move || route_thread_main(sessions, os_input, to_server, capabilities) }) .unwrap(); #[cfg(not(test))] @@ -78,6 +85,9 @@ pub fn start_server(os_input: Box) -> thread::JoinHandle<()> { let os_input = os_input.clone(); let sessions = sessions.clone(); let to_server = to_server.clone(); + let capabilities = PluginCapabilities { + arrow_fonts: config_options.simplified_ui, + }; move || { drop(std::fs::remove_file(&*ZELLIJ_IPC_PIPE)); let listener = LocalSocketListener::bind(&**ZELLIJ_IPC_PIPE).unwrap(); @@ -96,7 +106,14 @@ pub fn start_server(os_input: Box) -> thread::JoinHandle<()> { let os_input = os_input.clone(); let to_server = to_server.clone(); - move || route_thread_main(sessions, os_input, to_server) + move || { + route_thread_main( + sessions, + os_input, + to_server, + capabilities, + ) + } }) .unwrap(); } @@ -155,7 +172,7 @@ pub fn start_server(os_input: Box) -> thread::JoinHandle<()> { fn init_session( os_input: Box, opts: CliArgs, - config_options: ConfigOptions, + config_options: Options, to_server: SenderWithContext, full_screen_ws: PositionAndSize, ) -> SessionMetaData { @@ -214,6 +231,7 @@ fn init_session( Some(os_input.clone()), ); let max_panes = opts.max_panes; + let config_options = config_options; move || { screen_thread_main(screen_bus, max_panes, full_screen_ws, config_options); diff --git a/src/server/route.rs b/src/server/route.rs index 668cdfbd..bd4b1b60 100644 --- a/src/server/route.rs +++ b/src/server/route.rs @@ -1,6 +1,6 @@ use std::sync::{Arc, RwLock}; -use zellij_tile::data::Event; +use zellij_tile::data::{Event, PluginCapabilities}; use crate::common::errors::{ContextType, ServerContext}; use crate::common::input::actions::{Action, Direction}; @@ -12,7 +12,12 @@ use crate::common::thread_bus::SenderWithContext; use crate::common::wasm_vm::PluginInstruction; use crate::server::{ServerInstruction, SessionMetaData}; -fn route_action(action: Action, session: &SessionMetaData, os_input: &dyn ServerOsApi) { +fn route_action( + action: Action, + session: &SessionMetaData, + os_input: &dyn ServerOsApi, + capabilities: PluginCapabilities, +) { match action { Action::Write(val) => { session @@ -30,12 +35,16 @@ fn route_action(action: Action, session: &SessionMetaData, os_input: &dyn Server .senders .send_to_plugin(PluginInstruction::Update( None, - Event::ModeUpdate(get_mode_info(mode, palette)), + Event::ModeUpdate(get_mode_info(mode, palette, capabilities)), )) .unwrap(); session .senders - .send_to_screen(ScreenInstruction::ChangeMode(get_mode_info(mode, palette))) + .send_to_screen(ScreenInstruction::ChangeMode(get_mode_info( + mode, + palette, + capabilities, + ))) .unwrap(); session .senders @@ -181,6 +190,7 @@ pub fn route_thread_main( sessions: Arc>>, mut os_input: Box, to_server: SenderWithContext, + capabilities: PluginCapabilities, ) { loop { let (instruction, mut err_ctx) = os_input.recv_from_client(); @@ -192,7 +202,12 @@ pub fn route_thread_main( break; } ServerInstruction::Action(action) => { - route_action(action, rlocked_sessions.as_ref().unwrap(), &*os_input); + route_action( + action, + rlocked_sessions.as_ref().unwrap(), + &*os_input, + capabilities, + ); } ServerInstruction::TerminalResize(new_size) => { rlocked_sessions diff --git a/src/tests/integration/basic.rs b/src/tests/integration/basic.rs index c8305cab..4d2c4878 100644 --- a/src/tests/integration/basic.rs +++ b/src/tests/integration/basic.rs @@ -1,7 +1,7 @@ use crate::panes::PositionAndSize; use ::insta::assert_snapshot; -use crate::common::input::config::Config; +use crate::common::input::{config::Config, options::Options}; use crate::tests::fakes::FakeInputOutput; use crate::tests::utils::commands::{ PANE_MODE, QUIT, SCROLL_DOWN_IN_SCROLL_MODE, SCROLL_MODE, SCROLL_PAGE_DOWN_IN_SCROLL_MODE, @@ -32,6 +32,7 @@ pub fn starts_with_one_terminal() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -60,6 +61,7 @@ pub fn split_terminals_vertically() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -88,6 +90,7 @@ pub fn split_terminals_horizontally() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -123,6 +126,7 @@ pub fn split_largest_terminal() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -151,6 +155,7 @@ pub fn cannot_split_terminals_vertically_when_active_terminal_is_too_small() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -179,6 +184,7 @@ pub fn cannot_split_terminals_horizontally_when_active_terminal_is_too_small() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -207,6 +213,7 @@ pub fn cannot_split_largest_terminal_when_there_is_no_room() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -243,6 +250,7 @@ pub fn scrolling_up_inside_a_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -281,6 +289,7 @@ pub fn scrolling_down_inside_a_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -316,6 +325,7 @@ pub fn scrolling_page_up_inside_a_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -354,6 +364,7 @@ pub fn scrolling_page_down_inside_a_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -393,6 +404,7 @@ pub fn max_panes() { opts, Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -430,6 +442,7 @@ pub fn toggle_focused_pane_fullscreen() { opts, Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer diff --git a/src/tests/integration/close_pane.rs b/src/tests/integration/close_pane.rs index 869debed..d151f63d 100644 --- a/src/tests/integration/close_pane.rs +++ b/src/tests/integration/close_pane.rs @@ -5,7 +5,7 @@ use crate::tests::fakes::FakeInputOutput; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::{start, CliArgs}; -use crate::common::input::config::Config; +use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::{ CLOSE_PANE_IN_PANE_MODE, ESC, MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_DOWN_IN_RESIZE_MODE, RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE, RESIZE_UP_IN_RESIZE_MODE, @@ -45,6 +45,7 @@ pub fn close_pane_with_another_pane_above_it() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -88,6 +89,7 @@ pub fn close_pane_with_another_pane_below_it() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -128,6 +130,7 @@ pub fn close_pane_with_another_pane_to_the_left() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -169,6 +172,7 @@ pub fn close_pane_with_another_pane_to_the_right() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -215,6 +219,7 @@ pub fn close_pane_with_multiple_panes_above_it() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -259,6 +264,7 @@ pub fn close_pane_with_multiple_panes_below_it() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -305,6 +311,7 @@ pub fn close_pane_with_multiple_panes_to_the_left() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -349,6 +356,7 @@ pub fn close_pane_with_multiple_panes_to_the_right() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -415,6 +423,7 @@ pub fn close_pane_with_multiple_panes_above_it_away_from_screen_edges() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -477,6 +486,7 @@ pub fn close_pane_with_multiple_panes_below_it_away_from_screen_edges() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -541,6 +551,7 @@ pub fn close_pane_with_multiple_panes_to_the_left_away_from_screen_edges() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -605,6 +616,7 @@ pub fn close_pane_with_multiple_panes_to_the_right_away_from_screen_edges() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -642,6 +654,7 @@ pub fn closing_last_pane_exits_app() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output diff --git a/src/tests/integration/compatibility.rs b/src/tests/integration/compatibility.rs index 77d3b67e..b986b1de 100644 --- a/src/tests/integration/compatibility.rs +++ b/src/tests/integration/compatibility.rs @@ -7,7 +7,7 @@ use crate::tests::possible_tty_inputs::Bytes; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::{start, CliArgs}; -use crate::common::input::config::Config; +use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::QUIT; /* @@ -48,6 +48,7 @@ pub fn run_bandwhich_from_fish_shell() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -77,6 +78,7 @@ pub fn fish_tab_completion_options() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -111,6 +113,7 @@ pub fn fish_select_tab_completion_options() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -149,6 +152,7 @@ pub fn vim_scroll_region_down() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -184,6 +188,7 @@ pub fn vim_ctrl_d() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -218,6 +223,7 @@ pub fn vim_ctrl_u() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -247,6 +253,7 @@ pub fn htop() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -276,6 +283,7 @@ pub fn htop_scrolling() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -305,6 +313,7 @@ pub fn htop_right_scrolling() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -342,6 +351,7 @@ pub fn vim_overwrite() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -374,6 +384,7 @@ pub fn clear_scroll_region() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -403,6 +414,7 @@ pub fn display_tab_characters_properly() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -432,6 +444,7 @@ pub fn neovim_insert_mode() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -463,6 +476,7 @@ pub fn bash_cursor_linewrap() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -494,6 +508,7 @@ pub fn fish_paste_multiline() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -523,6 +538,7 @@ pub fn git_log() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -554,6 +570,7 @@ pub fn git_diff_scrollup() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -583,6 +600,7 @@ pub fn emacs_longbuf() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -612,6 +630,7 @@ pub fn top_and_quit() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -647,6 +666,7 @@ pub fn exa_plus_omf_theme() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer diff --git a/src/tests/integration/layouts.rs b/src/tests/integration/layouts.rs index a07995f6..bd7ceb3f 100644 --- a/src/tests/integration/layouts.rs +++ b/src/tests/integration/layouts.rs @@ -1,7 +1,7 @@ use insta::assert_snapshot; use std::path::PathBuf; -use crate::common::input::config::Config; +use crate::common::input::{config::Config, options::Options}; use crate::panes::PositionAndSize; use crate::tests::fakes::FakeInputOutput; use crate::tests::utils::commands::QUIT; @@ -33,6 +33,7 @@ pub fn accepts_basic_layout() { opts, Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer diff --git a/src/tests/integration/move_focus_down.rs b/src/tests/integration/move_focus_down.rs index 8ea953c6..e182acf9 100644 --- a/src/tests/integration/move_focus_down.rs +++ b/src/tests/integration/move_focus_down.rs @@ -5,7 +5,7 @@ use crate::tests::fakes::FakeInputOutput; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::{start, CliArgs}; -use crate::common::input::config::Config; +use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::{ MOVE_FOCUS_DOWN_IN_PANE_MODE, MOVE_FOCUS_UP_IN_PANE_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, @@ -37,6 +37,7 @@ pub fn move_focus_down() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -74,6 +75,7 @@ pub fn move_focus_down_to_the_most_recently_used_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output diff --git a/src/tests/integration/move_focus_left.rs b/src/tests/integration/move_focus_left.rs index 5b6e0504..c991d1e8 100644 --- a/src/tests/integration/move_focus_left.rs +++ b/src/tests/integration/move_focus_left.rs @@ -5,7 +5,7 @@ use crate::tests::fakes::FakeInputOutput; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::{start, CliArgs}; -use crate::common::input::config::Config; +use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::{ ENTER, MOVE_FOCUS_LEFT_IN_NORMAL_MODE, MOVE_FOCUS_LEFT_IN_PANE_MODE, MOVE_FOCUS_RIGHT_IN_PANE_MODE, NEW_TAB_IN_TAB_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE, @@ -37,6 +37,7 @@ pub fn move_focus_left() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -75,6 +76,7 @@ pub fn move_focus_left_to_the_most_recently_used_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -113,6 +115,7 @@ pub fn move_focus_left_changes_tab() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output diff --git a/src/tests/integration/move_focus_right.rs b/src/tests/integration/move_focus_right.rs index e5d5ef3e..ffc8cacf 100644 --- a/src/tests/integration/move_focus_right.rs +++ b/src/tests/integration/move_focus_right.rs @@ -5,7 +5,7 @@ use crate::tests::fakes::FakeInputOutput; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::{start, CliArgs}; -use crate::common::input::config::Config; +use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::{ ENTER, MOVE_FOCUS_LEFT_IN_PANE_MODE, MOVE_FOCUS_RIGHT_IN_NORMAL_MODE, MOVE_FOCUS_RIGHT_IN_PANE_MODE, NEW_TAB_IN_TAB_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE, @@ -38,6 +38,7 @@ pub fn move_focus_right() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -75,6 +76,7 @@ pub fn move_focus_right_to_the_most_recently_used_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -113,6 +115,7 @@ pub fn move_focus_right_changes_tab() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output diff --git a/src/tests/integration/move_focus_up.rs b/src/tests/integration/move_focus_up.rs index 62b64ed1..54bfef5e 100644 --- a/src/tests/integration/move_focus_up.rs +++ b/src/tests/integration/move_focus_up.rs @@ -5,7 +5,7 @@ use crate::tests::fakes::FakeInputOutput; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::{start, CliArgs}; -use crate::common::input::config::Config; +use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::{ MOVE_FOCUS_DOWN_IN_PANE_MODE, MOVE_FOCUS_UP_IN_PANE_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, @@ -36,6 +36,7 @@ pub fn move_focus_up() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -74,6 +75,7 @@ pub fn move_focus_up_to_the_most_recently_used_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output diff --git a/src/tests/integration/resize_down.rs b/src/tests/integration/resize_down.rs index 58104fe1..0e710b4e 100644 --- a/src/tests/integration/resize_down.rs +++ b/src/tests/integration/resize_down.rs @@ -5,7 +5,7 @@ use crate::tests::fakes::FakeInputOutput; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::{start, CliArgs}; -use crate::common::input::config::Config; +use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::{ MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_DOWN_IN_RESIZE_MODE, RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE, SLEEP, SPLIT_DOWN_IN_PANE_MODE, @@ -48,6 +48,7 @@ pub fn resize_down_with_pane_above() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -93,6 +94,7 @@ pub fn resize_down_with_pane_below() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -144,6 +146,7 @@ pub fn resize_down_with_panes_above_and_below() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -194,6 +197,7 @@ pub fn resize_down_with_multiple_panes_above() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -246,6 +250,7 @@ pub fn resize_down_with_panes_above_aligned_left_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -297,6 +302,7 @@ pub fn resize_down_with_panes_below_aligned_left_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -346,6 +352,7 @@ pub fn resize_down_with_panes_above_aligned_right_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -396,6 +403,7 @@ pub fn resize_down_with_panes_below_aligned_right_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -449,6 +457,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -504,6 +513,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -576,6 +586,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -650,6 +661,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_ CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -692,6 +704,7 @@ pub fn cannot_resize_down_when_pane_below_is_at_minimum_height() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output diff --git a/src/tests/integration/resize_left.rs b/src/tests/integration/resize_left.rs index d31dd6d2..5d498e96 100644 --- a/src/tests/integration/resize_left.rs +++ b/src/tests/integration/resize_left.rs @@ -5,7 +5,7 @@ use crate::tests::fakes::FakeInputOutput; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::{start, CliArgs}; -use crate::common::input::config::Config; +use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::{ MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE, RESIZE_UP_IN_RESIZE_MODE, SLEEP, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, @@ -44,6 +44,7 @@ pub fn resize_left_with_pane_to_the_left() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -87,6 +88,7 @@ pub fn resize_left_with_pane_to_the_right() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -132,6 +134,7 @@ pub fn resize_left_with_panes_to_the_left_and_right() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -180,6 +183,7 @@ pub fn resize_left_with_multiple_panes_to_the_left() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -230,6 +234,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -277,6 +282,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -326,6 +332,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_bottom_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -374,6 +381,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_bottom_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -427,6 +435,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_current_pa CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -482,6 +491,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_current_p CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -554,6 +564,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abov CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -629,6 +640,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_abo CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -671,6 +683,7 @@ pub fn cannot_resize_left_when_pane_to_the_left_is_at_minimum_width() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output diff --git a/src/tests/integration/resize_right.rs b/src/tests/integration/resize_right.rs index e5ea1b9d..8be15315 100644 --- a/src/tests/integration/resize_right.rs +++ b/src/tests/integration/resize_right.rs @@ -5,7 +5,7 @@ use crate::tests::fakes::FakeInputOutput; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::{start, CliArgs}; -use crate::common::input::config::Config; +use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::{ MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_MODE, RESIZE_RIGHT_IN_RESIZE_MODE, RESIZE_UP_IN_RESIZE_MODE, SLEEP, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, @@ -44,6 +44,7 @@ pub fn resize_right_with_pane_to_the_left() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -87,6 +88,7 @@ pub fn resize_right_with_pane_to_the_right() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -132,6 +134,7 @@ pub fn resize_right_with_panes_to_the_left_and_right() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -180,6 +183,7 @@ pub fn resize_right_with_multiple_panes_to_the_left() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -230,6 +234,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -277,6 +282,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -326,6 +332,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_bottom_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -374,6 +381,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_bottom_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -427,6 +435,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_current_p CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -482,6 +491,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_current_ CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -554,6 +564,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abo CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -628,6 +639,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -670,6 +682,7 @@ pub fn cannot_resize_right_when_pane_to_the_left_is_at_minimum_width() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output diff --git a/src/tests/integration/resize_up.rs b/src/tests/integration/resize_up.rs index 14819331..ba810a9f 100644 --- a/src/tests/integration/resize_up.rs +++ b/src/tests/integration/resize_up.rs @@ -5,7 +5,7 @@ use crate::tests::fakes::FakeInputOutput; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::{start, CliArgs}; -use crate::common::input::config::Config; +use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::{ MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE, RESIZE_UP_IN_RESIZE_MODE, SLEEP, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, @@ -46,6 +46,7 @@ pub fn resize_up_with_pane_above() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -91,6 +92,7 @@ pub fn resize_up_with_pane_below() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -141,6 +143,7 @@ pub fn resize_up_with_panes_above_and_below() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -190,6 +193,7 @@ pub fn resize_up_with_multiple_panes_above() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -240,6 +244,7 @@ pub fn resize_up_with_panes_above_aligned_left_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -291,6 +296,7 @@ pub fn resize_up_with_panes_below_aligned_left_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -340,6 +346,7 @@ pub fn resize_up_with_panes_above_aligned_right_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -390,6 +397,7 @@ pub fn resize_up_with_panes_below_aligned_right_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -443,6 +451,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -498,6 +507,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_current_pane() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -570,6 +580,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_ CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -644,6 +655,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_ri CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -686,6 +698,7 @@ pub fn cannot_resize_up_when_pane_above_is_at_minimum_height() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output diff --git a/src/tests/integration/tabs.rs b/src/tests/integration/tabs.rs index 0c751634..1d8a2c9d 100644 --- a/src/tests/integration/tabs.rs +++ b/src/tests/integration/tabs.rs @@ -5,7 +5,7 @@ use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots} use crate::{panes::PositionAndSize, tests::utils::commands::CLOSE_PANE_IN_PANE_MODE}; use crate::{start, CliArgs}; -use crate::common::input::config::Config; +use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::{ CLOSE_TAB_IN_TAB_MODE, NEW_TAB_IN_TAB_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE, SWITCH_NEXT_TAB_IN_TAB_MODE, SWITCH_PREV_TAB_IN_TAB_MODE, TAB_MODE, @@ -38,6 +38,7 @@ pub fn open_new_tab() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -74,6 +75,7 @@ pub fn switch_to_prev_tab() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -110,6 +112,7 @@ pub fn switch_to_next_tab() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -146,6 +149,7 @@ pub fn close_tab() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -183,6 +187,7 @@ pub fn close_last_pane_in_a_tab() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -222,6 +227,7 @@ pub fn close_the_middle_tab() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -266,6 +272,7 @@ pub fn close_the_tab_that_has_a_pane_in_fullscreen() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -302,6 +309,7 @@ pub fn closing_last_tab_exits_the_app() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output diff --git a/src/tests/integration/terminal_window_resize.rs b/src/tests/integration/terminal_window_resize.rs index 057c2523..7c06609a 100644 --- a/src/tests/integration/terminal_window_resize.rs +++ b/src/tests/integration/terminal_window_resize.rs @@ -1,7 +1,7 @@ use crate::panes::PositionAndSize; use ::insta::assert_snapshot; -use crate::common::input::config::Config; +use crate::common::input::{config::Config, options::Options}; use crate::tests::fakes::FakeInputOutput; use crate::tests::utils::commands::QUIT; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; @@ -35,6 +35,7 @@ pub fn window_width_decrease_with_one_pane() { opts, Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -71,6 +72,7 @@ pub fn window_width_increase_with_one_pane() { opts, Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -107,6 +109,7 @@ pub fn window_height_increase_with_one_pane() { opts, Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer @@ -143,6 +146,7 @@ pub fn window_width_and_height_decrease_with_one_pane() { opts, Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output .stdout_writer diff --git a/src/tests/integration/toggle_fullscreen.rs b/src/tests/integration/toggle_fullscreen.rs index 0e967bd2..8a63bbea 100644 --- a/src/tests/integration/toggle_fullscreen.rs +++ b/src/tests/integration/toggle_fullscreen.rs @@ -5,7 +5,7 @@ use crate::tests::fakes::FakeInputOutput; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::{start, CliArgs}; -use crate::common::input::config::Config; +use crate::common::input::{config::Config, options::Options}; use crate::tests::utils::commands::{ MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE, @@ -37,6 +37,7 @@ pub fn adding_new_terminal_in_fullscreen() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output @@ -72,6 +73,7 @@ pub fn move_focus_is_disabled_in_fullscreen() { CliArgs::default(), Box::new(fake_input_output.clone()), Config::default(), + Options::default(), ); let output_frames = fake_input_output diff --git a/zellij-tile/src/data.rs b/zellij-tile/src/data.rs index 4ff44eac..c1b9c30e 100644 --- a/zellij-tile/src/data.rs +++ b/zellij-tile/src/data.rs @@ -143,7 +143,13 @@ pub struct PluginIds { pub zellij_pid: u32, } -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] pub struct PluginCapabilities { pub arrow_fonts: bool, } + +impl Default for PluginCapabilities { + fn default() -> PluginCapabilities { + PluginCapabilities { arrow_fonts: true } + } +} From ad2f932b1a28701ecd49d0d99fd5107cf69cfbc8 Mon Sep 17 00:00:00 2001 From: a-kenji Date: Wed, 12 May 2021 16:01:52 +0200 Subject: [PATCH 44/44] Fix Default Keybind for Tab -> Resize Mode --- CHANGELOG.md | 1 + assets/config/default.yaml | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1adbec30..8c477958 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Terminal compatibility: preserve current style when clearing viewport (https://github.com/zellij-org/zellij/pull/493) * Fix propagation of plugin ui request (https://github.com/zellij-org/zellij/pull/495) * Handle pasted text properly (https://github.com/zellij-org/zellij/pull/494) +* Fix default keybinds for tab -> resize mode (https://github.com/zellij-org/zellij/pull/497) ## [0.9.0] - 2021-05-11 * Add more functionality to unbinding the default keybindings (https://github.com/zellij-org/zellij/pull/468) diff --git a/assets/config/default.yaml b/assets/config/default.yaml index cb6aa248..4582e79f 100644 --- a/assets/config/default.yaml +++ b/assets/config/default.yaml @@ -108,8 +108,10 @@ keybinds: key: [Ctrl: 'g'] - action: [SwitchToMode: Pane,] key: [Ctrl: 'p',] + - action: [SwitchToMode: Resize,] + key: [Ctrl: 'r',] - action: [SwitchToMode: Normal,] - key: [Ctrl: 'r', Ctrl: 't', Char: "\n", Char: ' ',] + key: [Ctrl: 't', Char: "\n", Char: ' ',] - action: [SwitchToMode: Scroll,] key: [Ctrl: 's'] - action: [SwitchToMode: RenameTab, TabNameInput: [0],]