diff --git a/default-plugins/session-manager/src/main.rs b/default-plugins/session-manager/src/main.rs index 7e39307d9..8f70cc295 100644 --- a/default-plugins/session-manager/src/main.rs +++ b/default-plugins/session-manager/src/main.rs @@ -7,8 +7,8 @@ use std::collections::BTreeMap; use ui::{ components::{ - render_controls_line, render_new_session_line, render_prompt, render_resurrection_toggle, - Colors, + render_controls_line, render_error, render_new_session_line, render_prompt, + render_renaming_session_screen, render_resurrection_toggle, Colors, }, SessionUiInfo, }; @@ -23,6 +23,8 @@ struct State { resurrectable_sessions: ResurrectableSessions, search_term: String, new_session_name: Option, + renaming_session_name: Option, + error: Option, browsing_resurrection_sessions: bool, colors: Colors, } @@ -67,6 +69,9 @@ impl ZellijPlugin for State { if self.browsing_resurrection_sessions { self.resurrectable_sessions.render(rows, cols); return; + } else if let Some(new_session_name) = self.renaming_session_name.as_ref() { + render_renaming_session_screen(&new_session_name, rows, cols); + return; } render_resurrection_toggle(cols, false); render_prompt( @@ -87,7 +92,11 @@ impl ZellijPlugin for State { self.sessions.is_searching, self.colors, ); - render_controls_line(self.sessions.is_searching, rows, cols, self.colors); + if let Some(error) = self.error.as_ref() { + render_error(&error, rows, cols); + } else { + render_controls_line(self.sessions.is_searching, rows, cols, self.colors); + } } } @@ -96,6 +105,10 @@ impl State { self.sessions.reset_selected_index(); } fn handle_key(&mut self, key: Key) -> bool { + if self.error.is_some() { + self.error = None; + return true; + } let mut should_render = false; if let Key::Right = key { if self.new_session_name.is_none() { @@ -110,14 +123,14 @@ impl State { } else if let Key::Down = key { if self.browsing_resurrection_sessions { self.resurrectable_sessions.move_selection_down(); - } else if self.new_session_name.is_none() { + } else if self.new_session_name.is_none() && self.renaming_session_name.is_none() { self.sessions.move_selection_down(); } should_render = true; } else if let Key::Up = key { if self.browsing_resurrection_sessions { self.resurrectable_sessions.move_selection_up(); - } else if self.new_session_name.is_none() { + } else if self.new_session_name.is_none() && self.renaming_session_name.is_none() { self.sessions.move_selection_up(); } should_render = true; @@ -126,6 +139,8 @@ impl State { self.handle_selection(); } else if let Some(new_session_name) = self.new_session_name.as_mut() { new_session_name.push(character); + } else if let Some(renaming_session_name) = self.renaming_session_name.as_mut() { + renaming_session_name.push(character); } else if self.browsing_resurrection_sessions { self.resurrectable_sessions.handle_character(character); } else { @@ -141,6 +156,12 @@ impl State { } else { new_session_name.pop(); } + } else if let Some(renaming_session_name) = self.renaming_session_name.as_mut() { + if renaming_session_name.is_empty() { + self.renaming_session_name = None; + } else { + renaming_session_name.pop(); + } } else if self.browsing_resurrection_sessions { self.resurrectable_sessions.handle_backspace(); } else { @@ -150,7 +171,7 @@ impl State { } should_render = true; } else if let Key::Ctrl('w') = key { - if self.sessions.is_searching { + if self.sessions.is_searching || self.browsing_resurrection_sessions { // no-op } else if self.new_session_name.is_some() { self.new_session_name = None; @@ -158,6 +179,15 @@ impl State { self.new_session_name = Some(String::new()); } should_render = true; + } else if let Key::Ctrl('r') = key { + if self.sessions.is_searching || self.browsing_resurrection_sessions { + // no-op + } else if self.renaming_session_name.is_some() { + self.renaming_session_name = None; + } else { + self.renaming_session_name = Some(String::new()); + } + should_render = true; } else if let Key::Ctrl('c') = key { if let Some(new_session_name) = self.new_session_name.as_mut() { if new_session_name.is_empty() { @@ -165,6 +195,12 @@ impl State { } else { new_session_name.clear() } + } else if let Some(renaming_session_name) = self.renaming_session_name.as_mut() { + if renaming_session_name.is_empty() { + self.renaming_session_name = None; + } else { + renaming_session_name.clear() + } } else if !self.search_term.is_empty() { self.search_term.clear(); self.sessions @@ -190,7 +226,15 @@ impl State { should_render = true; } } else if let Key::Esc = key { - hide_self(); + if self.renaming_session_name.is_some() { + self.renaming_session_name = None; + should_render = true; + } else if self.new_session_name.is_some() { + self.new_session_name = None; + should_render = true; + } else { + hide_self(); + } } should_render } @@ -210,6 +254,29 @@ impl State { } else { switch_session(Some(new_session_name)); } + } else if let Some(renaming_session_name) = &self.renaming_session_name.take() { + if renaming_session_name.is_empty() { + // TODO: implement these, then implement the error UI, then implement the renaming + // session screen, then test it + self.show_error("New name must not be empty."); + return; // s that we don't hide self + } else if self.session_name.as_ref() == Some(renaming_session_name) { + // noop - we're already called that! + return; // s that we don't hide self + } else if self.sessions.has_session(&renaming_session_name) { + self.show_error("A session by this name already exists."); + return; // s that we don't hide self + } else if self + .resurrectable_sessions + .has_session(&renaming_session_name) + { + self.show_error("A resurrectable session by this name already exists."); + return; // s that we don't hide self + } else { + self.update_current_session_name_in_ui(&renaming_session_name); + rename_session(&renaming_session_name); + return; // s that we don't hide self + } } else if let Some(selected_session_name) = self.sessions.get_selected_session_name() { let selected_tab = self.sessions.get_selected_tab_position(); let selected_pane = self.sessions.get_selected_pane_id(); @@ -235,6 +302,16 @@ impl State { .update_search_term(&self.search_term, &self.colors); hide_self(); } + fn show_error(&mut self, error_text: &str) { + self.error = Some(error_text.to_owned()); + } + fn update_current_session_name_in_ui(&mut self, new_name: &str) { + if let Some(old_session_name) = self.session_name.as_ref() { + self.sessions + .update_session_name(&old_session_name, new_name); + } + self.session_name = Some(new_name.to_owned()); + } fn update_session_infos(&mut self, session_infos: Vec) { let session_infos: Vec = session_infos .iter() diff --git a/default-plugins/session-manager/src/resurrectable_sessions.rs b/default-plugins/session-manager/src/resurrectable_sessions.rs index beed254fd..dfbb86f0d 100644 --- a/default-plugins/session-manager/src/resurrectable_sessions.rs +++ b/default-plugins/session-manager/src/resurrectable_sessions.rs @@ -308,6 +308,11 @@ impl ResurrectableSessions { self.search_term.pop(); self.update_search_term(); } + pub fn has_session(&self, session_name: &str) -> bool { + self.all_resurrectable_sessions + .iter() + .any(|s| s.0 == session_name) + } fn update_search_term(&mut self) { let mut matches = vec![]; let matcher = SkimMatcherV2::default().use_cache(true); diff --git a/default-plugins/session-manager/src/session_list.rs b/default-plugins/session-manager/src/session_list.rs index 788d324f7..fa63571e8 100644 --- a/default-plugins/session-manager/src/session_list.rs +++ b/default-plugins/session-manager/src/session_list.rs @@ -314,6 +314,15 @@ impl SessionList { pub fn reset_selected_index(&mut self) { self.selected_index.reset(); } + pub fn has_session(&self, session_name: &str) -> bool { + self.session_ui_infos.iter().any(|s| s.name == session_name) + } + pub fn update_session_name(&mut self, old_name: &str, new_name: &str) { + self.session_ui_infos + .iter_mut() + .find(|s| s.name == old_name) + .map(|s| s.name = new_name.to_owned()); + } } #[derive(Debug, Clone, Default)] diff --git a/default-plugins/session-manager/src/ui/components.rs b/default-plugins/session-manager/src/ui/components.rs index be3da41df..4d9839c0f 100644 --- a/default-plugins/session-manager/src/ui/components.rs +++ b/default-plugins/session-manager/src/ui/components.rs @@ -558,29 +558,68 @@ pub fn render_new_session_line(session_name: &Option, is_searching: bool } } +pub fn render_error(error_text: &str, rows: usize, columns: usize) { + print_text_with_coordinates( + Text::new(format!("Error: {}", error_text)).color_range(3, ..), + 0, + rows, + Some(columns), + None, + ); +} + +pub fn render_renaming_session_screen(new_session_name: &str, rows: usize, columns: usize) { + if rows == 0 || columns == 0 { + return; + } + let prompt_text = "NEW NAME FOR CURRENT SESSION"; + let new_session_name = format!("{}_", new_session_name); + let prompt_y_location = (rows / 2).saturating_sub(1); + let session_name_y_location = (rows / 2) + 1; + let prompt_x_location = columns.saturating_sub(prompt_text.chars().count()) / 2; + let session_name_x_location = columns.saturating_sub(new_session_name.chars().count()) / 2; + print_text_with_coordinates( + Text::new(prompt_text).color_range(0, ..), + prompt_x_location, + prompt_y_location, + None, + None, + ); + print_text_with_coordinates( + Text::new(new_session_name).color_range(3, ..), + session_name_x_location, + session_name_y_location, + None, + None, + ); +} + pub fn render_controls_line(is_searching: bool, row: usize, max_cols: usize, colors: Colors) { let (arrows, navigate) = if is_searching { (colors.magenta("<↓↑>"), colors.bold("Navigate")) } else { (colors.magenta("<←↓↑→>"), colors.bold("Navigate and Expand")) }; + let rename = colors.magenta(""); + let rename_text = colors.bold("Rename session"); let enter = colors.magenta(""); let select = colors.bold("Switch to selected"); let esc = colors.magenta(""); let to_hide = colors.bold("Hide"); - if max_cols >= 80 { + if max_cols >= 104 { print!( - "\u{1b}[m\u{1b}[{row}HHelp: {arrows} - {navigate}, {enter} - {select}, {esc} - {to_hide}" + "\u{1b}[m\u{1b}[{row}HHelp: {arrows} - {navigate}, {enter} - {select}, {rename} - {rename_text}, {esc} - {to_hide}" ); - } else if max_cols >= 57 { + } else if max_cols >= 73 { let navigate = colors.bold("Navigate"); let select = colors.bold("Switch"); + let rename_text = colors.bold("Rename"); print!( - "\u{1b}[m\u{1b}[{row}HHelp: {arrows} - {navigate}, {enter} - {select}, {esc} - {to_hide}" + "\u{1b}[m\u{1b}[{row}HHelp: {arrows} - {navigate}, {enter} - {select}, {rename} - {rename_text}, {esc} - {to_hide}" ); - } else if max_cols >= 20 { - print!("\u{1b}[m\u{1b}[{row}H{arrows}/{enter}/{esc}"); + } else if max_cols >= 28 { + print!("\u{1b}[m\u{1b}[{row}H{arrows}/{enter}/{rename}/{esc}"); } } diff --git a/zellij-client/src/cli_client.rs b/zellij-client/src/cli_client.rs index 6ebc10aeb..ef9f9122f 100644 --- a/zellij-client/src/cli_client.rs +++ b/zellij-client/src/cli_client.rs @@ -35,6 +35,10 @@ pub fn start_cli_client(os_input: Box, session_name: &str, acti log_lines.iter().for_each(|line| println!("{line}")); process::exit(0); }, + Some((ServerToClientMsg::LogError(log_lines), _)) => { + log_lines.iter().for_each(|line| eprintln!("{line}")); + process::exit(2); + }, _ => {}, } } diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index 6cb855118..c704ad84a 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -46,6 +46,7 @@ pub(crate) enum ClientInstruction { StartedParsingStdinQuery, DoneParsingStdinQuery, Log(Vec), + LogError(Vec), SwitchSession(ConnectToSession), SetSynchronizedOutput(Option), } @@ -62,6 +63,7 @@ impl From for ClientInstruction { ServerToClientMsg::Connected => ClientInstruction::Connected, ServerToClientMsg::ActiveClients(clients) => ClientInstruction::ActiveClients(clients), ServerToClientMsg::Log(log_lines) => ClientInstruction::Log(log_lines), + ServerToClientMsg::LogError(log_lines) => ClientInstruction::LogError(log_lines), ServerToClientMsg::SwitchSession(connect_to_session) => { ClientInstruction::SwitchSession(connect_to_session) }, @@ -80,6 +82,7 @@ impl From<&ClientInstruction> for ClientContext { ClientInstruction::Connected => ClientContext::Connected, ClientInstruction::ActiveClients(_) => ClientContext::ActiveClients, ClientInstruction::Log(_) => ClientContext::Log, + ClientInstruction::LogError(_) => ClientContext::LogError, ClientInstruction::StartedParsingStdinQuery => ClientContext::StartedParsingStdinQuery, ClientInstruction::DoneParsingStdinQuery => ClientContext::DoneParsingStdinQuery, ClientInstruction::SwitchSession(..) => ClientContext::SwitchSession, @@ -473,6 +476,11 @@ pub fn start_client( log::info!("{line}"); } }, + ClientInstruction::LogError(lines_to_log) => { + for line in lines_to_log { + log::error!("{line}"); + } + }, ClientInstruction::SwitchSession(connect_to_session) => { reconnect_to_session = Some(connect_to_session); os_input.send_to_server(ClientToServerMsg::ClientExited); diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs index 38b89ceac..ca24c015a 100644 --- a/zellij-server/src/plugins/zellij_exports.rs +++ b/zellij-server/src/plugins/zellij_exports.rs @@ -237,6 +237,9 @@ fn host_run_plugin_command(env: FunctionEnvMut) { PluginCommand::OpenCommandPaneInPlace(command_to_run) => { open_command_pane_in_place(env, command_to_run) }, + PluginCommand::RenameSession(new_session_name) => { + rename_session(env, new_session_name) + }, }, (PermissionStatus::Denied, permission) => { log::error!( @@ -1188,6 +1191,17 @@ fn rename_tab(env: &ForeignFunctionEnv, tab_index: u32, new_name: &str) { apply_action!(rename_tab_action, error_msg, env); } +fn rename_session(env: &ForeignFunctionEnv, new_session_name: String) { + let error_msg = || { + format!( + "failed to rename session in plugin {}", + env.plugin_env.name() + ) + }; + let action = Action::RenameSession(new_session_name); + apply_action!(action, error_msg, env); +} + // Custom panic handler for plugins. // // This is called when a panic occurs in a plugin. Since most panics will likely originate in the @@ -1315,6 +1329,7 @@ fn check_command_permission( | PluginCommand::SwitchSession(..) | PluginCommand::DeleteDeadSession(..) | PluginCommand::DeleteAllDeadSessions + | PluginCommand::RenameSession(..) | PluginCommand::RenameTab(..) => PermissionType::ChangeApplicationState, _ => return (PermissionStatus::Granted, None), }; diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index a7d85e4ac..fdc83ac64 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -784,6 +784,11 @@ pub(crate) fn route_action( .send_to_screen(ScreenInstruction::BreakPaneLeft(client_id)) .with_context(err_context)?; }, + Action::RenameSession(name) => { + senders + .send_to_screen(ScreenInstruction::RenameSession(name, client_id)) + .with_context(err_context)?; + }, } Ok(should_break) } diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index fbc355666..1adc2b1ac 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -15,6 +15,8 @@ use zellij_utils::input::command::RunCommand; use zellij_utils::input::options::Clipboard; use zellij_utils::pane_size::{Size, SizeInPixels}; use zellij_utils::{ + consts::{session_info_folder_for_session, ZELLIJ_SOCK_DIR}, + envs::set_session_name, input::command::TerminalAction, input::layout::{ FloatingPaneLayout, Layout, PluginUserConfiguration, Run, RunPlugin, RunPluginLocation, @@ -314,6 +316,7 @@ pub enum ScreenInstruction { ClientTabIndexOrPaneId, ), DumpLayoutToHd, + RenameSession(String, ClientId), // String -> new name } impl From<&ScreenInstruction> for ScreenContext { @@ -492,6 +495,7 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::ReplacePane(..) => ScreenContext::ReplacePane, ScreenInstruction::NewInPlacePluginPane(..) => ScreenContext::NewInPlacePluginPane, ScreenInstruction::DumpLayoutToHd => ScreenContext::DumpLayoutToHd, + ScreenInstruction::RenameSession(..) => ScreenContext::RenameSession, } } } @@ -1520,7 +1524,10 @@ impl Screen { } } - pub fn change_mode(&mut self, mode_info: ModeInfo, client_id: ClientId) -> Result<()> { + pub fn change_mode(&mut self, mut mode_info: ModeInfo, client_id: ClientId) -> Result<()> { + if mode_info.session_name.as_ref() != Some(&self.session_name) { + mode_info.session_name = Some(self.session_name.clone()); + } let previous_mode = self .mode_info .get(&client_id) @@ -3471,6 +3478,69 @@ pub(crate) fn screen_thread_main( screen.dump_layout_to_hd()?; } }, + ScreenInstruction::RenameSession(name, client_id) => { + if screen.session_infos_on_machine.contains_key(&name) { + let error_text = "A session by this name already exists."; + log::error!("{}", error_text); + if let Some(os_input) = &mut screen.bus.os_input { + let _ = os_input.send_to_client( + client_id, + ServerToClientMsg::LogError(vec![error_text.to_owned()]), + ); + } + } else if screen.resurrectable_sessions.contains_key(&name) { + let error_text = + "A resurrectable session by this name exists, cannot use this name."; + log::error!("{}", error_text); + if let Some(os_input) = &mut screen.bus.os_input { + let _ = os_input.send_to_client( + client_id, + ServerToClientMsg::LogError(vec![error_text.to_owned()]), + ); + } + } else { + let err_context = || format!("Failed to rename session"); + let old_session_name = screen.session_name.clone(); + + // update state + screen.session_name = name.clone(); + screen.default_mode_info.session_name = Some(name.clone()); + for (_client_id, mut mode_info) in screen.mode_info.iter_mut() { + mode_info.session_name = Some(name.clone()); + } + for (_, tab) in screen.tabs.iter_mut() { + tab.rename_session(name.clone()).with_context(err_context)?; + } + + // rename socket file + let old_socket_file_path = ZELLIJ_SOCK_DIR.join(&old_session_name); + let new_socket_file_path = ZELLIJ_SOCK_DIR.join(&name); + if let Err(e) = std::fs::rename(old_socket_file_path, new_socket_file_path) { + log::error!("Failed to rename ipc socket: {:?}", e); + } + + // rename session_info folder (TODO: make this atomic, right now there is a + // chance background_jobs will re-create this folder before it knows the + // session was renamed) + let old_session_info_folder = + session_info_folder_for_session(&old_session_name); + let new_session_info_folder = session_info_folder_for_session(&name); + if let Err(e) = + std::fs::rename(old_session_info_folder, new_session_info_folder) + { + log::error!("Failed to rename session_info folder: {:?}", e); + } + + // report + screen + .log_and_report_session_state() + .with_context(err_context)?; + + // set the env variable + set_session_name(name); + } + screen.unblock_input()?; + }, } } Ok(()) diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index 50b9c2e46..d1a940c79 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -838,6 +838,16 @@ impl Tab { } Ok(()) } + pub fn rename_session(&mut self, new_session_name: String) -> Result<()> { + { + let mode_infos = &mut self.mode_info.borrow_mut(); + for (_client_id, mut mode_info) in mode_infos.iter_mut() { + mode_info.session_name = Some(new_session_name.clone()); + } + self.default_mode_info.session_name = Some(new_session_name); + } + self.update_input_modes() + } pub fn update_input_modes(&mut self) -> Result<()> { // this updates all plugins with the client's input mode let mode_infos = self.mode_info.borrow(); diff --git a/zellij-tile/src/shim.rs b/zellij-tile/src/shim.rs index eb898b4fc..8f2b297a3 100644 --- a/zellij-tile/src/shim.rs +++ b/zellij-tile/src/shim.rs @@ -682,6 +682,14 @@ pub fn delete_all_dead_sessions() { unsafe { host_run_plugin_command() }; } +/// Rename the current session +pub fn rename_session(name: &str) { + let plugin_command = PluginCommand::RenameSession(name.to_owned()); + let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap(); + object_to_stdout(&protobuf_plugin_command.encode_to_vec()); + unsafe { host_run_plugin_command() }; +} + // Utility Functions #[allow(unused)] diff --git a/zellij-utils/assets/prost/api.action.rs b/zellij-utils/assets/prost/api.action.rs index 97976393b..de46a38b4 100644 --- a/zellij-utils/assets/prost/api.action.rs +++ b/zellij-utils/assets/prost/api.action.rs @@ -5,7 +5,7 @@ pub struct Action { pub name: i32, #[prost( oneof = "action::OptionalPayload", - tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44" + tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45" )] pub optional_payload: ::core::option::Option, } @@ -100,6 +100,8 @@ pub mod action { RenamePluginPanePayload(super::IdAndName), #[prost(message, tag = "44")] RenameTabPayload(super::IdAndName), + #[prost(string, tag = "45")] + RenameSessionPayload(::prost::alloc::string::String), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -400,6 +402,7 @@ pub enum ActionName { BreakPane = 77, BreakPaneRight = 78, BreakPaneLeft = 79, + RenameSession = 80, } impl ActionName { /// String value of the enum field names used in the ProtoBuf definition. @@ -488,6 +491,7 @@ impl ActionName { ActionName::BreakPane => "BreakPane", ActionName::BreakPaneRight => "BreakPaneRight", ActionName::BreakPaneLeft => "BreakPaneLeft", + ActionName::RenameSession => "RenameSession", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -573,6 +577,7 @@ impl ActionName { "BreakPane" => Some(Self::BreakPane), "BreakPaneRight" => Some(Self::BreakPaneRight), "BreakPaneLeft" => Some(Self::BreakPaneLeft), + "RenameSession" => Some(Self::RenameSession), _ => None, } } diff --git a/zellij-utils/assets/prost/api.plugin_command.rs b/zellij-utils/assets/prost/api.plugin_command.rs index f0d2fe02f..0dd5f6814 100644 --- a/zellij-utils/assets/prost/api.plugin_command.rs +++ b/zellij-utils/assets/prost/api.plugin_command.rs @@ -5,7 +5,7 @@ pub struct PluginCommand { pub name: i32, #[prost( oneof = "plugin_command::Payload", - tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45" + tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46" )] pub payload: ::core::option::Option, } @@ -102,6 +102,8 @@ pub mod plugin_command { WebRequestPayload(super::WebRequestPayload), #[prost(string, tag = "45")] DeleteDeadSessionPayload(::prost::alloc::string::String), + #[prost(string, tag = "46")] + RenameSessionPayload(::prost::alloc::string::String), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -315,6 +317,7 @@ pub enum CommandName { WebRequest = 72, DeleteDeadSession = 73, DeleteAllDeadSessions = 74, + RenameSession = 75, } impl CommandName { /// String value of the enum field names used in the ProtoBuf definition. @@ -398,6 +401,7 @@ impl CommandName { CommandName::WebRequest => "WebRequest", CommandName::DeleteDeadSession => "DeleteDeadSession", CommandName::DeleteAllDeadSessions => "DeleteAllDeadSessions", + CommandName::RenameSession => "RenameSession", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -478,6 +482,7 @@ impl CommandName { "WebRequest" => Some(Self::WebRequest), "DeleteDeadSession" => Some(Self::DeleteDeadSession), "DeleteAllDeadSessions" => Some(Self::DeleteAllDeadSessions), + "RenameSession" => Some(Self::RenameSession), _ => None, } } diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs index c196c17d1..02380ba4b 100644 --- a/zellij-utils/src/cli.rs +++ b/zellij-utils/src/cli.rs @@ -527,4 +527,7 @@ pub enum CliAction { #[clap(short, long, value_parser)] configuration: Option, }, + RenameSession { + name: String, + }, } diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index 82daf118d..bed6f4747 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -1104,4 +1104,5 @@ pub enum PluginCommand { Vec, // body BTreeMap, // context ), + RenameSession(String), // String -> new session name } diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index 1e4eab8a2..536b3bfba 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -347,6 +347,7 @@ pub enum ScreenContext { ReplacePane, NewInPlacePluginPane, DumpLayoutToHd, + RenameSession, } /// Stack call representations corresponding to the different types of [`PtyInstruction`]s. @@ -404,6 +405,7 @@ pub enum ClientContext { Connected, ActiveClients, Log, + LogError, OwnClientId, StartedParsingStdinQuery, DoneParsingStdinQuery, diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index c0714e59f..c36579c37 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -250,6 +250,7 @@ pub enum Action { BreakPane, BreakPaneRight, BreakPaneLeft, + RenameSession(String), } impl Action { @@ -537,6 +538,7 @@ impl Action { in_place, )]) }, + CliAction::RenameSession { name } => Ok(vec![Action::RenameSession(name)]), } } } diff --git a/zellij-utils/src/ipc.rs b/zellij-utils/src/ipc.rs index a60ecc615..b6e837228 100644 --- a/zellij-utils/src/ipc.rs +++ b/zellij-utils/src/ipc.rs @@ -101,6 +101,7 @@ pub enum ServerToClientMsg { Connected, ActiveClients(Vec), Log(Vec), + LogError(Vec), SwitchSession(ConnectToSession), } diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs index 0c32bfacb..061e24ebc 100644 --- a/zellij-utils/src/kdl/mod.rs +++ b/zellij-utils/src/kdl/mod.rs @@ -505,6 +505,7 @@ impl Action { })?; Ok(Action::Search(search_direction)) }, + "RenameSession" => Ok(Action::RenameSession(string)), _ => Err(ConfigError::new_kdl_error( format!("Unsupported action: {}", action_name), action_node.span().offset(), @@ -957,6 +958,11 @@ impl TryFrom<(&KdlNode, &Options)> for Action { "BreakPane" => Ok(Action::BreakPane), "BreakPaneRight" => Ok(Action::BreakPaneRight), "BreakPaneLeft" => Ok(Action::BreakPaneLeft), + "RenameSession" => parse_kdl_action_char_or_string_arguments!( + action_name, + action_arguments, + kdl_action + ), _ => Err(ConfigError::new_kdl_error( format!("Unsupported action: {}", action_name).into(), kdl_action.span().offset(), diff --git a/zellij-utils/src/plugin_api/action.proto b/zellij-utils/src/plugin_api/action.proto index 1545b9bf4..59eea7af1 100644 --- a/zellij-utils/src/plugin_api/action.proto +++ b/zellij-utils/src/plugin_api/action.proto @@ -51,6 +51,7 @@ message Action { IdAndName rename_terminal_pane_payload = 42; IdAndName rename_plugin_pane_payload = 43; IdAndName rename_tab_payload = 44; + string rename_session_payload = 45; } } @@ -221,6 +222,7 @@ enum ActionName { BreakPane = 77; BreakPaneRight = 78; BreakPaneLeft = 79; + RenameSession = 80; } message Position { diff --git a/zellij-utils/src/plugin_api/action.rs b/zellij-utils/src/plugin_api/action.rs index 0addb0dcc..1048d91f6 100644 --- a/zellij-utils/src/plugin_api/action.rs +++ b/zellij-utils/src/plugin_api/action.rs @@ -626,6 +626,12 @@ impl TryFrom for Action { Some(_) => Err("BreakPaneLeft should not have a payload"), None => Ok(Action::BreakPaneLeft), }, + Some(ProtobufActionName::RenameSession) => match protobuf_action.optional_payload { + Some(OptionalPayload::RenameSessionPayload(name)) => { + Ok(Action::RenameSession(name)) + }, + _ => Err("Wrong payload for Action::RenameSession"), + }, _ => Err("Unknown Action"), } } @@ -1164,6 +1170,10 @@ impl TryFrom for ProtobufAction { name: ProtobufActionName::BreakPaneLeft as i32, optional_payload: None, }), + Action::RenameSession(session_name) => Ok(ProtobufAction { + name: ProtobufActionName::RenameSession as i32, + optional_payload: Some(OptionalPayload::RenameSessionPayload(session_name)), + }), Action::NoOp | Action::Confirm | Action::NewInPlacePane(..) diff --git a/zellij-utils/src/plugin_api/plugin_command.proto b/zellij-utils/src/plugin_api/plugin_command.proto index 0b950ff0e..53994a88c 100644 --- a/zellij-utils/src/plugin_api/plugin_command.proto +++ b/zellij-utils/src/plugin_api/plugin_command.proto @@ -86,6 +86,7 @@ enum CommandName { WebRequest = 72; DeleteDeadSession = 73; DeleteAllDeadSessions = 74; + RenameSession = 75; } message PluginCommand { @@ -135,6 +136,7 @@ message PluginCommand { RunCommandPayload run_command_payload = 43; WebRequestPayload web_request_payload = 44; string delete_dead_session_payload = 45; + string rename_session_payload = 46; } } diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs index 6b70b18eb..ed476687c 100644 --- a/zellij-utils/src/plugin_api/plugin_command.rs +++ b/zellij-utils/src/plugin_api/plugin_command.rs @@ -635,6 +635,12 @@ impl TryFrom for PluginCommand { _ => Err("Mismatched payload for DeleteDeadSession"), }, Some(CommandName::DeleteAllDeadSessions) => Ok(PluginCommand::DeleteAllDeadSessions), + Some(CommandName::RenameSession) => match protobuf_plugin_command.payload { + Some(Payload::RenameSessionPayload(new_session_name)) => { + Ok(PluginCommand::RenameSession(new_session_name)) + }, + _ => Err("Mismatched payload for RenameSession"), + }, None => Err("Unrecognized plugin command"), } } @@ -1059,6 +1065,10 @@ impl TryFrom for ProtobufPluginCommand { name: CommandName::DeleteAllDeadSessions as i32, payload: None, }), + PluginCommand::RenameSession(new_session_name) => Ok(ProtobufPluginCommand { + name: CommandName::RenameSession as i32, + payload: Some(Payload::RenameSessionPayload(new_session_name)), + }), } } }