diff options
Diffstat (limited to 'zellij-server/src')
-rw-r--r-- | zellij-server/src/os_input_output.rs | 177 | ||||
-rw-r--r-- | zellij-server/src/pty.rs | 63 | ||||
-rw-r--r-- | zellij-server/src/tab.rs | 115 | ||||
-rw-r--r-- | zellij-server/src/unit/screen_tests.rs | 8 | ||||
-rw-r--r-- | zellij-server/src/unit/tab_tests.rs | 8 |
5 files changed, 247 insertions, 124 deletions
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<u32> { + 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<TerminalAction>, + 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<TerminalAction>) -> (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<usize, nix::Error>; /// 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<PathBuf>; } 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<TerminalAction>) -> (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<PathBuf> { + darwin_libproc::pid_cwd(pid.as_raw()).ok() + } + #[cfg(target_os = "linux")] + fn get_cwd(&self, pid: Pid) -> Option<PathBuf> { + 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<PathBuf> { + None + } } impl Clone for Box<dyn ServerOsApi> { @@ -353,3 +422,13 @@ pub fn get_server_os_input() -> Result<ServerOsInputOutput, nix::Error> { 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<Pid>, +} 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<TerminalAction>), SpawnTerminalVertically(Option<TerminalAction>), SpawnTerminalHorizontally(Option<TerminalAction>), + UpdateActivePane(Option<PaneId>), NewTab(Option<TerminalAction>, Option<TabLayout>), ClosePane(PaneId), CloseTab(Vec<PaneId>), @@ -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<PaneId>, pub bus: Bus<PtyInstruction>, - pub id_to_child_pid: HashMap<RawFd, Pid>, + pub id_to_child_pid: HashMap<RawFd, ChildId>, debug_to_file: bool, task_handles: HashMap<RawFd, JoinHandle<()>>, } @@ -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<PtyInstruction>, 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<TerminalAction>) -> 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<TerminalAction>, ) { + 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<PaneId>) { + 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<PaneId>) { + 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<RawFd>, 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<PaneId> = 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<usize> { @@ -1999,6 +2005,7 @@ impl Tab { borders }) } + fn panes_to_the_left_between_aligning_borders(&self, id: PaneId) -> Option<Vec<PaneId>> { 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 { |