diff --git a/default-tiles/status-bar/src/main.rs b/default-tiles/status-bar/src/main.rs index 8daa158ed..ee4d17635 100644 --- a/default-tiles/status-bar/src/main.rs +++ b/default-tiles/status-bar/src/main.rs @@ -63,8 +63,8 @@ impl ZellijTile for State { let second_line = keybinds(&self.mode_info, cols); // [48;5;238m is gray background, [0K is so that it fills the rest of the line - // [48;5;16m is black background, [0K is so that it fills the rest of the line + // [m is background reset, [0K is so that it clears the rest of the line println!("{}\u{1b}[48;5;238m\u{1b}[0K", first_line); - println!("{}\u{1b}[48;5;16m\u{1b}[0K", second_line); + println!("\u{1b}[m{}\u{1b}[0K", second_line); } } diff --git a/default-tiles/status-bar/src/second_line.rs b/default-tiles/status-bar/src/second_line.rs index ebcb93dd4..48a82c6c3 100644 --- a/default-tiles/status-bar/src/second_line.rs +++ b/default-tiles/status-bar/src/second_line.rs @@ -59,6 +59,122 @@ fn first_word_shortcut(is_first_shortcut: bool, letter: &str, description: &str) len, } } +fn quicknav_full() -> LinePart { + let text_first_part = " Tip: "; + let alt = "Alt"; + let text_second_part = " + "; + let new_pane_shortcut = "n"; + let text_third_part = " => open new pane. "; + let second_alt = "Alt"; + let text_fourth_part = " + "; + let brackets_navigation = "[]"; + let text_fifth_part = " or "; + let hjkl_navigation = "hjkl"; + let text_sixths_part = " => navigate between panes."; + let len = text_first_part.chars().count() + + alt.chars().count() + + text_second_part.chars().count() + + new_pane_shortcut.chars().count() + + text_third_part.chars().count() + + second_alt.chars().count() + + text_fourth_part.chars().count() + + brackets_navigation.chars().count() + + text_fifth_part.chars().count() + + hjkl_navigation.chars().count() + + text_sixths_part.chars().count(); + LinePart { + part: format!( + "{}{}{}{}{}{}{}{}{}{}{}", + text_first_part, + Style::new().fg(ORANGE).bold().paint(alt), + text_second_part, + Style::new().fg(GREEN).bold().paint(new_pane_shortcut), + text_third_part, + Style::new().fg(ORANGE).bold().paint(second_alt), + text_fourth_part, + Style::new().fg(GREEN).bold().paint(brackets_navigation), + text_fifth_part, + Style::new().fg(GREEN).bold().paint(hjkl_navigation), + text_sixths_part, + ), + len, + } +} + +fn quicknav_medium() -> LinePart { + let text_first_part = " Tip: "; + let alt = "Alt"; + let text_second_part = " + "; + let new_pane_shortcut = "n"; + let text_third_part = " => new pane. "; + let second_alt = "Alt"; + let text_fourth_part = " + "; + let brackets_navigation = "[]"; + let text_fifth_part = " or "; + let hjkl_navigation = "hjkl"; + let text_sixths_part = " => navigate."; + let len = text_first_part.chars().count() + + alt.chars().count() + + text_second_part.chars().count() + + new_pane_shortcut.chars().count() + + text_third_part.chars().count() + + second_alt.chars().count() + + text_fourth_part.chars().count() + + brackets_navigation.chars().count() + + text_fifth_part.chars().count() + + hjkl_navigation.chars().count() + + text_sixths_part.chars().count(); + LinePart { + part: format!( + "{}{}{}{}{}{}{}{}{}{}{}", + text_first_part, + Style::new().fg(ORANGE).bold().paint(alt), + text_second_part, + Style::new().fg(GREEN).bold().paint(new_pane_shortcut), + text_third_part, + Style::new().fg(ORANGE).bold().paint(second_alt), + text_fourth_part, + Style::new().fg(GREEN).bold().paint(brackets_navigation), + text_fifth_part, + Style::new().fg(GREEN).bold().paint(hjkl_navigation), + text_sixths_part, + ), + len, + } +} + +fn quicknav_short() -> LinePart { + let text_first_part = " QuickNav: "; + let alt = "Alt"; + let text_second_part = " + "; + let new_pane_shortcut = "n"; + let text_third_part = "/"; + let brackets_navigation = "[]"; + let text_fifth_part = "/"; + let hjkl_navigation = "hjkl"; + let len = text_first_part.chars().count() + + alt.chars().count() + + text_second_part.chars().count() + + new_pane_shortcut.chars().count() + + text_third_part.chars().count() + + brackets_navigation.chars().count() + + text_fifth_part.chars().count() + + hjkl_navigation.chars().count(); + LinePart { + part: format!( + "{}{}{}{}{}{}{}{}", + text_first_part, + Style::new().fg(ORANGE).bold().paint(alt), + text_second_part, + Style::new().fg(GREEN).bold().paint(new_pane_shortcut), + text_third_part, + Style::new().fg(GREEN).bold().paint(brackets_navigation), + text_fifth_part, + Style::new().fg(GREEN).bold().paint(hjkl_navigation), + ), + len, + } +} fn locked_interface_indication() -> LinePart { let locked_text = " -- INTERFACE LOCKED -- "; @@ -99,7 +215,7 @@ fn select_pane_shortcut(is_first_shortcut: bool) -> LinePart { fn full_shortcut_list(help: &ModeInfo) -> LinePart { match help.mode { - InputMode::Normal => LinePart::default(), + InputMode::Normal => quicknav_full(), InputMode::Locked => locked_interface_indication(), _ => { let mut line_part = LinePart::default(); @@ -118,7 +234,7 @@ fn full_shortcut_list(help: &ModeInfo) -> LinePart { fn shortened_shortcut_list(help: &ModeInfo) -> LinePart { match help.mode { - InputMode::Normal => LinePart::default(), + InputMode::Normal => quicknav_medium(), InputMode::Locked => locked_interface_indication(), _ => { let mut line_part = LinePart::default(); @@ -137,7 +253,14 @@ fn shortened_shortcut_list(help: &ModeInfo) -> LinePart { fn best_effort_shortcut_list(help: &ModeInfo, max_len: usize) -> LinePart { match help.mode { - InputMode::Normal => LinePart::default(), + InputMode::Normal => { + let line_part = quicknav_short(); + if line_part.len <= max_len { + line_part + } else { + LinePart::default() + } + } InputMode::Locked => { let line_part = locked_interface_indication(); if line_part.len <= max_len { @@ -157,7 +280,7 @@ fn best_effort_shortcut_list(help: &ModeInfo, max_len: usize) -> LinePart { break; } line_part.len += shortcut.len; - line_part.part = format!("{}{}", line_part.part, shortcut,); + line_part.part = format!("{}{}", line_part.part, shortcut); } let select_pane_shortcut = select_pane_shortcut(help.keybinds.is_empty()); if line_part.len + select_pane_shortcut.len <= max_len { diff --git a/src/client/boundaries.rs b/src/client/boundaries.rs index e498da894..28e01a117 100644 --- a/src/client/boundaries.rs +++ b/src/client/boundaries.rs @@ -21,9 +21,9 @@ pub mod boundary_type { pub mod colors { use ansi_term::Colour::{self, Fixed}; - pub const WHITE: Colour = Fixed(255); pub const GREEN: Colour = Fixed(154); pub const GRAY: Colour = Fixed(238); + pub const ORANGE: Colour = Fixed(166); } pub type BoundaryType = &'static str; // easy way to refer to boundary_type above @@ -768,7 +768,7 @@ impl Boundaries { let color = match color.is_some() { true => match input_mode { InputMode::Normal | InputMode::Locked => Some(colors::GREEN), - _ => Some(colors::WHITE), + _ => Some(colors::ORANGE), }, false => None, }; diff --git a/src/client/tab.rs b/src/client/tab.rs index 82c2639f8..3a12656d5 100644 --- a/src/client/tab.rs +++ b/src/client/tab.rs @@ -1809,6 +1809,62 @@ impl Tab { } self.render(); } + pub fn focus_next_pane(&mut self) { + if !self.has_selectable_panes() { + return; + } + if self.fullscreen_is_active { + return; + } + let active_pane_id = self.get_active_pane_id().unwrap(); + let mut panes: Vec<(&PaneId, &Box)> = self.get_selectable_panes().collect(); + panes.sort_by(|(_a_id, a_pane), (_b_id, b_pane)| { + if a_pane.y() == b_pane.y() { + a_pane.x().cmp(&b_pane.x()) + } else { + a_pane.y().cmp(&b_pane.y()) + } + }); + let first_pane = panes.get(0).unwrap(); + let active_pane_position = panes + .iter() + .position(|(id, _)| *id == &active_pane_id) // TODO: better + .unwrap(); + if let Some(next_pane) = panes.get(active_pane_position + 1) { + self.active_terminal = Some(*next_pane.0); + } else { + self.active_terminal = Some(*first_pane.0); + } + self.render(); + } + pub fn focus_previous_pane(&mut self) { + if !self.has_selectable_panes() { + return; + } + if self.fullscreen_is_active { + return; + } + let active_pane_id = self.get_active_pane_id().unwrap(); + let mut panes: Vec<(&PaneId, &Box)> = self.get_selectable_panes().collect(); + panes.sort_by(|(_a_id, a_pane), (_b_id, b_pane)| { + if a_pane.y() == b_pane.y() { + a_pane.x().cmp(&b_pane.x()) + } else { + a_pane.y().cmp(&b_pane.y()) + } + }); + let last_pane = panes.last().unwrap(); + let active_pane_position = panes + .iter() + .position(|(id, _)| *id == &active_pane_id) // TODO: better + .unwrap(); + if active_pane_position == 0 { + self.active_terminal = Some(*last_pane.0); + } else { + self.active_terminal = Some(*panes.get(active_pane_position - 1).unwrap().0); + } + self.render(); + } pub fn move_focus_left(&mut self) { if !self.has_selectable_panes() { return; diff --git a/src/common/errors.rs b/src/common/errors.rs index 00284cd49..1548aec00 100644 --- a/src/common/errors.rs +++ b/src/common/errors.rs @@ -178,7 +178,9 @@ pub enum ScreenContext { ResizeRight, ResizeDown, ResizeUp, - MoveFocus, + SwitchFocus, + FocusNextPane, + FocusPreviousPane, MoveFocusLeft, MoveFocusDown, MoveFocusUp, @@ -218,7 +220,9 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::ResizeRight => ScreenContext::ResizeRight, ScreenInstruction::ResizeDown => ScreenContext::ResizeDown, ScreenInstruction::ResizeUp => ScreenContext::ResizeUp, - ScreenInstruction::MoveFocus => ScreenContext::MoveFocus, + ScreenInstruction::SwitchFocus => ScreenContext::SwitchFocus, + ScreenInstruction::FocusNextPane => ScreenContext::FocusNextPane, + ScreenInstruction::FocusPreviousPane => ScreenContext::FocusPreviousPane, ScreenInstruction::MoveFocusLeft => ScreenContext::MoveFocusLeft, ScreenInstruction::MoveFocusDown => ScreenContext::MoveFocusDown, ScreenInstruction::MoveFocusUp => ScreenContext::MoveFocusUp, diff --git a/src/common/input/actions.rs b/src/common/input/actions.rs index ac923f7dd..47a5f763a 100644 --- a/src/common/input/actions.rs +++ b/src/common/input/actions.rs @@ -24,8 +24,10 @@ pub enum Action { /// Resize focus pane in specified direction. Resize(Direction), /// Switch focus to next pane in specified direction. - SwitchFocus(Direction), + FocusNextPane, + FocusPreviousPane, /// Move the focus pane in specified direction. + SwitchFocus, MoveFocus(Direction), /// Scroll up in focus pane. ScrollUp, diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs index 2020fc209..44065c679 100644 --- a/src/common/input/handler.rs +++ b/src/common/input/handler.rs @@ -26,6 +26,7 @@ struct InputHandler { send_pty_instructions: SenderWithContext, send_plugin_instructions: SenderWithContext, send_app_instructions: SenderWithContext, + should_exit: bool, } impl InputHandler { @@ -48,6 +49,7 @@ impl InputHandler { send_pty_instructions, send_plugin_instructions, send_app_instructions, + should_exit: false, } } @@ -60,30 +62,29 @@ impl InputHandler { self.send_app_instructions.update(err_ctx); self.send_screen_instructions.update(err_ctx); let keybinds = self.config.keybinds.clone(); - 'input_loop: loop { - //@@@ I think this should actually just iterate over stdin directly + let alt_left_bracket = vec![27, 91]; + loop { + if self.should_exit { + break; + } let stdin_buffer = self.os_input.read_from_stdin(); for key_result in stdin_buffer.events_and_raw() { match key_result { Ok((event, raw_bytes)) => match event { termion::event::Event::Key(key) => { let key = cast_termion_key(key); - // FIXME this explicit break is needed because the current test - // framework relies on it to not create dead threads that loop - // and eat up CPUs. Do not remove until the test framework has - // been revised. Sorry about this (@categorille) - let mut should_break = false; - for action in - Keybinds::key_to_actions(&key, raw_bytes, &self.mode, &keybinds) - { - should_break |= self.dispatch_action(action); - } - if should_break { - break 'input_loop; + self.handle_key(&key, raw_bytes, &keybinds); + } + termion::event::Event::Unsupported(unsupported_key) => { + // we have to do this because of a bug in termion + // this should be a key event and not an unsupported event + if unsupported_key == alt_left_bracket { + let key = Key::Alt('['); + self.handle_key(&key, raw_bytes, &keybinds); } } - termion::event::Event::Mouse(_) | termion::event::Event::Unsupported(_) => { - // Mouse and unsupported events aren't implemented yet, + termion::event::Event::Mouse(_) => { + // Mouse events aren't implemented yet, // use a NoOp untill then. } }, @@ -92,6 +93,14 @@ impl InputHandler { } } } + fn handle_key(&mut self, key: &Key, raw_bytes: Vec, keybinds: &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; + } + } + } /// Dispatches an [`Action`]. /// @@ -144,9 +153,19 @@ impl InputHandler { }; self.send_screen_instructions.send(screen_instr).unwrap(); } - Action::SwitchFocus(_) => { + Action::SwitchFocus => { self.send_screen_instructions - .send(ScreenInstruction::MoveFocus) + .send(ScreenInstruction::SwitchFocus) + .unwrap(); + } + Action::FocusNextPane => { + self.send_screen_instructions + .send(ScreenInstruction::FocusNextPane) + .unwrap(); + } + Action::FocusPreviousPane => { + self.send_screen_instructions + .send(ScreenInstruction::FocusPreviousPane) .unwrap(); } Action::MoveFocus(direction) => { diff --git a/src/common/input/keybinds.rs b/src/common/input/keybinds.rs index 6a14a1823..8a0649702 100644 --- a/src/common/input/keybinds.rs +++ b/src/common/input/keybinds.rs @@ -91,6 +91,14 @@ impl Keybinds { vec![Action::SwitchToMode(InputMode::Scroll)], ); defaults.insert(Key::Ctrl('q'), vec![Action::Quit]); + + defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]); + defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]); + defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]); + defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]); + defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]); + defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]); + defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]); } InputMode::Locked => { defaults.insert( @@ -133,6 +141,14 @@ impl Keybinds { defaults.insert(Key::Down, vec![Action::Resize(Direction::Down)]); defaults.insert(Key::Up, vec![Action::Resize(Direction::Up)]); defaults.insert(Key::Right, vec![Action::Resize(Direction::Right)]); + + defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]); + defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]); + defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]); + defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]); + defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]); + defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]); + defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]); } InputMode::Pane => { defaults.insert( @@ -173,7 +189,7 @@ impl Keybinds { defaults.insert(Key::Up, vec![Action::MoveFocus(Direction::Up)]); defaults.insert(Key::Right, vec![Action::MoveFocus(Direction::Right)]); - defaults.insert(Key::Char('p'), vec![Action::SwitchFocus(Direction::Right)]); + defaults.insert(Key::Char('p'), vec![Action::SwitchFocus]); defaults.insert(Key::Char('n'), vec![Action::NewPane(None)]); defaults.insert(Key::Char('d'), vec![Action::NewPane(Some(Direction::Down))]); defaults.insert( @@ -182,6 +198,14 @@ impl Keybinds { ); defaults.insert(Key::Char('x'), vec![Action::CloseFocus]); defaults.insert(Key::Char('f'), vec![Action::ToggleFocusFullscreen]); + + defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]); + defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]); + defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]); + defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]); + defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]); + defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]); + defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]); } InputMode::Tab => { defaults.insert( @@ -240,6 +264,13 @@ impl Keybinds { for i in '1'..='9' { defaults.insert(Key::Char(i), vec![Action::GoToTab(i.to_digit(10).unwrap())]); } + defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]); + defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]); + defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]); + defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]); + defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]); + defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]); + defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]); } InputMode::Scroll => { defaults.insert( @@ -272,6 +303,14 @@ impl Keybinds { defaults.insert(Key::Down, vec![Action::ScrollDown]); defaults.insert(Key::Up, vec![Action::ScrollUp]); + + defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]); + defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]); + defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]); + defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]); + defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]); + defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]); + defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]); } InputMode::RenameTab => { defaults.insert(Key::Char('\n'), vec![Action::SwitchToMode(InputMode::Tab)]); @@ -286,6 +325,14 @@ impl Keybinds { Action::SwitchToMode(InputMode::Tab), ], ); + + defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]); + defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]); + defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]); + defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]); + defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]); + defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]); + defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]); } } ModeKeybinds(defaults) diff --git a/src/common/mod.rs b/src/common/mod.rs index 8180a724d..7651905c1 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -326,9 +326,15 @@ pub fn start(mut os_input: Box, opts: CliArgs) { ScreenInstruction::ResizeUp => { screen.get_active_tab_mut().unwrap().resize_up(); } - ScreenInstruction::MoveFocus => { + 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(); } @@ -589,7 +595,7 @@ pub fn start(mut os_input: Box, opts: CliArgs) { } ApiCommand::MoveFocus => { send_screen_instructions - .send(ScreenInstruction::MoveFocus) + .send(ScreenInstruction::FocusNextPane) .unwrap(); } } diff --git a/src/common/screen.rs b/src/common/screen.rs index 121f21bdb..41e934cb6 100644 --- a/src/common/screen.rs +++ b/src/common/screen.rs @@ -28,7 +28,9 @@ pub enum ScreenInstruction { ResizeRight, ResizeDown, ResizeUp, - MoveFocus, + SwitchFocus, + FocusNextPane, + FocusPreviousPane, MoveFocusLeft, MoveFocusDown, MoveFocusUp,