From 692f5c586b33cd50b8a7ea597b61880ec819087a Mon Sep 17 00:00:00 2001 From: thrombe Date: Sat, 21 Dec 2024 00:53:14 +0530 Subject: [PATCH] feat: add RenameSession mode and related actions --- default-plugins/status-bar/src/first_line.rs | 2 +- default-plugins/status-bar/src/one_line_ui.rs | 3 +- default-plugins/status-bar/src/second_line.rs | 4 +- default-plugins/tab-bar/src/main.rs | 9 +- zellij-client/src/input_handler.rs | 6 ++ zellij-server/src/route.rs | 10 +++ zellij-server/src/screen.rs | 88 +++++++++++++++++++ zellij-utils/assets/prost/api.action.rs | 6 ++ zellij-utils/assets/prost/api.input_mode.rs | 4 + zellij-utils/src/data.rs | 4 + zellij-utils/src/errors.rs | 2 + zellij-utils/src/input/actions.rs | 2 + zellij-utils/src/input/keybinds.rs | 1 + zellij-utils/src/kdl/mod.rs | 16 ++++ zellij-utils/src/plugin_api/action.proto | 2 + zellij-utils/src/plugin_api/action.rs | 18 ++++ zellij-utils/src/plugin_api/input_mode.proto | 2 + zellij-utils/src/plugin_api/input_mode.rs | 2 + 18 files changed, 177 insertions(+), 4 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 9fd3b7d912..ff7e47b9a5 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -594,7 +594,7 @@ fn get_key_shortcut_for_mode<'a>( InputMode::Resize => KeyAction::Resize, InputMode::Move => KeyAction::Move, InputMode::Scroll | InputMode::Search | InputMode::EnterSearch => KeyAction::Search, - InputMode::Session => KeyAction::Session, + InputMode::Session | InputMode::RenameSession => KeyAction::Session, }; for shortcut in shortcuts.iter_mut() { if shortcut.action == key_action { diff --git a/default-plugins/status-bar/src/one_line_ui.rs b/default-plugins/status-bar/src/one_line_ui.rs index 777e97d27e..0bfa8a1bae 100644 --- a/default-plugins/status-bar/src/one_line_ui.rs +++ b/default-plugins/status-bar/src/one_line_ui.rs @@ -1070,6 +1070,7 @@ fn add_keygroup_separator(help: &ModeInfo, max_len: usize) -> Option { let mode_help_text = match help.mode { InputMode::RenamePane => Some("RENAMING PANE"), InputMode::RenameTab => Some("RENAMING TAB"), + InputMode::RenameSession => Some("RENAMING SESSION"), InputMode::EnterSearch => Some("ENTERING SEARCH TERM"), InputMode::Search => Some("SEARCHING"), _ => None, @@ -1290,7 +1291,7 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec Vec<(String, String, Vec Vec<(String, String, Vec self.mode_info.style.colors.white, }; + let mut name = self.mode_info.session_name.as_deref(); + if self.mode_info.mode == InputMode::RenameSession + && name.map(|s| s.len() == 0).unwrap_or_default() + { + name = Some("Enter name..."); + } + self.tab_line = tab_line( - self.mode_info.session_name.as_deref(), + name, all_tabs, active_tab_index, cols.saturating_sub(1), diff --git a/zellij-client/src/input_handler.rs b/zellij-client/src/input_handler.rs index a37590639a..02b7937c08 100644 --- a/zellij-client/src/input_handler.rs +++ b/zellij-client/src/input_handler.rs @@ -136,6 +136,12 @@ impl InputHandler { None, ); } + if self.mode == InputMode::RenameSession { + self.dispatch_action( + Action::SessionNameInput(pasted_text.as_bytes().to_vec()), + None, + ); + } }, _ => {}, } diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index d5b7825e3b..8d1857226a 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -570,6 +570,16 @@ pub(crate) fn route_action( .send_to_screen(ScreenInstruction::UndoRenameTab(client_id)) .with_context(err_context)?; }, + Action::SessionNameInput(c) => { + senders + .send_to_screen(ScreenInstruction::UpdateSessionName(c, client_id)) + .with_context(err_context)?; + }, + Action::UndoRenameSession => { + senders + .send_to_screen(ScreenInstruction::UndoRenameSession(client_id)) + .with_context(err_context)?; + }, Action::MoveTab(direction) => { let screen_instr = match direction { Direction::Left => ScreenInstruction::MoveTabLeft(client_id), diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 82c1a341cb..731b5f75cc 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -240,6 +240,8 @@ pub enum ScreenInstruction { ToggleTab(ClientId), UpdateTabName(Vec, ClientId), UndoRenameTab(ClientId), + UpdateSessionName(Vec, ClientId), + UndoRenameSession(ClientId), MoveTabLeft(ClientId), MoveTabRight(ClientId), TerminalResize(Size), @@ -501,6 +503,8 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::GoToTabName(..) => ScreenContext::GoToTabName, ScreenInstruction::UpdateTabName(..) => ScreenContext::UpdateTabName, ScreenInstruction::UndoRenameTab(..) => ScreenContext::UndoRenameTab, + ScreenInstruction::UpdateSessionName(..) => ScreenContext::UpdateSessionName, + ScreenInstruction::UndoRenameSession(..) => ScreenContext::UndoRenameSession, ScreenInstruction::MoveTabLeft(..) => ScreenContext::MoveTabLeft, ScreenInstruction::MoveTabRight(..) => ScreenContext::MoveTabRight, ScreenInstruction::TerminalResize(..) => ScreenContext::TerminalResize, @@ -689,6 +693,7 @@ pub(crate) struct Screen { copy_options: CopyOptions, debug: bool, session_name: String, + visual_session_name: Option, session_infos_on_machine: BTreeMap, // String is the session name, can // also be this session resurrectable_sessions: BTreeMap, // String is the session name, duration is @@ -753,6 +758,7 @@ impl Screen { copy_options, debug, session_name, + visual_session_name: None, session_infos_on_machine, default_layout, default_layout_name, @@ -1674,6 +1680,70 @@ impl Screen { Ok(()) } + pub fn update_visual_session_rename( + &mut self, + buf: Vec, + client_id: ClientId, + ) -> Result<()> { + let err_context = || format!("failed to update session name for client id: {client_id:?}"); + + let s = str::from_utf8(&buf) + .with_context(|| format!("failed to construct tab name from buf: {buf:?}")) + .with_context(err_context)?; + + if self.visual_session_name.is_none() { + self.visual_session_name = Some(String::new()); + } + let name = self.visual_session_name.as_mut().unwrap(); + + match s { + "\0" => { + *name = String::new(); + }, + "\u{007F}" | "\u{0008}" => { + // delete and backspace keys + name.pop(); + }, + c => { + // It only allows printable unicode + if buf.iter().all(|u| matches!(u, 0x20..=0x7E | 0xA0..=0xFF)) { + name.push_str(c); + } + }, + } + + self.generate_and_report_mode_info(client_id, self.visual_session_name.clone()) + .with_context(err_context) + } + + pub fn undo_visual_session_rename(&mut self, client_id: ClientId) -> Result<()> { + let err_context = || format!("failed to undo session rename for client {}", client_id); + _ = self.visual_session_name.take(); + self.generate_and_report_mode_info(client_id, Some(self.session_name.clone())) + .with_context(err_context) + } + + pub fn generate_and_report_mode_info( + &mut self, + client_id: ClientId, + name: Option, + ) -> Result<()> { + let mut mode_info = self + .mode_info + .get(&client_id) + .unwrap_or(&self.default_mode_info) + .clone(); + mode_info.session_name = name; + self.bus + .senders + .send_to_plugin(PluginInstruction::Update(vec![( + None, + Some(client_id), + Event::ModeUpdate(mode_info), + )])) + .context("Failed to report mode info") + } + pub fn update_active_tab_name(&mut self, buf: Vec, client_id: ClientId) -> Result<()> { let err_context = || format!("failed to update active tabs name for client id: {client_id:?}"); @@ -1911,6 +1981,14 @@ impl Screen { } } + if mode_info.mode == InputMode::RenameSession { + self.visual_session_name = Some(self.session_name.clone()); + } else if let Some(name) = self.visual_session_name.take() { + self.bus + .senders + .send_to_screen(ScreenInstruction::RenameSession(name, client_id))?; + } + self.style = mode_info.style; self.mode_info.insert(client_id, mode_info.clone()); for tab in self.tabs.values_mut() { @@ -3651,6 +3729,16 @@ pub(crate) fn screen_thread_main( screen.unblock_input()?; screen.render(None)?; }, + ScreenInstruction::UpdateSessionName(c, client_id) => { + screen.update_visual_session_rename(c, client_id)?; + screen.unblock_input()?; + screen.render(None)?; + }, + ScreenInstruction::UndoRenameSession(client_id) => { + screen.undo_visual_session_rename(client_id)?; + screen.unblock_input()?; + screen.render(None)?; + }, ScreenInstruction::MoveTabLeft(client_id) => { if pending_tab_ids.is_empty() { screen.move_active_tab_to_left(client_id)?; diff --git a/zellij-utils/assets/prost/api.action.rs b/zellij-utils/assets/prost/api.action.rs index 87ca37dd4c..2f77e4aadf 100644 --- a/zellij-utils/assets/prost/api.action.rs +++ b/zellij-utils/assets/prost/api.action.rs @@ -456,6 +456,8 @@ pub enum ActionName { MoveTab = 83, KeybindPipe = 84, TogglePanePinned = 85, + SessionNameInput = 86, + UndoRenameSession = 87, } impl ActionName { /// String value of the enum field names used in the ProtoBuf definition. @@ -550,6 +552,8 @@ impl ActionName { ActionName::MoveTab => "MoveTab", ActionName::KeybindPipe => "KeybindPipe", ActionName::TogglePanePinned => "TogglePanePinned", + ActionName::SessionNameInput => "SessionNameInput", + ActionName::UndoRenameSession => "UndoRenameSession", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -641,6 +645,8 @@ impl ActionName { "MoveTab" => Some(Self::MoveTab), "KeybindPipe" => Some(Self::KeybindPipe), "TogglePanePinned" => Some(Self::TogglePanePinned), + "SessionNameInput" => Some(Self::SessionNameInput), + "UndoRenameSession" => Some(Self::UndoRenameSession), _ => None, } } diff --git a/zellij-utils/assets/prost/api.input_mode.rs b/zellij-utils/assets/prost/api.input_mode.rs index 97e46481e7..8faa7e359b 100644 --- a/zellij-utils/assets/prost/api.input_mode.rs +++ b/zellij-utils/assets/prost/api.input_mode.rs @@ -37,6 +37,8 @@ pub enum InputMode { Prompt = 12, /// / `Tmux` mode allows for basic tmux keybindings functionality Tmux = 13, + /// / `RenameSession` mode allows assigning a new name to active session. + RenameSession = 14, } impl InputMode { /// String value of the enum field names used in the ProtoBuf definition. @@ -59,6 +61,7 @@ impl InputMode { InputMode::Move => "Move", InputMode::Prompt => "Prompt", InputMode::Tmux => "Tmux", + InputMode::RenameSession => "RenameSession", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -78,6 +81,7 @@ impl InputMode { "Move" => Some(Self::Move), "Prompt" => Some(Self::Prompt), "Tmux" => Some(Self::Tmux), + "RenameSession" => Some(Self::RenameSession), _ => None, } } diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index 1f3adf305e..834ba14328 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -1031,6 +1031,9 @@ pub enum InputMode { /// `RenamePane` mode allows assigning a new name to a pane. #[serde(alias = "renamepane")] RenamePane, + /// `RenameSession` mode allows assigning a new name to a session. + #[serde(alias = "renamesession")] + RenameSession, /// `Session` mode allows detaching sessions #[serde(alias = "session")] Session, @@ -1087,6 +1090,7 @@ impl FromStr for InputMode { "scroll" | "Scroll" => Ok(InputMode::Scroll), "renametab" | "RenameTab" => Ok(InputMode::RenameTab), "renamepane" | "RenamePane" => Ok(InputMode::RenamePane), + "renamesession" | "RenameSession" => Ok(InputMode::RenameSession), "session" | "Session" => Ok(InputMode::Session), "move" | "Move" => Ok(InputMode::Move), "prompt" | "Prompt" => Ok(InputMode::Prompt), diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index f27ee06cc2..764e4fe939 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -291,6 +291,8 @@ pub enum ScreenContext { GoToTabName, UpdateTabName, UndoRenameTab, + UpdateSessionName, + UndoRenameSession, MoveTabLeft, MoveTabRight, TerminalResize, diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index f6ecf09677..f139a2c028 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -188,6 +188,8 @@ pub enum Action { CloseFocus, PaneNameInput(Vec), UndoRenamePane, + SessionNameInput(Vec), + UndoRenameSession, /// Create a new tab, optionally with a specified tab layout. NewTab( Option, diff --git a/zellij-utils/src/input/keybinds.rs b/zellij-utils/src/input/keybinds.rs index 3da34e4b8b..006e02aa9a 100644 --- a/zellij-utils/src/input/keybinds.rs +++ b/zellij-utils/src/input/keybinds.rs @@ -84,6 +84,7 @@ impl Keybinds { }, InputMode::RenameTab => Action::TabNameInput(raw_bytes), InputMode::RenamePane => Action::PaneNameInput(raw_bytes), + InputMode::RenameSession => Action::SessionNameInput(raw_bytes), InputMode::EnterSearch => Action::SearchInput(raw_bytes), _ => Action::NoOp, } diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs index 37f36fd8a5..175db2092b 100644 --- a/zellij-utils/src/kdl/mod.rs +++ b/zellij-utils/src/kdl/mod.rs @@ -66,6 +66,7 @@ macro_rules! parse_kdl_action_arguments { "CloseTab" => Ok(Action::CloseTab), "ToggleTab" => Ok(Action::ToggleTab), "UndoRenameTab" => Ok(Action::UndoRenameTab), + "UndoRenameSession" => Ok(Action::UndoRenameSession), "Detach" => Ok(Action::Detach), "Copy" => Ok(Action::Copy), "Confirm" => Ok(Action::Confirm), @@ -414,6 +415,7 @@ impl Action { "Write" => Ok(Action::Write(None, bytes, false)), "PaneNameInput" => Ok(Action::PaneNameInput(bytes)), "TabNameInput" => Ok(Action::TabNameInput(bytes)), + "SessionNameInput" => Ok(Action::SessionNameInput(bytes)), "SearchInput" => Ok(Action::SearchInput(bytes)), "GoToTab" => { let tab_index = *bytes.get(0).ok_or_else(|| { @@ -728,6 +730,14 @@ impl Action { Some(node) }, Action::UndoRenameTab => Some(KdlNode::new("UndoRenameTab")), + Action::SessionNameInput(bytes) => { + let mut node = KdlNode::new("SessionNameInput"); + for byte in bytes { + node.push(KdlValue::Base10(*byte as i64)); + } + Some(node) + }, + Action::UndoRenameSession => Some(KdlNode::new("UndoRenameSession")), Action::MoveTab(direction) => { let mut node = KdlNode::new("MoveTab"); let direction = match direction { @@ -1298,6 +1308,9 @@ impl TryFrom<(&KdlNode, &Options)> for Action { "UndoRenamePane" => { parse_kdl_action_arguments!(action_name, action_arguments, kdl_action) }, + "UndoRenameSession" => { + parse_kdl_action_arguments!(action_name, action_arguments, kdl_action) + }, "NoOp" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action), "GoToNextTab" => parse_kdl_action_arguments!(action_name, action_arguments, kdl_action), "GoToPreviousTab" => { @@ -1475,6 +1488,9 @@ impl TryFrom<(&KdlNode, &Options)> for Action { "TabNameInput" => { parse_kdl_action_u8_arguments!(action_name, action_arguments, kdl_action) }, + "SessionNameInput" => { + parse_kdl_action_u8_arguments!(action_name, action_arguments, kdl_action) + }, "SearchInput" => { parse_kdl_action_u8_arguments!(action_name, action_arguments, kdl_action) }, diff --git a/zellij-utils/src/plugin_api/action.proto b/zellij-utils/src/plugin_api/action.proto index df7f3b05b7..c3db6b4b88 100644 --- a/zellij-utils/src/plugin_api/action.proto +++ b/zellij-utils/src/plugin_api/action.proto @@ -245,6 +245,8 @@ enum ActionName { MoveTab = 83; KeybindPipe = 84; TogglePanePinned = 85; + SessionNameInput = 86; + UndoRenameSession = 87; } message Position { diff --git a/zellij-utils/src/plugin_api/action.rs b/zellij-utils/src/plugin_api/action.rs index 9638543361..3707d7825b 100644 --- a/zellij-utils/src/plugin_api/action.rs +++ b/zellij-utils/src/plugin_api/action.rs @@ -359,6 +359,16 @@ impl TryFrom for Action { Some(_) => Err("UndoRenameTab should not have a payload"), None => Ok(Action::UndoRenameTab), }, + Some(ProtobufActionName::SessionNameInput) => match protobuf_action.optional_payload { + Some(OptionalPayload::RenameSessionPayload(bytes)) => { + Ok(Action::SessionNameInput(bytes.into_bytes())) + }, + _ => Err("Wrong payload for Action::SessionNameInput"), + }, + Some(ProtobufActionName::UndoRenameSession) => match protobuf_action.optional_payload { + Some(_) => Err("UndoRenameSession should not have a payload"), + None => Ok(Action::UndoRenameSession), + }, Some(ProtobufActionName::MoveTab) => match protobuf_action.optional_payload { Some(OptionalPayload::MoveTabPayload(move_tab_payload)) => { let direction: Direction = ProtobufMoveTabDirection::from_i32(move_tab_payload) @@ -1022,6 +1032,14 @@ impl TryFrom for ProtobufAction { name: ProtobufActionName::UndoRenameTab as i32, optional_payload: None, }), + Action::SessionNameInput(bytes) => Ok(ProtobufAction { + name: ProtobufActionName::RenameSession as i32, + optional_payload: Some(OptionalPayload::RenameSessionPayload(String::from_utf8_lossy(&bytes).into())), + }), + Action::UndoRenameSession => Ok(ProtobufAction { + name: ProtobufActionName::UndoRenameSession as i32, + optional_payload: None, + }), Action::MoveTab(direction) => { let direction: ProtobufMoveTabDirection = direction.try_into()?; Ok(ProtobufAction { diff --git a/zellij-utils/src/plugin_api/input_mode.proto b/zellij-utils/src/plugin_api/input_mode.proto index 3112249527..1970528224 100644 --- a/zellij-utils/src/plugin_api/input_mode.proto +++ b/zellij-utils/src/plugin_api/input_mode.proto @@ -37,4 +37,6 @@ enum InputMode { Prompt = 12; /// `Tmux` mode allows for basic tmux keybindings functionality Tmux = 13; + /// `RenameSession` mode allows assigning a new name to active session. + RenameSession = 14; } diff --git a/zellij-utils/src/plugin_api/input_mode.rs b/zellij-utils/src/plugin_api/input_mode.rs index 83a6a00970..2d3d0d37f2 100644 --- a/zellij-utils/src/plugin_api/input_mode.rs +++ b/zellij-utils/src/plugin_api/input_mode.rs @@ -19,6 +19,7 @@ impl TryFrom for InputMode { ProtobufInputMode::Search => Ok(InputMode::Search), ProtobufInputMode::RenameTab => Ok(InputMode::RenameTab), ProtobufInputMode::RenamePane => Ok(InputMode::RenamePane), + ProtobufInputMode::RenameSession => Ok(InputMode::RenameSession), ProtobufInputMode::Session => Ok(InputMode::Session), ProtobufInputMode::Move => Ok(InputMode::Move), ProtobufInputMode::Prompt => Ok(InputMode::Prompt), @@ -41,6 +42,7 @@ impl TryFrom for ProtobufInputMode { InputMode::Search => ProtobufInputMode::Search, InputMode::RenameTab => ProtobufInputMode::RenameTab, InputMode::RenamePane => ProtobufInputMode::RenamePane, + InputMode::RenameSession => ProtobufInputMode::RenameSession, InputMode::Session => ProtobufInputMode::Session, InputMode::Move => ProtobufInputMode::Move, InputMode::Prompt => ProtobufInputMode::Prompt,