diff options
27 files changed, 1214 insertions, 947 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d3b681273..fd34b4f95 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/` +**Basic information** -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. +`zellij --version`: +`tput lines`: +`tput cols`: +`uname -av` or `ver`(Windows): -## Information +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) -`zellij --version`: +**Further information** +Reproduction steps, noticable behavior, related issues etc diff --git a/CHANGELOG.md b/CHANGELOG.md index fb319f113..3ca3c48d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +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) * 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) ## [0.8.0] - 2021-05-07 * Terminal compatibility: pass vttest 8 (https://github.com/zellij-org/zellij/pull/461) diff --git a/assets/config/default.yaml b/assets/config/default.yaml index d56004ef1..cb6aa2485 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/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index a54585ae4..9b2ee7565 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/example/default.yaml b/example/default.yaml index a113c8eed..9b6c89045 100644 --- a/example/default.yaml +++ b/example/default.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/example/tmux-overview.yaml b/example/tmux-overview.yaml index 887a84ad2..c40bdb771 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/mod.rs b/src/client/mod.rs index 5ebf8cec0..8fcbc0705 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 c3b633ecc..dde3cd9b2 100644 --- a/src/client/panes/plugin_pane.rs +++ b/src/client/panes/plugin_pane.rs @@ -1,9 +1,12 @@ -use crate::{common::SenderWithContext, pty_bus::VteBytes, tab::Pane, wasm_vm::PluginInstruction}; +use std::sync::mpsc::channel; +use std::time::Instant; +use std::unimplemented; +use crate::common::thread_bus::SenderWithContext; use crate::panes::{PaneId, PositionAndSize}; - -use std::time::Instant; -use std::{sync::mpsc::channel, unimplemented}; +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 f58c3a273..5792bba93 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_bus::VteBytes; +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/client/tab.rs b/src/client/tab.rs index c4dc34743..f0ec08690 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<dyn ServerOsApi>, - send_plugin_instructions: SenderWithContext<PluginInstruction>, - send_pty_instructions: SenderWithContext<PtyInstruction>, - send_server_instructions: SenderWithContext<ServerInstruction>, + 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<dyn ServerOsApi>, - send_plugin_instructions: SenderWithContext<PluginInstruction>, - send_pty_instructions: SenderWithContext<PtyInstruction>, - send_server_instructions: SenderWithContext<ServerInstruction>, + senders: ThreadSenders, max_panes: Option<usize>, pane_id: Option<PaneId>, 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<Item = (&PaneId, &Box<dyn Pane>)> { @@ -1853,12 +1848,13 @@ impl Tab { } self.render(); } - pub fn move_focus_left(&mut self) { + // returns a boolean that indicates whether the focus moved + 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 { @@ -1873,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()); @@ -1881,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() { @@ -1943,12 +1941,13 @@ impl Tab { } self.render(); } - pub fn move_focus_right(&mut self) { + // returns a boolean that indicates whether the focus moved + 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 { @@ -1963,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()); @@ -1971,7 +1972,7 @@ impl Tab { } else { self.active_terminal = Some(active_terminal.unwrap().pid()); } - self.render(); + false } fn horizontal_borders(&self, terminals: &[PaneId]) -> HashSet<usize> { terminals.iter().fold(HashSet::new(), |mut borders, t| { @@ -2086,8 +2087,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 +2199,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 d8327462f..c2caf19d6 100644 --- a/src/common/errors.rs +++ b/src/common/errors.rs @@ -1,20 +1,22 @@ //! Error context system based on a thread-local representation of the call stack, itself based on //! the instructions that are sent between threads. -use super::{ServerInstruction, ASYNCOPENCALLS, OPENCALLS}; -use crate::client::ClientInstruction; -use crate::pty_bus::PtyInstruction; -use crate::screen::ScreenInstruction; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Error, Formatter}; +use crate::client::ClientInstruction; +use crate::common::thread_bus::{ASYNCOPENCALLS, OPENCALLS}; +use crate::pty::PtyInstruction; +use crate::screen::ScreenInstruction; +use crate::server::ServerInstruction; + /// The maximum amount of calls an [`ErrorContext`] will keep track /// of in its stack representation. This is a per-thread maximum. const MAX_THREAD_CALL_STACK: usize = 6; #[cfg(not(test))] -use super::SenderWithContext; +use super::thread_bus::SenderWithContext; #[cfg(not(test))] use std::panic::PanicInfo; /// Custom panic handler/hook. Prints the [`ErrorContext`]. @@ -190,9 +192,11 @@ pub enum ScreenContext { FocusNextPane, FocusPreviousPane, MoveFocusLeft, + MoveFocusLeftOrPreviousTab, MoveFocusDown, MoveFocusUp, MoveFocusRight, + MoveFocusRightOrNextTab, Exit, ScrollUp, ScrollDown, @@ -235,9 +239,13 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::FocusNextPane => ScreenContext::FocusNextPane, ScreenInstruction::FocusPreviousPane => ScreenContext::FocusPreviousPane, ScreenInstruction::MoveFocusLeft => ScreenContext::MoveFocusLeft, + ScreenInstruction::MoveFocusLeftOrPreviousTab => { + ScreenContext::MoveFocusLeftOrPreviousTab + } ScreenInstruction::MoveFocusDown => ScreenContext::MoveFocusDown, |