diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index ae90ba93..24763cbc 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -364,6 +364,7 @@ impl SessionMetaData { rounded_corners: new_config.ui.pane_frames.rounded_corners, hide_session_name: new_config.ui.pane_frames.hide_session_name, stacked_resize: new_config.options.stacked_resize.unwrap_or(true), + default_editor: new_config.options.scrollback_editor.clone(), }) .unwrap(); self.senders diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__switch_to_mode_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__switch_to_mode_plugin_command.snap index 36de9362..7baa636d 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__switch_to_mode_plugin_command.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__switch_to_mode_plugin_command.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/plugins/./unit/plugin_tests.rs -assertion_line: 1043 +assertion_line: 1143 expression: "format!(\"{:#?}\", switch_to_mode_event)" --- Some( @@ -76,6 +76,8 @@ Some( session_name: Some( "zellij-test", ), + editor: None, + shell: None, }, 1, ), diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 6059cda4..9248f21a 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -40,7 +40,7 @@ use crate::{ panes::sixel::SixelImageStore, panes::PaneId, plugins::{PluginId, PluginInstruction, PluginRenderAsset}, - pty::{ClientTabIndexOrPaneId, PtyInstruction, VteBytes}, + pty::{get_default_shell, ClientTabIndexOrPaneId, PtyInstruction, VteBytes}, tab::{SuppressedPanes, Tab}, thread_bus::Bus, ui::{ @@ -368,6 +368,7 @@ pub enum ScreenInstruction { rounded_corners: bool, hide_session_name: bool, stacked_resize: bool, + default_editor: Option, }, RerunCommandPane(u32), // u32 - terminal pane id ResizePaneWithId(ResizeStrategy, PaneId), @@ -687,12 +688,13 @@ pub(crate) struct Screen { resurrectable_sessions: BTreeMap, // String is the session name, duration is // its creation time default_layout: Box, - default_shell: Option, + default_shell: PathBuf, styled_underlines: bool, arrow_fonts: bool, layout_dir: Option, default_layout_name: Option, explicitly_disable_kitty_keyboard_protocol: bool, + default_editor: Option, } impl Screen { @@ -709,7 +711,7 @@ impl Screen { debug: bool, default_layout: Box, default_layout_name: Option, - default_shell: Option, + default_shell: PathBuf, session_serialization: bool, serialize_pane_viewport: bool, scrollback_lines_to_serialize: Option, @@ -718,6 +720,7 @@ impl Screen { layout_dir: Option, explicitly_disable_kitty_keyboard_protocol: bool, stacked_resize: bool, + default_editor: Option, ) -> Self { let session_name = mode_info.session_name.clone().unwrap_or_default(); let session_info = SessionInfo::new(session_name.clone()); @@ -760,6 +763,7 @@ impl Screen { resurrectable_sessions, layout_dir, explicitly_disable_kitty_keyboard_protocol, + default_editor, } } @@ -1359,6 +1363,7 @@ impl Screen { self.arrow_fonts, self.styled_underlines, self.explicitly_disable_kitty_keyboard_protocol, + self.default_editor.clone(), ); for (client_id, mode_info) in &self.mode_info { tab.change_mode_info(mode_info.clone(), *client_id); @@ -1650,7 +1655,7 @@ impl Screen { } fn dump_layout_to_hd(&mut self) -> Result<()> { let err_context = || format!("Failed to log and report session state"); - let session_layout_metadata = self.get_layout_metadata(self.default_shell.clone()); + let session_layout_metadata = self.get_layout_metadata(Some(self.default_shell.clone())); self.bus .senders .send_to_plugin(PluginInstruction::LogLayoutToHd(session_layout_metadata)) @@ -2450,6 +2455,7 @@ impl Screen { rounded_corners: bool, hide_session_name: bool, stacked_resize: bool, + default_editor: Option, client_id: ClientId, ) -> Result<()> { let should_support_arrow_fonts = !simplified_ui; @@ -2458,7 +2464,8 @@ impl Screen { self.default_mode_info.update_theme(theme); self.default_mode_info .update_rounded_corners(rounded_corners); - self.default_shell = default_shell.clone(); + self.default_shell = default_shell.clone().unwrap_or_else(|| get_default_shell()); + self.default_editor = default_editor.clone().or_else(|| get_default_editor()); self.auto_layout = auto_layout; self.copy_options.command = copy_command.clone(); self.copy_options.copy_on_select = copy_on_select; @@ -2477,6 +2484,7 @@ impl Screen { tab.update_theme(theme); tab.update_rounded_corners(rounded_corners); tab.update_default_shell(default_shell.clone()); + tab.update_default_editor(self.default_editor.clone()); tab.update_auto_layout(auto_layout); tab.update_copy_options(&self.copy_options); tab.set_pane_frames(pane_frames); @@ -2749,6 +2757,19 @@ impl Screen { } } +#[cfg(not(test))] +fn get_default_editor() -> Option { + std::env::var("EDITOR") + .or_else(|_| std::env::var("VISUAL")) + .map(|e| PathBuf::from(e)) + .ok() +} + +#[cfg(test)] +fn get_default_editor() -> Option { + None +} + // The box is here in order to make the // NewClient enum smaller #[allow(clippy::boxed_local)] @@ -2769,7 +2790,20 @@ pub(crate) fn screen_thread_main( let scrollback_lines_to_serialize = config_options.scrollback_lines_to_serialize; let session_is_mirrored = config_options.mirror_session.unwrap_or(false); let layout_dir = config_options.layout_dir; - let default_shell = config_options.default_shell; + #[cfg(test)] + let default_shell = config_options + .default_shell + .clone() + .unwrap_or(PathBuf::from("/bin/sh")); + #[cfg(not(test))] + let default_shell = config_options + .default_shell + .clone() + .unwrap_or_else(|| get_default_shell()); + let default_editor = config_options + .scrollback_editor + .clone() + .or_else(|| get_default_editor()); let default_layout_name = config_options .default_layout .map(|l| format!("{}", l.display())); @@ -2819,6 +2853,7 @@ pub(crate) fn screen_thread_main( layout_dir, explicitly_disable_kitty_keyboard_protocol, stacked_resize, + default_editor, ); let mut pending_tab_ids: HashSet = HashSet::new(); @@ -3240,7 +3275,7 @@ pub(crate) fn screen_thread_main( ScreenInstruction::DumpLayoutToPlugin(plugin_id) => { let err_context = || format!("Failed to dump layout"); let session_layout_metadata = - screen.get_layout_metadata(screen.default_shell.clone()); + screen.get_layout_metadata(Some(screen.default_shell.clone())); screen .bus .senders @@ -3254,7 +3289,7 @@ pub(crate) fn screen_thread_main( ScreenInstruction::ListClientsToPlugin(plugin_id, client_id) => { let err_context = || format!("Failed to dump layout"); let session_layout_metadata = - screen.get_layout_metadata(screen.default_shell.clone()); + screen.get_layout_metadata(Some(screen.default_shell.clone())); screen .bus .senders @@ -4561,6 +4596,7 @@ pub(crate) fn screen_thread_main( rounded_corners, hide_session_name, stacked_resize, + default_editor, } => { screen .reconfigure( @@ -4577,6 +4613,7 @@ pub(crate) fn screen_thread_main( rounded_corners, hide_session_name, stacked_resize, + default_editor, client_id, ) .non_fatal(); diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index 23a791d5..60a2ae98 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -188,7 +188,8 @@ pub(crate) struct Tab { pending_instructions: Vec, // instructions that came while the tab was // pending and need to be re-applied swap_layouts: SwapLayouts, - default_shell: Option, + default_shell: PathBuf, + default_editor: Option, debug: bool, arrow_fonts: bool, styled_underlines: bool, @@ -582,11 +583,12 @@ impl Tab { terminal_emulator_colors: Rc>, terminal_emulator_color_codes: Rc>>, swap_layouts: (Vec, Vec), - default_shell: Option, + default_shell: PathBuf, debug: bool, arrow_fonts: bool, styled_underlines: bool, explicitly_disable_kitty_keyboard_protocol: bool, + default_editor: Option, ) -> Self { let name = if name.is_empty() { format!("Tab #{}", index + 1) @@ -682,6 +684,7 @@ impl Tab { arrow_fonts, styled_underlines, explicitly_disable_kitty_keyboard_protocol, + default_editor, } } @@ -886,8 +889,13 @@ impl Tab { let mode_infos = self.mode_info.borrow(); let mut plugin_updates = vec![]; for client_id in self.connected_clients.borrow().iter() { - let mode_info = mode_infos.get(client_id).unwrap_or(&self.default_mode_info); - plugin_updates.push((None, Some(*client_id), Event::ModeUpdate(mode_info.clone()))); + let mut mode_info = mode_infos + .get(client_id) + .unwrap_or(&self.default_mode_info) + .clone(); + mode_info.shell = Some(self.default_shell.clone()); + mode_info.editor = self.default_editor.clone(); + plugin_updates.push((None, Some(*client_id), Event::ModeUpdate(mode_info))); } self.senders .send_to_plugin(PluginInstruction::Update(plugin_updates)) @@ -1906,7 +1914,7 @@ impl Tab { self.senders .send_to_pty(PtyInstruction::DropToShellInPane { pane_id: PaneId::Terminal(active_terminal_id), - shell: self.default_shell.clone(), + shell: Some(self.default_shell.clone()), working_dir, }) .with_context(err_context)?; @@ -4418,8 +4426,15 @@ impl Tab { pane.update_arrow_fonts(should_support_arrow_fonts); } } - pub fn update_default_shell(&mut self, default_shell: Option) { - self.default_shell = default_shell; + pub fn update_default_shell(&mut self, mut default_shell: Option) { + if let Some(default_shell) = default_shell.take() { + self.default_shell = default_shell; + } + } + pub fn update_default_editor(&mut self, mut default_editor: Option) { + if let Some(default_editor) = default_editor.take() { + self.default_editor = Some(default_editor); + } } pub fn update_copy_options(&mut self, copy_options: &CopyOptions) { self.clipboard_provider = match ©_options.command { diff --git a/zellij-server/src/tab/unit/tab_integration_tests.rs b/zellij-server/src/tab/unit/tab_integration_tests.rs index 33c99d83..6e2bca6c 100644 --- a/zellij-server/src/tab/unit/tab_integration_tests.rs +++ b/zellij-server/src/tab/unit/tab_integration_tests.rs @@ -251,11 +251,12 @@ fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab { terminal_emulator_colors, terminal_emulator_color_codes, (vec![], vec![]), - None, + PathBuf::from("my_default_shell"), debug, arrow_fonts, styled_underlines, explicitly_disable_kitty_keyboard_protocol, + None, ); tab.apply_layout( TiledPaneLayout::default(), @@ -333,11 +334,12 @@ fn create_new_tab_with_swap_layouts( terminal_emulator_colors, terminal_emulator_color_codes, swap_layouts, - None, + PathBuf::from("my_default_shell"), debug, arrow_fonts, styled_underlines, explicitly_disable_kitty_keyboard_protocol, + None, ); let ( base_layout, @@ -416,11 +418,12 @@ fn create_new_tab_with_os_api( terminal_emulator_colors, terminal_emulator_color_codes, (vec![], vec![]), // swap layouts - None, + PathBuf::from("my_default_shell"), debug, arrow_fonts, styled_underlines, explicitly_disable_kitty_keyboard_protocol, + None, ); tab.apply_layout( TiledPaneLayout::default(), @@ -485,11 +488,12 @@ fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str) terminal_emulator_colors, terminal_emulator_color_codes, (vec![], vec![]), // swap layouts - None, + PathBuf::from("my_default_shell"), debug, arrow_fonts, styled_underlines, explicitly_disable_kitty_keyboard_protocol, + None, ); let pane_ids = tab_layout .extract_run_instructions() @@ -568,11 +572,12 @@ fn create_new_tab_with_mock_pty_writer( terminal_emulator_colors, terminal_emulator_color_codes, (vec![], vec![]), // swap layouts - None, + PathBuf::from("my_default_shell"), debug, arrow_fonts, styled_underlines, explicitly_disable_kitty_keyboard_protocol, + None, ); tab.apply_layout( TiledPaneLayout::default(), @@ -642,11 +647,12 @@ fn create_new_tab_with_sixel_support( terminal_emulator_colors, terminal_emulator_color_codes, (vec![], vec![]), // swap layouts - None, + PathBuf::from("my_default_shell"), debug, arrow_fonts, styled_underlines, explicitly_disable_kitty_keyboard_protocol, + None, ); tab.apply_layout( TiledPaneLayout::default(), diff --git a/zellij-server/src/tab/unit/tab_tests.rs b/zellij-server/src/tab/unit/tab_tests.rs index 88302969..101d77fd 100644 --- a/zellij-server/src/tab/unit/tab_tests.rs +++ b/zellij-server/src/tab/unit/tab_tests.rs @@ -191,11 +191,12 @@ fn create_new_tab(size: Size, stacked_resize: bool) -> Tab { terminal_emulator_colors, terminal_emulator_color_codes, (vec![], vec![]), // swap layouts - None, + PathBuf::from("my_default_shell"), debug, arrow_fonts, styled_underlines, explicitly_disable_kitty_keyboard_protocol, + None, ); tab.apply_layout( TiledPaneLayout::default(), @@ -257,11 +258,12 @@ fn create_new_tab_with_layout(size: Size, layout: TiledPaneLayout) -> Tab { terminal_emulator_colors, terminal_emulator_color_codes, (vec![], vec![]), // swap layouts - None, + PathBuf::from("my_default_shell"), debug, arrow_fonts, styled_underlines, explicitly_disable_kitty_keyboard_protocol, + None, ); let mut new_terminal_ids = vec![]; for i in 0..layout.extract_run_instructions().len() { @@ -329,11 +331,12 @@ fn create_new_tab_with_cell_size( terminal_emulator_colors, terminal_emulator_color_codes, (vec![], vec![]), // swap layouts - None, + PathBuf::from("my_default_shell"), debug, arrow_fonts, styled_underlines, explicitly_disable_kitty_keyboard_protocol, + None, ); tab.apply_layout( TiledPaneLayout::default(), diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index 18ed1a0b..bbb416fe 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -261,7 +261,7 @@ fn create_new_screen(size: Size) -> Screen { let copy_options = CopyOptions::default(); let default_layout = Box::new(Layout::default()); let default_layout_name = None; - let default_shell = None; + let default_shell = PathBuf::from("my_default_shell"); let session_serialization = true; let serialize_pane_viewport = false; let scrollback_lines_to_serialize = None; @@ -293,6 +293,7 @@ fn create_new_screen(size: Size) -> Screen { layout_dir, explicitly_disable_kitty_keyboard_protocol, stacked_resize, + None, ); screen } diff --git a/zellij-utils/assets/prost/api.event.rs b/zellij-utils/assets/prost/api.event.rs index 20abfc38..8484cc10 100644 --- a/zellij-utils/assets/prost/api.event.rs +++ b/zellij-utils/assets/prost/api.event.rs @@ -438,6 +438,10 @@ pub struct ModeUpdatePayload { pub session_name: ::core::option::Option<::prost::alloc::string::String>, #[prost(enumeration = "super::input_mode::InputMode", optional, tag = "6")] pub base_mode: ::core::option::Option, + #[prost(string, optional, tag = "7")] + pub editor: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag = "8")] + pub shell: ::core::option::Option<::prost::alloc::string::String>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index 966c87ea..5952d72e 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -1150,6 +1150,8 @@ pub struct ModeInfo { pub style: Style, pub capabilities: PluginCapabilities, pub session_name: Option, + pub editor: Option, + pub shell: Option, } impl ModeInfo { diff --git a/zellij-utils/src/input/mod.rs b/zellij-utils/src/input/mod.rs index aaf466d6..66d02624 100644 --- a/zellij-utils/src/input/mod.rs +++ b/zellij-utils/src/input/mod.rs @@ -43,6 +43,8 @@ mod not_wasm { style: attributes.style, capabilities, session_name, + editor: None, + shell: None, } } diff --git a/zellij-utils/src/plugin_api/event.proto b/zellij-utils/src/plugin_api/event.proto index 86f74bdf..5a55baf0 100644 --- a/zellij-utils/src/plugin_api/event.proto +++ b/zellij-utils/src/plugin_api/event.proto @@ -322,6 +322,8 @@ message ModeUpdatePayload { bool arrow_fonts_support = 4; optional string session_name = 5; optional input_mode.InputMode base_mode = 6; + optional string editor = 7; + optional string shell = 8; } message InputModeKeybinds { diff --git a/zellij-utils/src/plugin_api/event.rs b/zellij-utils/src/plugin_api/event.rs index 9a6829d6..08db3d6e 100644 --- a/zellij-utils/src/plugin_api/event.rs +++ b/zellij-utils/src/plugin_api/event.rs @@ -1157,6 +1157,10 @@ impl TryFrom for ModeInfo { .and_then(|m| m.try_into().ok()) .ok_or("malformed payload for mode_info")?; let session_name = protobuf_mode_update_payload.session_name; + let editor = protobuf_mode_update_payload + .editor + .map(|e| PathBuf::from(e)); + let shell = protobuf_mode_update_payload.shell.map(|s| PathBuf::from(s)); let capabilities = PluginCapabilities { arrow_fonts: protobuf_mode_update_payload.arrow_fonts_support, }; @@ -1167,6 +1171,8 @@ impl TryFrom for ModeInfo { capabilities, session_name, base_mode, + editor, + shell, }; Ok(mode_info) } @@ -1182,6 +1188,8 @@ impl TryFrom for ProtobufModeUpdatePayload { let style: ProtobufStyle = mode_info.style.try_into()?; let arrow_fonts_support: bool = mode_info.capabilities.arrow_fonts; let session_name = mode_info.session_name; + let editor = mode_info.editor.map(|e| e.display().to_string()); + let shell = mode_info.shell.map(|s| s.display().to_string()); let mut protobuf_input_mode_keybinds: Vec = vec![]; for (input_mode, input_mode_keybinds) in mode_info.keybinds { let mode: ProtobufInputMode = input_mode.try_into()?; @@ -1213,6 +1221,8 @@ impl TryFrom for ProtobufModeUpdatePayload { arrow_fonts_support, session_name, base_mode: base_mode.map(|b_m| b_m as i32), + editor, + shell, }) } } @@ -1455,6 +1465,8 @@ fn serialize_mode_update_event_with_non_default_values() { capabilities: PluginCapabilities { arrow_fonts: false }, session_name: Some("my awesome test session".to_owned()), base_mode: Some(InputMode::Locked), + editor: Some(PathBuf::from("my_awesome_editor")), + shell: Some(PathBuf::from("my_awesome_shell")), }); let protobuf_event: ProtobufEvent = mode_update_event.clone().try_into().unwrap(); let serialized_protobuf_event = protobuf_event.encode_to_vec();