From 6a228b2a9a6ae5fc7f0dc5bfca1dd27eab1bc61b Mon Sep 17 00:00:00 2001 From: Vladimir Stoilov Date: Fri, 28 Feb 2025 23:58:24 +0200 Subject: [PATCH 1/4] Add Switch to Next/Prev Split or Tab command --- frontends/rioterm/src/bindings/mod.rs | 11 ++++++++++ frontends/rioterm/src/context/grid.rs | 29 +++++++++++++++++++++++++++ frontends/rioterm/src/context/mod.rs | 26 ++++++++++++++++++++++++ frontends/rioterm/src/screen/mod.rs | 10 +++++++++ 4 files changed, 76 insertions(+) diff --git a/frontends/rioterm/src/bindings/mod.rs b/frontends/rioterm/src/bindings/mod.rs index 854c35943f..81862318ee 100644 --- a/frontends/rioterm/src/bindings/mod.rs +++ b/frontends/rioterm/src/bindings/mod.rs @@ -254,6 +254,8 @@ impl From for Action { "splitdown" => Some(Action::SplitDown), "selectnextsplit" => Some(Action::SelectNextSplit), "selectprevsplit" => Some(Action::SelectPrevSplit), + "selectnextsplitortab" => Some(Action::SelectNextSplitOrTab), + "selectprevsplitortab" => Some(Action::SelectPrevSplitOrTab), "togglevimode" => Some(Action::ToggleViMode), "togglefullscreen" => Some(Action::ToggleFullscreen), "none" => Some(Action::None), @@ -465,9 +467,18 @@ pub enum Action { /// Split vertically SplitDown, + /// Select next split SelectNextSplit, + + /// Select previous split SelectPrevSplit, + /// Select next split if available if not next tab + SelectNextSplitOrTab, + + /// Select previous split if available if not previous tab + SelectPrevSplitOrTab, + /// Allow receiving char input. ReceiveChar, diff --git a/frontends/rioterm/src/context/grid.rs b/frontends/rioterm/src/context/grid.rs index c17127104d..ebfa3f5a59 100644 --- a/frontends/rioterm/src/context/grid.rs +++ b/frontends/rioterm/src/context/grid.rs @@ -140,6 +140,21 @@ impl ContextGrid { } } + #[inline] + pub fn select_next_split_no_loop(&mut self) -> bool { + if self.inner.len() == 1 { + return false; + } + + if self.current >= self.inner.len() - 1 { + return false; + } else { + self.current += 1; + } + + return true; + } + #[inline] pub fn select_prev_split(&mut self) { if self.inner.len() == 1 { @@ -153,6 +168,20 @@ impl ContextGrid { } } + #[inline] + pub fn select_prev_split_no_loop(&mut self) -> bool { + if self.inner.len() == 1 { + return false; + } + + if self.current == 0 { + return false; + } else { + self.current -= 1; + } + return true; + } + #[inline] #[allow(unused)] pub fn current_index(&self) -> usize { diff --git a/frontends/rioterm/src/context/mod.rs b/frontends/rioterm/src/context/mod.rs index 66cf4b39f4..74639624fb 100644 --- a/frontends/rioterm/src/context/mod.rs +++ b/frontends/rioterm/src/context/mod.rs @@ -570,6 +570,32 @@ impl ContextManager { self.current_route = self.current().route_id; } + #[inline] + pub fn switch_to_next_split_or_tab(&mut self) { + if self.contexts[self.current_index].select_next_split_no_loop() { + self.current_route = self.current().route_id; + return; + } + self.switch_to_next(); + // Make sure first split is selected + let current_tab = &mut self.contexts[self.current_index]; + current_tab.current = 0; + self.current_route = self.current().route_id; + } + + #[inline] + pub fn switch_to_prev_split_or_tab(&mut self) { + if self.contexts[self.current_index].select_prev_split_no_loop() { + self.current_route = self.current().route_id; + return; + } + self.switch_to_prev(); + // Make sure last split is selected + let current_tab = &mut self.contexts[self.current_index]; + current_tab.current = current_tab.len() - 1; + self.current_route = self.current().route_id; + } + #[inline] pub fn select_tab(&mut self, tab_index: usize) { if self.config.is_native { diff --git a/frontends/rioterm/src/screen/mod.rs b/frontends/rioterm/src/screen/mod.rs index f9cd0e0b0f..77b6cee7da 100644 --- a/frontends/rioterm/src/screen/mod.rs +++ b/frontends/rioterm/src/screen/mod.rs @@ -997,6 +997,16 @@ impl Screen<'_> { self.context_manager.select_prev_split(); self.render(); } + Act::SelectNextSplitOrTab => { + self.cancel_search(); + self.context_manager.switch_to_next_split_or_tab(); + self.render(); + } + Act::SelectPrevSplitOrTab => { + self.cancel_search(); + self.context_manager.switch_to_prev_split_or_tab(); + self.render(); + } Act::SelectTab(tab_index) => { self.context_manager.select_tab(*tab_index); self.cancel_search(); From 2aa471c4ca53c4fefbb4d3d237e9ad9f41750b2b Mon Sep 17 00:00:00 2001 From: Vladimir Stoilov Date: Sat, 1 Mar 2025 09:42:16 +0200 Subject: [PATCH 2/4] Update docs for Select Next/Prev SplitOrTab commands --- docs/docs/key-bindings.md | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/docs/docs/key-bindings.md b/docs/docs/key-bindings.md index 5af8f0612c..fa79742c17 100644 --- a/docs/docs/key-bindings.md +++ b/docs/docs/key-bindings.md @@ -74,24 +74,26 @@ Execute a predefined action in Rio terminal. ### [Split Actions](#split-actions) -| Action | Description | -| :-------------- | :------------------------------------------------------------------------- | -| SplitRight | Create a split by right side | -| SplitDown | Create a split by under current pane | -| SelectNextSplit | Select next split | -| SelectPrevSplit | Select previous split | -| CloseSplitOrTab | Close split, if split is the last then will close the tab | +| Action | Description | +| :------------------- | :------------------------------------------------------------------------- | +| SplitRight | Create a split by right side | +| SplitDown | Create a split by under current pane | +| SelectNextSplit | Select next split | +| SelectPrevSplit | Select previous split | +| CloseSplitOrTab | Close split, if split is the last then will close the tab | +| SelectNextSplitOrTab | Select next split if available if not next tab | +| SelectPrevSplitOrTab | Select previous split if available if not previous tab | ### [Tab Actions](#tab-actions) | Action | Description | | :------------------- | :---------------------------------------------------------------------- | -| CreateTab | | -| CloseTab | | -| CloseUnfocusedTabs | | -| SelectPrevTab | | -| SelectNextTab | | -| SelectLastTab | | +| CreateTab | Create new tab | +| CloseTab | Close current tab | +| CloseUnfocusedTabs | Close all tabs that are not currently focused | +| SelectNextTab | Select next tab | +| SelectPrevTab | Select pervious tab | +| SelectLastTab | Select last tab | | MoveCurrentTabToPrev | Move the current focused tab to the previous slot if any is available | | SelectTab(tab_index) | Example: Select first tab `SelectTab(0)`, second tab `SelectTab(1)` | From 32f2549a1ac252f8872bc543974e08b08194d5ed Mon Sep 17 00:00:00 2001 From: Vladimir Stoilov Date: Sat, 1 Mar 2025 12:02:12 +0200 Subject: [PATCH 3/4] Add docs for MoveCurrentTabToNext --- docs/docs/key-bindings.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/docs/key-bindings.md b/docs/docs/key-bindings.md index fa79742c17..5f910ced40 100644 --- a/docs/docs/key-bindings.md +++ b/docs/docs/key-bindings.md @@ -94,7 +94,8 @@ Execute a predefined action in Rio terminal. | SelectNextTab | Select next tab | | SelectPrevTab | Select pervious tab | | SelectLastTab | Select last tab | -| MoveCurrentTabToPrev | Move the current focused tab to the previous slot if any is available | +| MoveCurrentTabToNext | Move the current focused tab to the next slot, or first when last | +| MoveCurrentTabToPrev | Move the current focused tab to the previous slot, or last when first | | SelectTab(tab_index) | Example: Select first tab `SelectTab(0)`, second tab `SelectTab(1)` | ### [Scroll Actions](#scroll-actions) From 6cc0c3088bcec5598ceeceaf760f2e2d393f1912 Mon Sep 17 00:00:00 2001 From: Vladimir Stoilov Date: Sun, 2 Mar 2025 14:44:52 +0200 Subject: [PATCH 4/4] Add switch tab/split unit tests --- frontends/rioterm/src/context/mod.rs | 188 +++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) diff --git a/frontends/rioterm/src/context/mod.rs b/frontends/rioterm/src/context/mod.rs index 74639624fb..1487765426 100644 --- a/frontends/rioterm/src/context/mod.rs +++ b/frontends/rioterm/src/context/mod.rs @@ -1255,6 +1255,194 @@ pub mod test { assert_eq!(context_manager.current_index, 1); } + #[test] + fn test_switch_to_next_split_or_tab() { + let window_id: WindowId = WindowId::from(0); + + let mut context_manager = + ContextManager::start_with_capacity(5, VoidListener {}, window_id).unwrap(); + let should_redirect = true; + let split_down = false; + + context_manager.add_context(should_redirect, 0); + context_manager.split(0, split_down); + context_manager.split(0, split_down); + context_manager.add_context(should_redirect, 0); + context_manager.add_context(should_redirect, 0); + context_manager.split(0, split_down); + context_manager.add_context(should_redirect, 0); + context_manager.set_current(0); + assert_eq!(context_manager.len(), 5); + assert_eq!(context_manager.current_index, 0); + + let mut current_index; + + context_manager.switch_to_next_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 1); + assert_eq!(context_manager.contexts[current_index].current, 0); + + context_manager.switch_to_next_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 1); + assert_eq!(context_manager.contexts[current_index].current, 1); + + context_manager.switch_to_next_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 1); + assert_eq!(context_manager.contexts[current_index].current, 2); + + context_manager.switch_to_next_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 2); + assert_eq!(context_manager.contexts[current_index].current, 0); + + context_manager.switch_to_next_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 3); + assert_eq!(context_manager.contexts[current_index].current, 0); + + context_manager.switch_to_next_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 3); + assert_eq!(context_manager.contexts[current_index].current, 1); + + context_manager.switch_to_next_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 4); + assert_eq!(context_manager.contexts[current_index].current, 0); + + context_manager.switch_to_next_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 0); + assert_eq!(context_manager.contexts[current_index].current, 0); + } + + #[test] + fn test_switch_to_prev_split_or_tab() { + let window_id: WindowId = WindowId::from(0); + + let mut context_manager = + ContextManager::start_with_capacity(5, VoidListener {}, window_id).unwrap(); + let should_redirect = true; + let split_down = false; + + context_manager.add_context(should_redirect, 0); + context_manager.split(0, split_down); + context_manager.split(0, split_down); + context_manager.add_context(should_redirect, 0); + context_manager.add_context(should_redirect, 0); + context_manager.split(0, split_down); + context_manager.add_context(should_redirect, 0); + context_manager.set_current(0); + assert_eq!(context_manager.len(), 5); + assert_eq!(context_manager.current_index, 0); + + let mut current_index; + + context_manager.switch_to_prev_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 4); + assert_eq!(context_manager.contexts[current_index].current, 0); + + context_manager.switch_to_prev_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 3); + assert_eq!(context_manager.contexts[current_index].current, 1); + + context_manager.switch_to_prev_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 3); + assert_eq!(context_manager.contexts[current_index].current, 0); + + context_manager.switch_to_prev_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 2); + assert_eq!(context_manager.contexts[current_index].current, 0); + + context_manager.switch_to_prev_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 1); + assert_eq!(context_manager.contexts[current_index].current, 2); + + context_manager.switch_to_prev_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 1); + assert_eq!(context_manager.contexts[current_index].current, 1); + + context_manager.switch_to_prev_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 1); + assert_eq!(context_manager.contexts[current_index].current, 0); + + context_manager.switch_to_prev_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 0); + assert_eq!(context_manager.contexts[current_index].current, 0); + } + + #[test] + fn test_switch_to_next_and_prev_split_or_tab() { + let window_id: WindowId = WindowId::from(0); + + let mut context_manager = + ContextManager::start_with_capacity(5, VoidListener {}, window_id).unwrap(); + let should_redirect = true; + let split_down = false; + + context_manager.add_context(should_redirect, 0); + context_manager.split(0, split_down); + context_manager.split(0, split_down); + context_manager.add_context(should_redirect, 0); + context_manager.set_current(0); + assert_eq!(context_manager.len(), 3); + assert_eq!(context_manager.current_index, 0); + + let mut current_index; + + // Next + context_manager.switch_to_next_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 1); + assert_eq!(context_manager.contexts[current_index].current, 0); + + context_manager.switch_to_next_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 1); + assert_eq!(context_manager.contexts[current_index].current, 1); + + context_manager.switch_to_next_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 1); + assert_eq!(context_manager.contexts[current_index].current, 2); + + context_manager.switch_to_next_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 2); + assert_eq!(context_manager.contexts[current_index].current, 0); + + // Prev + context_manager.switch_to_prev_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 1); + assert_eq!(context_manager.contexts[current_index].current, 2); + + context_manager.switch_to_prev_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 1); + assert_eq!(context_manager.contexts[current_index].current, 1); + + context_manager.switch_to_prev_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 1); + assert_eq!(context_manager.contexts[current_index].current, 0); + + context_manager.switch_to_prev_split_or_tab(); + current_index = context_manager.current_index; + assert_eq!(current_index, 0); + assert_eq!(context_manager.contexts[current_index].current, 0); + } + #[test] fn test_move_current_to_next() { let window_id = WindowId::from(0);