diff options
author | Aram Drevekenin <aram@poor.dev> | 2023-09-02 12:27:14 +0200 |
---|---|---|
committer | Aram Drevekenin <aram@poor.dev> | 2023-09-02 12:27:14 +0200 |
commit | 89b8c2ade0d7d7e93e3c903ab763daa2ca106fa4 (patch) | |
tree | 806ab886e4ef8187b793f13f6c7a96520e2d98fa | |
parent | 697723ddd30715e2997ad12352ea3c89ebdb7e17 (diff) |
prototype
-rw-r--r-- | src/main.rs | 4 | ||||
-rw-r--r-- | zellij-client/src/cli_client.rs | 3 | ||||
-rw-r--r-- | zellij-client/src/input_handler.rs | 10 | ||||
-rw-r--r-- | zellij-client/src/lib.rs | 1 | ||||
-rw-r--r-- | zellij-client/src/os_input_output.rs | 5 | ||||
-rw-r--r-- | zellij-server/src/os_input_output.rs | 1 | ||||
-rw-r--r-- | zellij-server/src/plugins/zellij_exports.rs | 5 | ||||
-rw-r--r-- | zellij-server/src/pty.rs | 108 | ||||
-rw-r--r-- | zellij-server/src/route.rs | 46 | ||||
-rw-r--r-- | zellij-server/src/screen.rs | 90 | ||||
-rw-r--r-- | zellij-server/src/tab/mod.rs | 70 | ||||
-rw-r--r-- | zellij-utils/src/cli.rs | 16 | ||||
-rw-r--r-- | zellij-utils/src/errors.rs | 2 | ||||
-rw-r--r-- | zellij-utils/src/input/actions.rs | 18 | ||||
-rw-r--r-- | zellij-utils/src/ipc.rs | 2 | ||||
-rw-r--r-- | zellij-utils/src/plugin_api/action.rs | 5 |
16 files changed, 351 insertions, 35 deletions
diff --git a/src/main.rs b/src/main.rs index f32d76cd8..da059b7fb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,6 +25,7 @@ fn main() { direction, cwd, floating, + in_place, name, close_on_exit, start_suspended, @@ -36,6 +37,7 @@ fn main() { direction, cwd, floating, + in_place, name, close_on_exit, start_suspended, @@ -49,6 +51,7 @@ fn main() { direction, line_number, floating, + in_place, cwd, })) = opts.command { @@ -64,6 +67,7 @@ fn main() { direction, line_number, floating, + in_place, cwd, }; commands::send_action_to_session(command_cli_action, opts.session, config); diff --git a/zellij-client/src/cli_client.rs b/zellij-client/src/cli_client.rs index 197efa141..2a2543adf 100644 --- a/zellij-client/src/cli_client.rs +++ b/zellij-client/src/cli_client.rs @@ -18,8 +18,9 @@ pub fn start_cli_client(os_input: Box<dyn ClientOsApi>, session_name: &str, acti sock_dir }; os_input.connect_to_server(&*zellij_ipc_pipe); + let pane_id = os_input.env_variable("ZELLIJ_PANE_ID").and_then(|e| e.trim().parse().ok()); for action in actions { - let msg = ClientToServerMsg::Action(action, None); + let msg = ClientToServerMsg::Action(action, pane_id, None); os_input.send_to_server(msg); } loop { diff --git a/zellij-client/src/input_handler.rs b/zellij-client/src/input_handler.rs index 2781d3afb..cf5b3d326 100644 --- a/zellij-client/src/input_handler.rs +++ b/zellij-client/src/input_handler.rs @@ -283,13 +283,13 @@ impl InputHandler { Action::NoOp => {}, Action::Quit => { self.os_input - .send_to_server(ClientToServerMsg::Action(action, client_id)); + .send_to_server(ClientToServerMsg::Action(action, None, client_id)); self.exit(ExitReason::Normal); should_break = true; }, Action::Detach => { self.os_input - .send_to_server(ClientToServerMsg::Action(action, client_id)); + .send_to_server(ClientToServerMsg::Action(action, None, client_id)); self.exit(ExitReason::NormalDetached); should_break = true; }, @@ -298,7 +298,7 @@ impl InputHandler { // server later that atomically changes the mode as well self.mode = mode; self.os_input - .send_to_server(ClientToServerMsg::Action(action, None)); + .send_to_server(ClientToServerMsg::Action(action, None, None)); }, Action::CloseFocus | Action::ClearScreen @@ -318,7 +318,7 @@ impl InputHandler { | Action::MoveFocusOrTab(_) => { self.command_is_executing.blocking_input_thread(); self.os_input - .send_to_server(ClientToServerMsg::Action(action, client_id)); + .send_to_server(ClientToServerMsg::Action(action, None, client_id)); self.command_is_executing .wait_until_input_thread_is_unblocked(); }, @@ -333,7 +333,7 @@ impl InputHandler { }, _ => self .os_input - .send_to_server(ClientToServerMsg::Action(action, client_id)), + .send_to_server(ClientToServerMsg::Action(action, None, client_id)), } should_break diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index 6aeb22e5a..a472cd1dc 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -316,6 +316,7 @@ pub fn start_client( os_api.send_to_server(ClientToServerMsg::Action( on_force_close.into(), None, + None, )); } }), diff --git a/zellij-client/src/os_input_output.rs b/zellij-client/src/os_input_output.rs index 212ed2f7d..bfc8cc076 100644 --- a/zellij-client/src/os_input_output.rs +++ b/zellij-client/src/os_input_output.rs @@ -114,6 +114,7 @@ pub trait ClientOsApi: Send + Sync { fn disable_mouse(&self) -> Result<()>; // Repeatedly send action, until stdin is readable again fn stdin_poller(&self) -> StdinPoller; + fn env_variable(&self, _name: &str) -> Option<String> { None } } impl ClientOsApi for ClientOsInputOutput { @@ -282,6 +283,10 @@ impl ClientOsApi for ClientOsInputOutput { fn stdin_poller(&self) -> StdinPoller { StdinPoller::default() } + + fn env_variable(&self, name: &str) -> Option<String> { + std::env::var(name).ok() + } } impl Clone for Box<dyn ClientOsApi> { diff --git a/zellij-server/src/os_input_output.rs b/zellij-server/src/os_input_output.rs index f4cbf9b4a..28cb124c8 100644 --- a/zellij-server/src/os_input_output.rs +++ b/zellij-server/src/os_input_output.rs @@ -183,6 +183,7 @@ fn handle_openpty( } command .args(&cmd.args) + .env("ZELLIJ_PANE_ID", &format!("{}", terminal_id)) .pre_exec(move || -> std::io::Result<()> { if libc::login_tty(pid_secondary) != 0 { panic!("failed to set controlling terminal"); diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs index 6ec50c3a5..908bfeb08 100644 --- a/zellij-server/src/plugins/zellij_exports.rs +++ b/zellij-server/src/plugins/zellij_exports.rs @@ -51,6 +51,7 @@ macro_rules! apply_action { if let Err(e) = route_action( $action, $env.plugin_env.client_id, + Some(PaneId::Plugin($env.plugin_env.plugin_id)), $env.plugin_env.senders.clone(), $env.plugin_env.capabilities.clone(), $env.plugin_env.client_attributes.clone(), @@ -347,12 +348,14 @@ fn get_zellij_version(env: &ForeignFunctionEnv) { fn open_file(env: &ForeignFunctionEnv, file_to_open: FileToOpen) { let error_msg = || format!("failed to open file in plugin {}", env.plugin_env.name()); let floating = false; + let in_place = false; let action = Action::EditFile( file_to_open.path, file_to_open.line_number, file_to_open.cwd, None, floating, + in_place, ); apply_action!(action, error_msg, env); } @@ -360,12 +363,14 @@ fn open_file(env: &ForeignFunctionEnv, file_to_open: FileToOpen) { fn open_file_floating(env: &ForeignFunctionEnv, file_to_open: FileToOpen) { let error_msg = || format!("failed to open file in plugin {}", env.plugin_env.name()); let floating = true; + let in_place = false; let action = Action::EditFile( file_to_open.path, file_to_open.line_number, file_to_open.cwd, None, floating, + in_place, ); apply_action!(action, error_msg, env); } diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs index a785f536d..be363a63f 100644 --- a/zellij-server/src/pty.rs +++ b/zellij-server/src/pty.rs @@ -26,9 +26,10 @@ pub type VteBytes = Vec<u8>; pub type TabIndex = u32; #[derive(Clone, Copy, Debug)] -pub enum ClientOrTabIndex { +pub enum ClientTabIndexOrPaneId { ClientId(ClientId), TabIndex(usize), + PaneId(PaneId), } /// Instructions related to PTYs (pseudoterminals). @@ -38,7 +39,7 @@ pub enum PtyInstruction { Option<TerminalAction>, Option<bool>, Option<String>, - ClientOrTabIndex, + ClientTabIndexOrPaneId, ), // bool (if Some) is // should_float, String is an optional pane name OpenInPlaceEditor(PathBuf, Option<usize>, ClientId), // Option<usize> is the optional line number @@ -62,6 +63,11 @@ pub enum PtyInstruction { ClosePane(PaneId), CloseTab(Vec<PaneId>), ReRunCommandInPane(PaneId, RunCommand), + SpawnInPlaceTerminal( + Option<TerminalAction>, + Option<String>, + ClientTabIndexOrPaneId, + ), // String is an optional pane name Exit, } @@ -78,6 +84,7 @@ impl From<&PtyInstruction> for PtyContext { PtyInstruction::CloseTab(_) => PtyContext::CloseTab, PtyInstruction::NewTab(..) => PtyContext::NewTab, PtyInstruction::ReRunCommandInPane(..) => PtyContext::ReRunCommandInPane, + PtyInstruction::SpawnInPlaceTerminal(..) => PtyContext::SpawnInPlaceTerminal, PtyInstruction::Exit => PtyContext::Exit, } } @@ -164,13 +171,76 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> { }, } }, + PtyInstruction::SpawnInPlaceTerminal( + terminal_action, + name, + client_id_tab_index_or_pane_id, + ) => { + let err_context = + || format!("failed to spawn terminal for {:?}", client_id_tab_index_or_pane_id); + let (hold_on_close, run_command, pane_title) = match &terminal_action { + Some(TerminalAction::RunCommand(run_command)) => ( + run_command.hold_on_close, + Some(run_command.clone()), + Some(name.unwrap_or_else(|| run_command.to_string())), + ), + _ => (false, None, name), + }; + match pty + .spawn_terminal(terminal_action, client_id_tab_index_or_pane_id) + .with_context(err_context) + { + Ok((pid, starts_held)) => { + let hold_for_command = if starts_held { run_command } else { None }; + pty.bus + .senders + .send_to_screen(ScreenInstruction::ReplacePane( + PaneId::Terminal(pid), + hold_for_command, + pane_title, + client_id_tab_index_or_pane_id, + )) + .with_context(err_context)?; + }, + Err(err) => match err.downcast_ref::<ZellijError>() { + Some(ZellijError::CommandNotFound { terminal_id, .. }) => { + if hold_on_close { + let hold_for_command = None; // we do not hold an "error" pane + pty.bus + .senders + .send_to_screen(ScreenInstruction::ReplacePane( + PaneId::Terminal(*terminal_id), + hold_for_command, + pane_title, + client_id_tab_index_or_pane_id, + )) + .with_context(err_context)?; + if let Some(run_command) = run_command { + send_command_not_found_to_screen( + pty.bus.senders.clone(), + *terminal_id, + run_command.clone(), + None, + ) + .with_context(err_context)?; + } + } else { + log::error!("Failed to spawn terminal: {:?}", err); + pty.close_pane(PaneId::Terminal(*terminal_id)) + .with_context(err_context)?; + } + }, + _ => Err::<(), _>(err).non_fatal(), + }, + } + }, PtyInstruction::OpenInPlaceEditor(temp_file, line_number, client_id) => { let err_context = || format!("failed to open in-place editor for client {}", client_id); match pty.spawn_terminal( Some(TerminalAction::OpenFile(temp_file, line_number, None)), - ClientOrTabIndex::ClientId(client_id), + ClientTabIndexOrPaneId::ClientId(client_id), ) { Ok((pid, _starts_held)) => { pty.bus @@ -199,7 +269,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> { _ => (false, None, name), }; match pty - .spawn_terminal(terminal_action, ClientOrTabIndex::ClientId(client_id)) + .spawn_terminal(terminal_action, ClientTabIndexOrPaneId::ClientId(client_id)) .with_context(err_context) { Ok((pid, starts_held)) => { @@ -270,7 +340,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> { _ => (false, None, name), }; match pty - .spawn_terminal(terminal_action, ClientOrTabIndex::ClientId(client_id)) + .spawn_terminal(terminal_action, ClientTabIndexOrPaneId::ClientId(client_id)) .with_context(err_context) { Ok((pid, starts_held)) => { @@ -501,25 +571,47 @@ impl Pty { }; }; } + fn fill_cwd_from_pane_id(&self, terminal_action: &mut TerminalAction, pane_id: &u32) { + if let TerminalAction::RunCommand(run_command) = terminal_action { + if run_command.cwd.is_none() { + run_command.cwd = self + .id_to_child_pid.get(pane_id) + .and_then(|&id| { + self.bus + .os_input + .as_ref() + .and_then(|input| input.get_cwd(Pid::from_raw(id))) + }); + }; + }; + } pub fn spawn_terminal( &mut self, terminal_action: Option<TerminalAction>, - client_or_tab_index: ClientOrTabIndex, + client_or_tab_index: ClientTabIndexOrPaneId, ) -> Result<(u32, bool)> { // bool is starts_held let err_context = || format!("failed to spawn terminal for {:?}", client_or_tab_index); // returns the terminal id let terminal_action = match client_or_tab_index { - ClientOrTabIndex::ClientId(client_id) => { + ClientTabIndexOrPaneId::ClientId(client_id) => { let mut terminal_action = terminal_action.unwrap_or_else(|| self.get_default_terminal(None, None)); self.fill_cwd(&mut terminal_action, client_id); terminal_action }, - ClientOrTabIndex::TabIndex(_) => { + ClientTabIndexOrPaneId::TabIndex(_) => { terminal_action.unwrap_or_else(|| self.get_default_terminal(None, None)) }, + ClientTabIndexOrPaneId::PaneId(pane_id) => { + let mut terminal_action = + terminal_action.unwrap_or_else(|| self.get_default_terminal(None, None)); + if let PaneId::Terminal(terminal_pane_id) = pane_id { + self.fill_cwd_from_pane_id(&mut terminal_action, &terminal_pane_id); + } + terminal_action + } }; let (hold_on_start, hold_on_close) = match &terminal_action { TerminalAction::RunCommand(run_command) => { diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index b29dd934f..54489584d 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -6,7 +6,7 @@ use crate::{ os_input_output::ServerOsApi, panes::PaneId, plugins::PluginInstruction, - pty::{ClientOrTabIndex, PtyInstruction}, + pty::{ClientTabIndexOrPaneId, PtyInstruction}, screen::ScreenInstruction, ServerInstruction, SessionMetaData, SessionState, }; @@ -30,6 +30,7 @@ use crate::ClientId; pub(crate) fn route_action( action: Action, client_id: ClientId, + pane_id: Option<PaneId>, senders: ThreadSenders, capabilities: PluginCapabilities, client_attributes: ClientAttributes, @@ -257,12 +258,12 @@ pub(crate) fn route_action( shell, None, name, - ClientOrTabIndex::ClientId(client_id), + ClientTabIndexOrPaneId::ClientId(client_id), ), }; senders.send_to_pty(pty_instr).with_context(err_context)?; }, - Action::EditFile(path_to_file, line_number, cwd, split_direction, should_float) => { + Action::EditFile(path_to_file, line_number, cwd, split_direction, should_float, should_open_in_place) => { let title = format!("Editing: {}", path_to_file.display()); let open_file = TerminalAction::OpenFile(path_to_file, line_number, cwd); let pty_instr = match (split_direction, should_float) { @@ -287,7 +288,7 @@ pub(crate) fn route_action( Some(open_file), Some(should_float), Some(title), - ClientOrTabIndex::ClientId(client_id), + ClientTabIndexOrPaneId::ClientId(client_id), ), }; senders.send_to_pty(pty_instr).with_context(err_context)?; @@ -319,10 +320,38 @@ pub(crate) fn route_action( run_cmd, Some(should_float), name, - ClientOrTabIndex::ClientId(client_id), + ClientTabIndexOrPaneId::ClientId(client_id), )) .with_context(err_context)?; }, + Action::NewInPlacePane(run_command, name) => { + let run_cmd = run_command + .map(|cmd| TerminalAction::RunCommand(cmd.into())) + .or_else(|| default_shell.clone()); + match pane_id { + Some(pane_id) => { + senders + .send_to_pty(PtyInstruction::SpawnInPlaceTerminal( + run_cmd, + name, + ClientTabIndexOrPaneId::PaneId(pane_id), + )) + .with_context(err_context)?; + }, + None => { + senders + .send_to_pty(PtyInstruction::SpawnInPlaceTerminal( + run_cmd, + name, + ClientTabIndexOrPaneId::ClientId(client_id) + )) + .with_context(err_context)?; + }, + _ => { + log::error!("To open an in place editor, we must have either a pane id or a client id") + } + } + }, Action::NewTiledPane(direction, run_command, name) => { let should_float = false; let run_cmd = run_command @@ -346,7 +375,7 @@ pub(crate) fn route_action( run_cmd, Some(should_float), name, - ClientOrTabIndex::ClientId(client_id), + ClientTabIndexOrPaneId::ClientId(client_id), ), }; senders.send_to_pty(pty_instr).with_context(err_context)?; @@ -394,7 +423,7 @@ pub(crate) fn route_action( run_cmd, None, None, - ClientOrTabIndex::ClientId(client_id), + ClientTabIndexOrPaneId::ClientId(client_id), ), }; senders.send_to_pty(pty_instr).with_context(err_context)?; @@ -752,7 +781,7 @@ pub(crate) fn route_thread_main( -> Result<bool> { let mut should_break = false; match instruction { - ClientToServerMsg::Action(action, maybe_client_id) => { + ClientToServerMsg::Action(action, maybe_pane_id, maybe_client_id) => { let client_id = maybe_client_id.unwrap_or(client_id); if let Some(rlocked_sessions) = rlocked_sessions.as_ref() { if let Action::SwitchToMode(input_mode) = action { @@ -769,6 +798,7 @@ pub(crate) fn route_thread_main( if route_action( action, client_id, + maybe_pane_id.map(|p| PaneId::Terminal(p)), rlocked_sessions.senders.clone(), rlocked_sessions.capabilities.clone(), rlocked_sessions.client_attributes.clone(), diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index efa9110b8..b4832541f 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -32,7 +32,7 @@ use crate::{ panes::sixel::SixelImageStore, panes::PaneId, plugins::PluginInstruction, - pty::{ClientOrTabIndex, PtyInstruction, VteBytes}, + pty::{ClientTabIndexOrPaneId, PtyInstruction, VteBytes}, tab::Tab, thread_bus::Bus, ui::{ @@ -141,7 +141,7 @@ pub enum ScreenInstruction { Option<InitialTitle>, Option<ShouldFloat>, HoldForCommand, - ClientOrTabIndex, + ClientTabIndexOrPaneId, ), OpenInPlaceEditor(PaneId, ClientId), TogglePaneEmbedOrFloating(ClientId), @@ -294,6 +294,12 @@ pub enum ScreenInstruction { BreakPaneRight(ClientId), BreakPaneLeft(ClientId), UpdateSessionInfos(BTreeMap<String, SessionInfo>), // String is the session name + ReplacePane( + PaneId, + HoldForCommand, + Option<InitialTitle>, + ClientTabIndexOrPaneId + ), } impl From<&ScreenInstruction> for ScreenContext { @@ -468,6 +474,7 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::BreakPaneRight(..) => ScreenContext::BreakPaneRight, ScreenInstruction::BreakPaneLeft(..) => ScreenContext::BreakPaneLeft, ScreenInstruction::UpdateSessionInfos(..) => ScreenContext::UpdateSessionInfos, + ScreenInstruction::ReplacePane(..) => ScreenContext::ReplacePane, } } } @@ -1822,7 +1829,66 @@ impl Screen { self.render()?; Ok(()) } - + pub fn replace_pane( + &mut self, + new_pane_id: PaneId, + hold_for_command: HoldForCommand, + pane_title: Option<InitialTitle>, + client_id_tab_index_or_pane_id: ClientTabIndexOrPaneId + ) -> Result<()> { + let err_context = || format!("failed to replace pane"); + let suppress_pane = |tab: &mut Tab, pane_id: PaneId, new_pane_id: PaneId| { + tab.suppress_pane_and_replace_with_pid(pane_id, new_pane_id); + if let Some(pane_title) = pane_title { + tab.rename_pane(pane_title.as_bytes().to_vec(), new_pane_id); + } + if let Some(hold_for_command) = hold_for_command { + let is_first_run = true; + tab.hold_pane( + new_pane_id, + None, + is_first_run, + hold_for_command + ) + } + }; + match client_id_tab_index_ |