From 8e2be2c61e79a0c59a0b03f62f40da1f296b9f2c Mon Sep 17 00:00:00 2001 From: Jae-Heon Ji <32578710+jaeheonji@users.noreply.github.com> Date: Sat, 18 Jun 2022 09:58:42 +0900 Subject: [PATCH] feat: add action to undo rename (#1513) --- src/tests/e2e/cases.rs | 101 ++++++++++++++++++ ...__tests__e2e__cases__undo_rename_pane.snap | 28 +++++ ...j__tests__e2e__cases__undo_rename_tab.snap | 28 +++++ zellij-server/src/panes/plugin_pane.rs | 16 ++- zellij-server/src/panes/terminal_pane.rs | 15 ++- zellij-server/src/route.rs | 12 +++ zellij-server/src/screen.rs | 41 +++++++ zellij-server/src/tab/mod.rs | 21 +++- zellij-utils/assets/config/default.yaml | 4 +- zellij-utils/src/errors.rs | 2 + zellij-utils/src/input/actions.rs | 2 + 11 files changed, 265 insertions(+), 5 deletions(-) create mode 100644 src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_pane.snap create mode 100644 src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_tab.snap diff --git a/src/tests/e2e/cases.rs b/src/tests/e2e/cases.rs index 00cf2715..88b8ff51 100644 --- a/src/tests/e2e/cases.rs +++ b/src/tests/e2e/cases.rs @@ -32,6 +32,7 @@ pub const MOVE_FOCUS_DOWN_IN_PANE_MODE: [u8; 1] = [106]; // j pub const MOVE_FOCUS_UP_IN_PANE_MODE: [u8; 1] = [107]; // k pub const MOVE_FOCUS_LEFT_IN_PANE_MODE: [u8; 1] = [104]; // h pub const MOVE_FOCUS_RIGHT_IN_PANE_MODE: [u8; 1] = [108]; // l +pub const RENAME_PANE_MODE: [u8; 1] = [99]; // c pub const SCROLL_MODE: [u8; 1] = [19]; // ctrl-s pub const SCROLL_UP_IN_SCROLL_MODE: [u8; 1] = [107]; // k @@ -51,6 +52,7 @@ pub const NEW_TAB_IN_TAB_MODE: [u8; 1] = [110]; // n 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 RENAME_TAB_MODE: [u8; 1] = [114]; // r pub const SESSION_MODE: [u8; 1] = [15]; // ctrl-o pub const DETACH_IN_SESSION_MODE: [u8; 1] = [100]; // d @@ -1853,3 +1855,102 @@ pub fn edit_scrollback() { }; assert!(last_snapshot.contains(".dump")); } + +#[test] +#[ignore] +pub fn undo_rename_tab() { + let fake_win_size = Size { + cols: 120, + rows: 24, + }; + + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + let mut runner = RemoteRunner::new(fake_win_size).add_step(Step { + name: "Undo tab name change", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.status_bar_appears() + && remote_terminal.snapshot_contains("Tab #1") + { + remote_terminal.send_key(&TAB_MODE); + remote_terminal.send_key(&RENAME_TAB_MODE); + remote_terminal.send_key(&[97, 97]); + remote_terminal.send_key(&ESC); + step_is_complete = true; + } + step_is_complete + }, + }); + runner.run_all_steps(); + + let last_snapshot = runner.take_snapshot_after(Step { + name: "Wait for tab name to apper on screen", + instruction: |remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.snapshot_contains("Tab #1") { + step_is_complete = true + } + step_is_complete + }, + }); + + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; + assert_snapshot!(last_snapshot); +} + +#[test] +#[ignore] +pub fn undo_rename_pane() { + let fake_win_size = Size { + cols: 120, + rows: 24, + }; + + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + let mut runner = RemoteRunner::new(fake_win_size).add_step(Step { + name: "Undo pane name change", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2) + { + remote_terminal.send_key(&PANE_MODE); + remote_terminal.send_key(&RENAME_PANE_MODE); + remote_terminal.send_key(&[97, 97]); + remote_terminal.send_key(&ESC); + step_is_complete = true; + } + step_is_complete + }, + }); + runner.run_all_steps(); + + let last_snapshot = runner.take_snapshot_after(Step { + name: "Wait for pane name to apper on screen", + instruction: |remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.snapshot_contains("Pane #1") { + step_is_complete = true + } + step_is_complete + }, + }); + + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; + assert_snapshot!(last_snapshot); +} diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_pane.snap new file mode 100644 index 00000000..469d5b7d --- /dev/null +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_pane.snap @@ -0,0 +1,28 @@ +--- +source: src/tests/e2e/cases.rs +expression: last_snapshot +--- + Zellij (e2e-test)  Tab #1  +┌ Pane #1 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│$ █ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_tab.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_tab.snap new file mode 100644 index 00000000..469d5b7d --- /dev/null +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_tab.snap @@ -0,0 +1,28 @@ +--- +source: src/tests/e2e/cases.rs +expression: last_snapshot +--- + Zellij (e2e-test)  Tab #1  +┌ Pane #1 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│$ █ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/zellij-server/src/panes/plugin_pane.rs b/zellij-server/src/panes/plugin_pane.rs index a4f8cacd..56b252be 100644 --- a/zellij-server/src/panes/plugin_pane.rs +++ b/zellij-server/src/panes/plugin_pane.rs @@ -30,6 +30,7 @@ pub(crate) struct PluginPane { pub active_at: Instant, pub pane_title: String, pub pane_name: String, + prev_pane_name: String, frame: bool, borderless: bool, } @@ -54,7 +55,8 @@ impl PluginPane { content_offset: Offset::default(), pane_title: title, borderless: false, - pane_name, + pane_name: pane_name.clone(), + prev_pane_name: pane_name, } } } @@ -386,6 +388,18 @@ impl Pane for PluginPane { fn set_content_offset(&mut self, offset: Offset) { self.content_offset = offset; } + + fn store_pane_name(&mut self) { + if self.pane_name != self.prev_pane_name { + self.prev_pane_name = self.pane_name.clone() + } + } + fn load_pane_name(&mut self) { + if self.pane_name != self.prev_pane_name { + self.pane_name = self.prev_pane_name.clone() + } + } + fn set_borderless(&mut self, borderless: bool) { self.borderless = borderless; } diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs index ac441300..c6083355 100644 --- a/zellij-server/src/panes/terminal_pane.rs +++ b/zellij-server/src/panes/terminal_pane.rs @@ -50,6 +50,7 @@ pub struct TerminalPane { content_offset: Offset, pane_title: String, pane_name: String, + prev_pane_name: String, frame: HashMap, borderless: bool, fake_cursor_locations: HashSet<(usize, usize)>, // (x, y) - these hold a record of previous fake cursors which we need to clear on render @@ -472,6 +473,17 @@ impl Pane for TerminalPane { self.reflow_lines(); } + fn store_pane_name(&mut self) { + if self.pane_name != self.prev_pane_name { + self.prev_pane_name = self.pane_name.clone() + } + } + fn load_pane_name(&mut self) { + if self.pane_name != self.prev_pane_name { + self.pane_name = self.prev_pane_name.clone() + } + } + fn set_borderless(&mut self, borderless: bool) { self.borderless = borderless; } @@ -521,7 +533,8 @@ impl TerminalPane { style, selection_scrolled_at: time::Instant::now(), pane_title: initial_pane_title, - pane_name, + pane_name: pane_name.clone(), + prev_pane_name: pane_name, borderless: false, fake_cursor_locations: HashSet::new(), } diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index f95f17ec..e953c4fe 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -259,6 +259,12 @@ fn route_action( .send_to_screen(ScreenInstruction::UpdatePaneName(c, client_id)) .unwrap(); }, + Action::UndoRenamePane => { + session + .senders + .send_to_screen(ScreenInstruction::UndoRenamePane(client_id)) + .unwrap(); + }, Action::Run(command) => { let run_cmd = Some(TerminalAction::RunCommand(command.clone().into())); let pty_instr = match command.direction { @@ -330,6 +336,12 @@ fn route_action( .send_to_screen(ScreenInstruction::UpdateTabName(c, client_id)) .unwrap(); }, + Action::UndoRenameTab => { + session + .senders + .send_to_screen(ScreenInstruction::UndoRenameTab(client_id)) + .unwrap(); + }, Action::Quit => { to_server .send(ServerInstruction::ClientExit(client_id)) diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index cb55aaa3..b0187fc7 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -85,6 +85,7 @@ pub enum ScreenInstruction { SetSelectable(PaneId, bool, usize), ClosePane(PaneId, Option), UpdatePaneName(Vec, ClientId), + UndoRenamePane(ClientId), NewTab(Layout, Vec, ClientId), SwitchTabNext(ClientId), SwitchTabPrev(ClientId), @@ -93,6 +94,7 @@ pub enum ScreenInstruction { GoToTab(u32, Option), // this Option is a hacky workaround, please do not copy thie behaviour ToggleTab(ClientId), UpdateTabName(Vec, ClientId), + UndoRenameTab(ClientId), TerminalResize(Size), TerminalPixelDimensions(PixelDimensions), TerminalBackgroundColor(String), @@ -168,12 +170,14 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::SetSelectable(..) => ScreenContext::SetSelectable, ScreenInstruction::ClosePane(..) => ScreenContext::ClosePane, ScreenInstruction::UpdatePaneName(..) => ScreenContext::UpdatePaneName, + ScreenInstruction::UndoRenamePane(..) => ScreenContext::UndoRenamePane, ScreenInstruction::NewTab(..) => ScreenContext::NewTab, ScreenInstruction::SwitchTabNext(..) => ScreenContext::SwitchTabNext, ScreenInstruction::SwitchTabPrev(..) => ScreenContext::SwitchTabPrev, ScreenInstruction::CloseTab(..) => ScreenContext::CloseTab, ScreenInstruction::GoToTab(..) => ScreenContext::GoToTab, ScreenInstruction::UpdateTabName(..) => ScreenContext::UpdateTabName, + ScreenInstruction::UndoRenameTab(..) => ScreenContext::UndoRenameTab, ScreenInstruction::TerminalResize(..) => ScreenContext::TerminalResize, ScreenInstruction::TerminalPixelDimensions(..) => { ScreenContext::TerminalPixelDimensions @@ -742,12 +746,23 @@ impl Screen { log::error!("Active tab not found for client id: {:?}", client_id); } } + pub fn undo_active_rename_tab(&mut self, client_id: ClientId) { + if let Some(active_tab) = self.get_active_tab_mut(client_id) { + if active_tab.name != active_tab.prev_name { + active_tab.name = active_tab.prev_name.clone(); + self.update_tabs(); + } + } else { + log::error!("Active tab not found for client id: {:?}", client_id); + } + } pub fn change_mode(&mut self, mode_info: ModeInfo, client_id: ClientId) { let previous_mode = self .mode_info .get(&client_id) .unwrap_or(&self.default_mode_info) .mode; + if previous_mode == InputMode::Scroll && (mode_info.mode == InputMode::Normal || mode_info.mode == InputMode::Locked) { @@ -755,6 +770,23 @@ impl Screen { active_tab.clear_active_terminal_scroll(client_id); } } + + if mode_info.mode == InputMode::RenameTab { + if let Some(active_tab) = self.get_active_tab_mut(client_id) { + active_tab.prev_name = active_tab.name.clone(); + } + } + + if mode_info.mode == InputMode::RenamePane { + if let Some(active_tab) = self.get_active_tab_mut(client_id) { + if let Some(active_pane) = + active_tab.get_active_pane_or_floating_pane_mut(client_id) + { + active_pane.store_pane_name(); + } + } + } + self.style = mode_info.style; self.mode_info.insert(client_id, mode_info.clone()); for tab in self.tabs.values_mut() { @@ -1140,6 +1172,11 @@ pub(crate) fn screen_thread_main( .update_active_pane_name(c, client_id)); screen.render(); }, + ScreenInstruction::UndoRenamePane(client_id) => { + active_tab!(screen, client_id, |tab: &mut Tab| tab + .undo_active_rename_pane(client_id)); + screen.render(); + }, ScreenInstruction::ToggleActiveTerminalFullscreen(client_id) => { active_tab!(screen, client_id, |tab: &mut Tab| tab .toggle_active_pane_fullscreen(client_id)); @@ -1186,6 +1223,10 @@ pub(crate) fn screen_thread_main( screen.update_active_tab_name(c, client_id); screen.render(); }, + ScreenInstruction::UndoRenameTab(client_id) => { + screen.undo_active_rename_tab(client_id); + screen.render(); + }, ScreenInstruction::TerminalResize(new_size) => { screen.resize_to_screen(new_size); screen.render(); diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index b7fb2365..6e7c076f 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -70,6 +70,7 @@ pub(crate) struct Tab { pub index: usize, pub position: usize, pub name: String, + pub prev_name: String, tiled_panes: TiledPanes, floating_panes: FloatingPanes, suppressed_panes: HashMap>, @@ -267,6 +268,8 @@ pub trait Pane { || position_on_screen.column() == self.x() || position_on_screen.column() == (self.x() + self.cols()).saturating_sub(1) } + fn store_pane_name(&mut self); + fn load_pane_name(&mut self); fn set_borderless(&mut self, borderless: bool); fn borderless(&self) -> bool; fn handle_right_click(&mut self, _to: &Position, _client_id: ClientId) {} @@ -346,7 +349,8 @@ impl Tab { tiled_panes, floating_panes, suppressed_panes: HashMap::new(), - name, + name: name.clone(), + prev_name: name, max_panes, viewport, display_area, @@ -1932,6 +1936,21 @@ impl Tab { } } + pub fn undo_active_rename_pane(&mut self, client_id: ClientId) { + if let Some(active_terminal_id) = self.get_active_terminal_id(client_id) { + let active_terminal = if self.are_floating_panes_visible() { + self.floating_panes + .get_pane_mut(PaneId::Terminal(active_terminal_id)) + } else { + self.tiled_panes + .get_pane_mut(PaneId::Terminal(active_terminal_id)) + } + .unwrap(); + + active_terminal.load_pane_name(); + } + } + pub fn is_position_inside_viewport(&self, point: &Position) -> bool { let Position { line: Line(line), diff --git a/zellij-utils/assets/config/default.yaml b/zellij-utils/assets/config/default.yaml index 961d37fb..caff98a7 100644 --- a/zellij-utils/assets/config/default.yaml +++ b/zellij-utils/assets/config/default.yaml @@ -327,7 +327,7 @@ keybinds: renametab: - action: [SwitchToMode: Normal,] key: [Char: "\n", Ctrl: 'c', Esc] - - action: [TabNameInput: [27] , SwitchToMode: Tab,] + - action: [UndoRenameTab , SwitchToMode: Tab,] key: [Esc,] - action: [NewPane: ,] key: [ Alt: 'n',] @@ -348,7 +348,7 @@ keybinds: renamepane: - action: [SwitchToMode: Normal,] key: [Char: "\n", Ctrl: 'c', Esc] - - action: [PaneNameInput: [27] , SwitchToMode: Pane,] + - action: [UndoRenamePane , SwitchToMode: Pane,] key: [Esc,] - action: [NewPane: ,] key: [ Alt: 'n',] diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index 13cf516b..c1317715 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -265,12 +265,14 @@ pub enum ScreenContext { SetFixedWidth, ClosePane, UpdatePaneName, + UndoRenamePane, NewTab, SwitchTabNext, SwitchTabPrev, CloseTab, GoToTab, UpdateTabName, + UndoRenameTab, TerminalResize, TerminalPixelDimensions, TerminalBackgroundColor, diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index 4bc03c42..6527a536 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -91,6 +91,7 @@ pub enum Action { /// Close the focus pane. CloseFocus, PaneNameInput(Vec), + UndoRenamePane, /// Create a new tab, optionally with a specified tab layout. NewTab(Option), /// Do nothing. @@ -104,6 +105,7 @@ pub enum Action { GoToTab(u32), ToggleTab, TabNameInput(Vec), + UndoRenameTab, /// Run specified command in new pane. Run(RunCommandAction), /// Detach session and exit