From 4f94f95c90a0b5c2cb3992533cec40cc55f05983 Mon Sep 17 00:00:00 2001 From: spacemaison Date: Fri, 10 Sep 2021 08:35:06 -0700 Subject: [PATCH] feat(cwd-pane): Keeping the cwd when opening new panes (#691) * feat(cwd-pane): Add a new trait to get the cwd of a given pid Co-authored-by: Quentin Rasmont * feat(cwd-pane): Allow for setting the cwd when spawning a new terminal Co-authored-by: Quentin Rasmont * feat(cwd-pane): Add an active_pane field to the Pty struct Co-authored-by: Quentin Rasmont * feat(cwd-pane): Update Pty with Tab's active pane id Co-authored-by: Quentin Rasmont * feat(cwd-pane): Refactor spawn_terminal to use cwd by default Co-authored-by: Quentin Rasmont * feat(cwd-pane): Fix tests and lints Co-authored-by: Quentin Rasmont * feat(cwd-pane): Fix formatting * feat(cwd-pane): Refactor child pid fetching to handle errors better Instead of panicking when transfering the process id of the forked child command we just return an empty process id. * feat(cwd-pane): Add non Linux/MacOS targets for the get_cwd method. This will allow Zellij to compile on non Linux/MacOS targets without having an inherited cwd. * feat(cwd-pane): Refactor spawn_terminal method to use ChildId struct. The spawn_terminal methods been refactored to use the ChildId struct in order to clarify what the Pid's returned by it are. The documentation for the ChildId struct was improved as well. * feat(cwd-pane): Fix tests/lints Co-authored-by: Jesse Tuchsen Co-authored-by: Quentin Rasmont --- Cargo.lock | 57 +++++--- zellij-server/Cargo.toml | 4 + zellij-server/src/os_input_output.rs | 177 ++++++++++++++++++------- zellij-server/src/pty.rs | 63 ++++++--- zellij-server/src/tab.rs | 115 ++++++++-------- zellij-server/src/unit/screen_tests.rs | 8 +- zellij-server/src/unit/tab_tests.rs | 8 +- zellij-utils/src/errors.rs | 1 + zellij-utils/src/input/command.rs | 5 + 9 files changed, 295 insertions(+), 143 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6c2cdcd3e..7bd709ac9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,11 +4,11 @@ version = 3 [[package]] name = "addr2line" -version = "0.16.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd" +checksum = "e7a2e47a1fbe209ee101dd6d61285226744c6c8d3c21c8dc878ba6cb9f467f3a" dependencies = [ - "gimli 0.25.0", + "gimli 0.24.0", ] [[package]] @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" dependencies = [ "memchr", ] @@ -227,16 +227,16 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" -version = "0.3.61" +version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a905d892734eea339e896738c14b9afce22b5318f64b951e70bf3844419b01" +checksum = "4717cfcbfaa661a0fd48f8453951837ae7e8f81e481fbb136e3202d72805a744" dependencies = [ "addr2line", "cc", "cfg-if 1.0.0", "libc", "miniz_oxide", - "object 0.26.0", + "object 0.24.0", "rustc-demangle", ] @@ -621,6 +621,26 @@ dependencies = [ "syn", ] +[[package]] +name = "darwin-libproc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc629b7cf42586fee31dae31f9ab73fa5ff5f0170016aa61be5fcbc12a90c516" +dependencies = [ + "darwin-libproc-sys", + "libc", + "memchr", +] + +[[package]] +name = "darwin-libproc-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef0aa083b94c54aa4cfd9bbfd37856714c139d1dc511af80270558c7ba3b4816" +dependencies = [ + "libc", +] + [[package]] name = "derivative" version = "2.2.0" @@ -897,9 +917,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.25.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" +checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189" [[package]] name = "gloo-timers" @@ -1188,9 +1208,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.4.0" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" [[package]] name = "memmap2" @@ -1325,12 +1345,9 @@ dependencies = [ [[package]] name = "object" -version = "0.26.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55827317fb4c08822499848a14237d2874d6f139828893017237e7ab93eb386" -dependencies = [ - "memchr", -] +checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170" [[package]] name = "once_cell" @@ -1675,9 +1692,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.4" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759" dependencies = [ "aho-corasick", "memchr", @@ -2652,9 +2669,11 @@ dependencies = [ "ansi_term 0.12.1", "async-trait", "base64", + "byteorder", "cassowary", "chrono", "daemonize", + "darwin-libproc", "insta", "log", "serde_json", diff --git a/zellij-server/Cargo.toml b/zellij-server/Cargo.toml index fe56b7424..1c8c986b6 100644 --- a/zellij-server/Cargo.toml +++ b/zellij-server/Cargo.toml @@ -12,6 +12,7 @@ license = "MIT" ansi_term = "0.12.1" async-trait = "0.1.50" base64 = "0.13.0" +byteorder = "1.4.3" daemonize = "0.4.1" serde_json = "1.0" unicode-width = "0.1.8" @@ -23,6 +24,9 @@ log = "0.4.14" typetag = "0.1.7" chrono = "0.4.19" +[target.'cfg(target_os = "macos")'.dependencies] +darwin-libproc = "0.2.0" + [dev-dependencies] insta = "1.6.0" diff --git a/zellij-server/src/os_input_output.rs b/zellij-server/src/os_input_output.rs index 05980805f..e24b24b41 100644 --- a/zellij-server/src/os_input_output.rs +++ b/zellij-server/src/os_input_output.rs @@ -1,4 +1,8 @@ +#[cfg(target_os = "macos")] +use darwin_libproc; + use std::env; +use std::fs; use std::os::unix::io::RawFd; use std::os::unix::process::CommandExt; use std::path::PathBuf; @@ -10,7 +14,7 @@ use zellij_utils::{async_std, interprocess, libc, nix, signal_hook, zellij_tile} use async_std::fs::File as AsyncFile; use async_std::os::unix::io::FromRawFd; use interprocess::local_socket::LocalSocketStream; -use nix::pty::{forkpty, Winsize}; +use nix::pty::{forkpty, ForkptyResult, Winsize}; use nix::sys::signal::{kill, Signal}; use nix::sys::termios; use nix::sys::wait::waitpid; @@ -29,6 +33,7 @@ use zellij_utils::{ use async_std::io::ReadExt; pub use async_trait::async_trait; +use byteorder::{BigEndian, ByteOrder}; pub use nix::unistd::Pid; @@ -92,44 +97,94 @@ fn handle_command_exit(mut child: Child) { } } +fn handle_fork_pty( + fork_pty_res: ForkptyResult, + cmd: RunCommand, + parent_fd: RawFd, + child_fd: RawFd, +) -> (RawFd, ChildId) { + let pid_primary = fork_pty_res.master; + let (pid_secondary, pid_shell) = match fork_pty_res.fork_result { + ForkResult::Parent { child } => { + let pid_shell = read_from_pipe(parent_fd, child_fd); + (child, pid_shell) + } + ForkResult::Child => { + let child = unsafe { + let command = &mut Command::new(cmd.command); + if let Some(current_dir) = cmd.cwd { + command.current_dir(current_dir); + } + command + .args(&cmd.args) + .pre_exec(|| -> std::io::Result<()> { + // this is the "unsafe" part, for more details please see: + // https://doc.rust-lang.org/std/os/unix/process/trait.CommandExt.html#notes-and-safety + unistd::setpgid(Pid::from_raw(0), Pid::from_raw(0)) + .expect("failed to create a new process group"); + Ok(()) + }) + .spawn() + .expect("failed to spawn") + }; + unistd::tcsetpgrp(0, Pid::from_raw(child.id() as i32)) + .expect("faled to set child's forceground process group"); + write_to_pipe(child.id(), parent_fd, child_fd); + handle_command_exit(child); + ::std::process::exit(0); + } + }; + + ( + pid_primary, + ChildId { + primary: pid_secondary, + shell: pid_shell.map(|pid| Pid::from_raw(pid as i32)), + }, + ) +} + /// Spawns a new terminal from the parent terminal with [`termios`](termios::Termios) /// `orig_termios`. /// -fn handle_terminal(cmd: RunCommand, orig_termios: termios::Termios) -> (RawFd, Pid) { - let (pid_primary, pid_secondary): (RawFd, Pid) = { - match forkpty(None, Some(&orig_termios)) { - Ok(fork_pty_res) => { - let pid_primary = fork_pty_res.master; - let pid_secondary = match fork_pty_res.fork_result { - ForkResult::Parent { child } => child, - ForkResult::Child => { - let child = unsafe { - Command::new(cmd.command) - .args(&cmd.args) - .pre_exec(|| -> std::io::Result<()> { - // this is the "unsafe" part, for more details please see: - // https://doc.rust-lang.org/std/os/unix/process/trait.CommandExt.html#notes-and-safety - unistd::setpgid(Pid::from_raw(0), Pid::from_raw(0)) - .expect("failed to create a new process group"); - Ok(()) - }) - .spawn() - .expect("failed to spawn") - }; - unistd::tcsetpgrp(0, Pid::from_raw(child.id() as i32)) - .expect("faled to set child's forceground process group"); - handle_command_exit(child); - ::std::process::exit(0); - } - }; - (pid_primary, pid_secondary) - } - Err(e) => { - panic!("failed to fork {:?}", e); - } +fn handle_terminal(cmd: RunCommand, orig_termios: termios::Termios) -> (RawFd, ChildId) { + // Create a pipe to allow the child the communicate the shell's pid to it's + // parent. + let (parent_fd, child_fd) = unistd::pipe().expect("failed to create pipe"); + match forkpty(None, Some(&orig_termios)) { + Ok(fork_pty_res) => handle_fork_pty(fork_pty_res, cmd, parent_fd, child_fd), + Err(e) => { + panic!("failed to fork {:?}", e); } - }; - (pid_primary, pid_secondary) + } +} + +/// Write to a pipe given both file descriptors +fn write_to_pipe(data: u32, parent_fd: RawFd, child_fd: RawFd) { + let mut buff = [0; 4]; + BigEndian::write_u32(&mut buff, data); + if unistd::close(parent_fd).is_err() { + return; + } + if unistd::write(child_fd, &buff).is_err() { + return; + } + unistd::close(child_fd).unwrap_or_default(); +} + +/// Read from a pipe given both file descriptors +fn read_from_pipe(parent_fd: RawFd, child_fd: RawFd) -> Option { + let mut buffer = [0; 4]; + if unistd::close(child_fd).is_err() { + return None; + } + if unistd::read(parent_fd, &mut buffer).is_err() { + return None; + } + if unistd::close(parent_fd).is_err() { + return None; + } + Some(u32::from_be_bytes(buffer)) } /// If a [`TerminalAction::OpenFile(file)`] is given, the text editor specified by environment variable `EDITOR` @@ -145,11 +200,11 @@ fn handle_terminal(cmd: RunCommand, orig_termios: termios::Termios) -> (RawFd, P /// This function will panic if both the `EDITOR` and `VISUAL` environment variables are not /// set. pub fn spawn_terminal( - terminal_action: Option, + terminal_action: TerminalAction, orig_termios: termios::Termios, -) -> (RawFd, Pid) { +) -> (RawFd, ChildId) { let cmd = match terminal_action { - Some(TerminalAction::OpenFile(file_to_open)) => { + TerminalAction::OpenFile(file_to_open) => { if env::var("EDITOR").is_err() && env::var("VISUAL").is_err() { panic!("Can't edit files if an editor is not defined. To fix: define the EDITOR or VISUAL environment variables with the path to your editor (eg. /usr/bin/vim)"); } @@ -160,15 +215,13 @@ pub fn spawn_terminal( .into_os_string() .into_string() .expect("Not valid Utf8 Encoding")]; - RunCommand { command, args } - } - Some(TerminalAction::RunCommand(command)) => command, - None => { - let command = - PathBuf::from(env::var("SHELL").expect("Could not find the SHELL variable")); - let args = vec![]; - RunCommand { command, args } + RunCommand { + command, + args, + cwd: None, + } } + TerminalAction::RunCommand(command) => command, }; handle_terminal(cmd, orig_termios) @@ -214,8 +267,10 @@ impl AsyncReader for RawFdAsyncReader { pub trait ServerOsApi: Send + Sync { /// Sets the size of the terminal associated to file descriptor `fd`. fn set_terminal_size_using_fd(&self, fd: RawFd, cols: u16, rows: u16); - /// Spawn a new terminal, with a terminal action. - fn spawn_terminal(&self, terminal_action: Option) -> (RawFd, Pid); + /// Spawn a new terminal, with a terminal action. The returned tuple contains the master file + /// descriptor of the forked psuedo terminal and a [ChildId] struct containing process id's for + /// the forked child process. + fn spawn_terminal(&self, terminal_action: TerminalAction) -> (RawFd, ChildId); /// Read bytes from the standard output of the virtual terminal referred to by `fd`. fn read_from_tty_stdout(&self, fd: RawFd, buf: &mut [u8]) -> Result; /// Creates an `AsyncReader` that can be used to read from `fd` in an async context @@ -247,6 +302,8 @@ pub trait ServerOsApi: Send + Sync { /// Update the receiver socket for the client fn update_receiver(&mut self, stream: LocalSocketStream); fn load_palette(&self) -> Palette; + /// Returns the current working directory for a given pid + fn get_cwd(&self, pid: Pid) -> Option; } impl ServerOsApi for ServerOsInputOutput { @@ -255,7 +312,7 @@ impl ServerOsApi for ServerOsInputOutput { set_terminal_size_using_fd(fd, cols, rows); } } - fn spawn_terminal(&self, terminal_action: Option) -> (RawFd, Pid) { + fn spawn_terminal(&self, terminal_action: TerminalAction) -> (RawFd, ChildId) { let orig_termios = self.orig_termios.lock().unwrap(); spawn_terminal(terminal_action, orig_termios.clone()) } @@ -336,6 +393,18 @@ impl ServerOsApi for ServerOsInputOutput { fn load_palette(&self) -> Palette { default_palette() } + #[cfg(target_os = "macos")] + fn get_cwd(&self, pid: Pid) -> Option { + darwin_libproc::pid_cwd(pid.as_raw()).ok() + } + #[cfg(target_os = "linux")] + fn get_cwd(&self, pid: Pid) -> Option { + fs::read_link(format!("/proc/{}/cwd", pid)).ok() + } + #[cfg(all(not(target_os = "linux"), not(target_os = "macos")))] + fn get_cwd(&self, _pid: Pid) -> Option { + None + } } impl Clone for Box { @@ -353,3 +422,13 @@ pub fn get_server_os_input() -> Result { send_instructions_to_client: Arc::new(Mutex::new(None)), }) } + +/// Process id's for forked terminals +#[derive(Debug)] +pub struct ChildId { + /// Primary process id of a forked terminal + pub primary: Pid, + /// Process id of the command running inside the forked terminal, usually a shell. The primary + /// field is it's parent process id. + pub shell: Option, +} diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs index e28729b72..c68572580 100644 --- a/zellij-server/src/pty.rs +++ b/zellij-server/src/pty.rs @@ -1,5 +1,5 @@ use crate::{ - os_input_output::{AsyncReader, Pid, ServerOsApi}, + os_input_output::{AsyncReader, ChildId, ServerOsApi}, panes::PaneId, screen::ScreenInstruction, thread_bus::{Bus, ThreadSenders}, @@ -12,14 +12,16 @@ use async_std::{ }; use std::{ collections::HashMap, + env, os::unix::io::RawFd, + path::PathBuf, time::{Duration, Instant}, }; use zellij_utils::{ async_std, errors::{get_current_ctx, ContextType, PtyContext}, input::{ - command::TerminalAction, + command::{RunCommand, TerminalAction}, layout::{Layout, LayoutFromYaml, Run, TabLayout}, }, logging::debug_to_file, @@ -33,6 +35,7 @@ pub(crate) enum PtyInstruction { SpawnTerminal(Option), SpawnTerminalVertically(Option), SpawnTerminalHorizontally(Option), + UpdateActivePane(Option), NewTab(Option, Option), ClosePane(PaneId), CloseTab(Vec), @@ -45,6 +48,7 @@ impl From<&PtyInstruction> for PtyContext { PtyInstruction::SpawnTerminal(_) => PtyContext::SpawnTerminal, PtyInstruction::SpawnTerminalVertically(_) => PtyContext::SpawnTerminalVertically, PtyInstruction::SpawnTerminalHorizontally(_) => PtyContext::SpawnTerminalHorizontally, + PtyInstruction::UpdateActivePane(_) => PtyContext::UpdateActivePane, PtyInstruction::ClosePane(_) => PtyContext::ClosePane, PtyInstruction::CloseTab(_) => PtyContext::CloseTab, PtyInstruction::NewTab(..) => PtyContext::NewTab, @@ -54,8 +58,9 @@ impl From<&PtyInstruction> for PtyContext { } pub(crate) struct Pty { + pub active_pane: Option, pub bus: Bus, - pub id_to_child_pid: HashMap, + pub id_to_child_pid: HashMap, debug_to_file: bool, task_handles: HashMap>, } @@ -86,6 +91,9 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: LayoutFromYaml) { .send_to_screen(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid))) .unwrap(); } + PtyInstruction::UpdateActivePane(pane_id) => { + pty.set_active_pane(pane_id); + } PtyInstruction::NewTab(terminal_action, tab_layout) => { let merged_layout = layout.template.clone().insert_tab_layout(tab_layout); pty.spawn_terminals_for_layout(merged_layout.into(), terminal_action.clone()); @@ -208,14 +216,30 @@ fn stream_terminal_bytes( impl Pty { pub fn new(bus: Bus, debug_to_file: bool) -> Self { Pty { + active_pane: None, bus, id_to_child_pid: HashMap::new(), debug_to_file, task_handles: HashMap::new(), } } + pub fn get_default_terminal(&self) -> TerminalAction { + TerminalAction::RunCommand(RunCommand { + args: vec![], + command: PathBuf::from(env::var("SHELL").expect("Could not find the SHELL variable")), + cwd: self + .active_pane + .and_then(|pane| match pane { + PaneId::Plugin(..) => None, + PaneId::Terminal(id) => self.id_to_child_pid.get(&id).and_then(|id| id.shell), + }) + .and_then(|id| self.bus.os_input.as_ref().map(|input| input.get_cwd(id))) + .flatten(), + }) + } pub fn spawn_terminal(&mut self, terminal_action: Option) -> RawFd { - let (pid_primary, pid_secondary): (RawFd, Pid) = self + let terminal_action = terminal_action.unwrap_or_else(|| self.get_default_terminal()); + let (pid_primary, child_id): (RawFd, ChildId) = self .bus .os_input .as_mut() @@ -228,7 +252,7 @@ impl Pty { self.debug_to_file, ); self.task_handles.insert(pid_primary, task_handle); - self.id_to_child_pid.insert(pid_primary, pid_secondary); + self.id_to_child_pid.insert(pid_primary, child_id); pid_primary } pub fn spawn_terminals_for_layout( @@ -236,29 +260,26 @@ impl Pty { layout: Layout, default_shell: Option, ) { + let default_shell = default_shell.unwrap_or_else(|| self.get_default_terminal()); let extracted_run_instructions = layout.extract_run_instructions(); let mut new_pane_pids = vec![]; for run_instruction in extracted_run_instructions { match run_instruction { Some(Run::Command(command)) => { let cmd = TerminalAction::RunCommand(command); - let (pid_primary, pid_secondary): (RawFd, Pid) = self - .bus - .os_input - .as_mut() - .unwrap() - .spawn_terminal(Some(cmd)); - self.id_to_child_pid.insert(pid_primary, pid_secondary); + let (pid_primary, child_id): (RawFd, ChildId) = + self.bus.os_input.as_mut().unwrap().spawn_terminal(cmd); + self.id_to_child_pid.insert(pid_primary, child_id); new_pane_pids.push(pid_primary); } None => { - let (pid_primary, pid_secondary): (RawFd, Pid) = self + let (pid_primary, child_id): (RawFd, ChildId) = self .bus .os_input .as_mut() .unwrap() .spawn_terminal(default_shell.clone()); - self.id_to_child_pid.insert(pid_primary, pid_secondary); + self.id_to_child_pid.insert(pid_primary, child_id); new_pane_pids.push(pid_primary); } // Investigate moving plugin loading to here. @@ -285,10 +306,15 @@ impl Pty { pub fn close_pane(&mut self, id: PaneId) { match id { PaneId::Terminal(id) => { - let child_pid = self.id_to_child_pid.remove(&id).unwrap(); + let pids = self.id_to_child_pid.remove(&id).unwrap(); let handle = self.task_handles.remove(&id).unwrap(); task::block_on(async { - self.bus.os_input.as_mut().unwrap().kill(child_pid).unwrap(); + self.bus + .os_input + .as_mut() + .unwrap() + .kill(pids.primary) + .unwrap(); let timeout = Duration::from_millis(100); match async_timeout(timeout, handle.cancel()).await { Ok(_) => {} @@ -297,7 +323,7 @@ impl Pty { .os_input .as_mut() .unwrap() - .force_kill(child_pid) + .force_kill(pids.primary) .unwrap(); } }; @@ -315,6 +341,9 @@ impl Pty { self.close_pane(id); }); } + pub fn set_active_pane(&mut self, pane_id: Option) { + self.active_pane = pane_id; + } } impl Drop for Pty { diff --git a/zellij-server/src/tab.rs b/zellij-server/src/tab.rs index ba8a8c88b..dafb28639 100644 --- a/zellij-server/src/tab.rs +++ b/zellij-server/src/tab.rs @@ -295,6 +295,13 @@ impl Tab { } } + fn set_active_terminal(&mut self, pane_id: Option) { + self.active_terminal = pane_id; + self.senders + .send_to_pty(PtyInstruction::UpdateActivePane(self.active_terminal)) + .unwrap(); + } + pub fn apply_layout(&mut self, layout: Layout, new_pids: Vec, tab_index: usize) { // TODO: this should be an attribute on Screen instead of full_screen_ws let free_space = PaneGeom::default(); @@ -393,7 +400,7 @@ impl Tab { self.set_pane_frames(self.draw_pane_frames); // This is the end of the nasty viewport hack... // FIXME: Active / new / current terminal, should be pane - self.active_terminal = self.panes.iter().map(|(id, _)| id.to_owned()).next(); + self.set_active_terminal(self.panes.iter().map(|(id, _)| id.to_owned()).next()); self.render(); } pub fn new_pane(&mut self, pid: PaneId) { @@ -466,7 +473,7 @@ impl Tab { } } } - self.active_terminal = Some(pid); + self.set_active_terminal(Some(pid)); self.render(); } pub fn horizontal_split(&mut self, pid: PaneId) { @@ -495,7 +502,7 @@ impl Tab { ); active_pane.set_geom(top_winsize); self.panes.insert(pid, Box::new(new_terminal)); - self.active_terminal = Some(pid); + self.set_active_terminal(Some(pid)); self.relayout_tab(Direction::Vertical); self.render(); } @@ -524,7 +531,7 @@ impl Tab { active_pane.set_geom(left_winsize); self.panes.insert(pid, Box::new(new_terminal)); } - self.active_terminal = Some(pid); + self.set_active_terminal(Some(pid)); self.relayout_tab(Direction::Horizontal); self.render(); } @@ -1757,16 +1764,16 @@ impl Tab { } let active_terminal_id = self.get_active_pane_id().unwrap(); let terminal_ids: Vec = self.get_selectable_panes().map(|(&pid, _)| pid).collect(); // TODO: better, no allocations - let first_terminal = terminal_ids.get(0).unwrap(); let active_terminal_id_position = terminal_ids .iter() .position(|id| id == &active_terminal_id) .unwrap(); - if let Some(next_terminal) = terminal_ids.get(active_terminal_id_position + 1) { - self.active_terminal = Some(*next_terminal); - } else { - self.active_terminal = Some(*first_terminal); - } + let active_terminal = terminal_ids + .get(active_terminal_id_position + 1) + .or_else(|| terminal_ids.get(0)) + .copied(); + + self.set_active_terminal(active_terminal); self.render(); } pub fn focus_next_pane(&mut self) { @@ -1785,16 +1792,17 @@ impl Tab { a_pane.y().cmp(&b_pane.y()) } }); - let first_pane = panes.get(0).unwrap(); let active_pane_position = panes .iter() .position(|(id, _)| *id == &active_pane_id) // TODO: better .unwrap(); - if let Some(next_pane) = panes.get(active_pane_position + 1) { - self.active_terminal = Some(*next_pane.0); - } else { - self.active_terminal = Some(*first_pane.0); - } + + let active_terminal = panes + .get(active_pane_position + 1) + .or_else(|| panes.get(0)) + .map(|p| *p.0); + + self.set_active_terminal(active_terminal); self.render(); } pub fn focus_previous_pane(&mut self) { @@ -1818,11 +1826,13 @@ impl Tab { .iter() .position(|(id, _)| *id == &active_pane_id) // TODO: better .unwrap(); - if active_pane_position == 0 { - self.active_terminal = Some(*last_pane.0); + + let active_terminal = if active_pane_position == 0 { + Some(*last_pane.0) } else { - self.active_terminal = Some(*panes.get(active_pane_position - 1).unwrap().0); - } + Some(*panes.get(active_pane_position - 1).unwrap().0) + }; + self.set_active_terminal(active_terminal); self.render(); } // returns a boolean that indicates whether the focus moved @@ -1834,7 +1844,7 @@ impl Tab { return false; } let active_terminal = self.get_active_pane(); - if let Some(active) = active_terminal { + let updated_active_terminal = if let Some(active) = active_terminal { let terminals = self.get_selectable_panes(); let next_index = terminals .enumerate() @@ -1853,17 +1863,16 @@ impl Tab { let next_active_pane = self.panes.get_mut(&p).unwrap(); next_active_pane.set_should_render(true); - self.active_terminal = Some(p); + self.set_active_terminal(Some(p)); self.render(); return true; } - None => { - self.active_terminal = Some(active.pid()); - } + None => Some(active.pid()), } } else { - self.active_terminal = Some(active_terminal.unwrap().pid()); - } + Some(active_terminal.unwrap().pid()) + }; + self.set_active_terminal(updated_active_terminal); false } pub fn move_focus_down(&mut self) { @@ -1874,7 +1883,7 @@ impl Tab { return; } let active_terminal = self.get_active_pane(); - if let Some(active) = active_terminal { + let updated_active_terminal = if let Some(active) = active_terminal { let terminals = self.get_selectable_panes(); let next_index = terminals .enumerate() @@ -1893,15 +1902,14 @@ impl Tab { let next_active_pane = self.panes.get_mut(&p).unwrap(); next_active_pane.set_should_render(true); - self.active_terminal = Some(p); - } - None => { - self.active_terminal = Some(active.pid()); + Some(p) } + None => Some(active.pid()), } } else { - self.active_terminal = Some(active_terminal.unwrap().pid()); - } + Some(active_terminal.unwrap().pid()) + }; + self.set_active_terminal(updated_active_terminal); self.render(); } pub fn move_focus_up(&mut self) { @@ -1912,7 +1920,7 @@ impl Tab { return; } let active_terminal = self.get_active_pane(); - if let Some(active) = active_terminal { + let updated_active_terminal = if let Some(active) = active_terminal { let terminals = self.get_selectable_panes(); let next_index = terminals .enumerate() @@ -1931,15 +1939,14 @@ impl Tab { let next_active_pane = self.panes.get_mut(&p).unwrap(); next_active_pane.set_should_render(true); - self.active_terminal = Some(p); - } - None => { - self.active_terminal = Some(active.pid()); + Some(p) } + None => Some(active.pid()), } } else { - self.active_terminal = Some(active_terminal.unwrap().pid()); - } + Some(active_terminal.unwrap().pid()) + }; + self.set_active_terminal(updated_active_terminal); self.render(); } // returns a boolean that indicates whether the focus moved @@ -1951,7 +1958,7 @@ impl Tab { return false; } let active_terminal = self.get_active_pane(); - if let Some(active) = active_terminal { + let updated_active_terminal = if let Some(active) = active_terminal { let terminals = self.get_selectable_panes(); let next_index = terminals .enumerate() @@ -1970,17 +1977,16 @@ impl Tab { let next_active_pane = self.panes.get_mut(&p).unwrap(); next_active_pane.set_should_render(true); - self.active_terminal = Some(p); + self.set_active_terminal(Some(p)); self.render(); return true; } - None => { - self.active_terminal = Some(active.pid()); - } + None => Some(active.pid()), } } else { - self.active_terminal = Some(active_terminal.unwrap().pid()); - } + Some(active_terminal.unwrap().pid()) + }; + self.set_active_terminal(updated_active_terminal); false } fn horizontal_borders(&self, terminals: &[PaneId]) -> HashSet { @@ -1999,6 +2005,7 @@ impl Tab { borders }) } + fn panes_to_the_left_between_aligning_borders(&self, id: PaneId) -> Option> { if let Some(terminal) = self.panes.get(&id) { let upper_close_border = terminal.y(); @@ -2125,7 +2132,7 @@ impl Tab { if let Some(pane) = self.panes.get_mut(&id) { pane.set_selectable(selectable); if self.get_active_pane_id() == Some(id) && !selectable { - self.active_terminal = self.next_active_pane(&self.get_pane_ids()) + self.set_active_terminal(self.next_active_pane(&self.get_pane_ids())); } } self.render(); @@ -2146,7 +2153,7 @@ impl Tab { self.panes.remove(&id); if self.active_terminal == Some(id) { let next_active_pane = self.next_active_pane(&panes); - self.active_terminal = next_active_pane; + self.set_active_terminal(next_active_pane); } self.relayout_tab(Direction::Horizontal); return; @@ -2158,7 +2165,7 @@ impl Tab { self.panes.remove(&id); if self.active_terminal == Some(id) { let next_active_pane = self.next_active_pane(&panes); - self.active_terminal = next_active_pane; + self.set_active_terminal(next_active_pane); } self.relayout_tab(Direction::Horizontal); return; @@ -2170,7 +2177,7 @@ impl Tab { self.panes.remove(&id); if self.active_terminal == Some(id) { let next_active_pane = self.next_active_pane(&panes); - self.active_terminal = next_active_pane; + self.set_active_terminal(next_active_pane); } self.relayout_tab(Direction::Vertical); return; @@ -2182,7 +2189,7 @@ impl Tab { self.panes.remove(&id); if self.active_terminal == Some(id) { let next_active_pane = self.next_active_pane(&panes); - self.active_terminal = next_active_pane; + self.set_active_terminal(next_active_pane); } self.relayout_tab(Direction::Vertical); return; @@ -2304,7 +2311,7 @@ impl Tab { } fn focus_pane_at(&mut self, point: &Position) { if let Some(clicked_pane) = self.get_pane_id_at(point) { - self.active_terminal = Some(clicked_pane); + self.set_active_terminal(Some(clicked_pane)); self.render(); } } diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index 95c1b98d1..aeea671ba 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -1,10 +1,11 @@ use super::{Screen, ScreenInstruction}; use crate::zellij_tile::data::{ModeInfo, Palette}; use crate::{ - os_input_output::{AsyncReader, Pid, ServerOsApi}, + os_input_output::{AsyncReader, ChildId, Pid, ServerOsApi}, thread_bus::Bus, SessionState, }; +use std::path::PathBuf; use std::sync::{Arc, RwLock}; use zellij_utils::input::command::TerminalAction; use zellij_utils::input::layout::LayoutTemplate; @@ -28,7 +29,7 @@ impl ServerOsApi for FakeInputOutput { fn set_terminal_size_using_fd(&self, _fd: RawFd, _cols: u16, _rows: u16) { // noop } - fn spawn_terminal(&self, _file_to_open: Option) -> (RawFd, Pid) { + fn spawn_terminal(&self, _file_to_open: TerminalAction) -> (RawFd, ChildId) { unimplemented!() } fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result { @@ -73,6 +74,9 @@ impl ServerOsApi for FakeInputOutput { fn load_palette(&self) -> Palette { unimplemented!() } + fn get_cwd(&self, _pid: Pid) -> Option { + unimplemented!() + } } fn create_new_screen(size: Size) -> Screen { diff --git a/zellij-server/src/unit/tab_tests.rs b/zellij-server/src/unit/tab_tests.rs index ea96df627..de5c10670 100644 --- a/zellij-server/src/unit/tab_tests.rs +++ b/zellij-server/src/unit/tab_tests.rs @@ -1,11 +1,12 @@ use super::Tab; use crate::zellij_tile::data::{ModeInfo, Palette}; use crate::{ - os_input_output::{AsyncReader, Pid, ServerOsApi}, + os_input_output::{AsyncReader, ChildId, Pid, ServerOsApi}, panes::PaneId, thread_bus::ThreadSenders, SessionState, }; +use std::path::PathBuf; use std::sync::{Arc, RwLock}; use zellij_utils::input::layout::LayoutTemplate; use zellij_utils::pane_size::Size; @@ -27,7 +28,7 @@ impl ServerOsApi for FakeInputOutput { fn set_terminal_size_using_fd(&self, _fd: RawFd, _cols: u16, _rows: u16) { // noop } - fn spawn_terminal(&self, _file_to_open: Option) -> (RawFd, Pid) { + fn spawn_terminal(&self, _file_to_open: TerminalAction) -> (RawFd, ChildId) { unimplemented!() } fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result { @@ -72,6 +73,9 @@ impl ServerOsApi for FakeInputOutput { fn load_palette(&self) -> Palette { unimplemented!() } + fn get_cwd(&self, _pid: Pid) -> Option { + unimplemented!() + } } fn create_new_tab(size: Size) -> Tab { diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index 65a54c560..87c3bb4dc 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -237,6 +237,7 @@ pub enum PtyContext { SpawnTerminal, SpawnTerminalVertically, SpawnTerminalHorizontally, + UpdateActivePane, NewTab, ClosePane, CloseTab, diff --git a/zellij-utils/src/input/command.rs b/zellij-utils/src/input/command.rs index 2054f208d..debd5bc59 100644 --- a/zellij-utils/src/input/command.rs +++ b/zellij-utils/src/input/command.rs @@ -15,6 +15,8 @@ pub struct RunCommand { pub command: PathBuf, #[serde(default)] pub args: Vec, + #[serde(default)] + pub cwd: Option, } /// Intermediate representation @@ -25,6 +27,8 @@ pub struct RunCommandAction { #[serde(default)] pub args: Vec, #[serde(default)] + pub cwd: Option, + #[serde(default)] pub direction: Option, } @@ -33,6 +37,7 @@ impl From for RunCommand { RunCommand { command: action.command, args: action.args, + cwd: action.cwd, } } }