diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index fd34b4f958..9e45c461e5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -3,7 +3,7 @@ name: "\U0001F41B Bug Report" about: "If something isn't working as expected." labels: bug --- -Thank you for taking the time to file this issue! Please follow the instructions and fill the missing parts below the instructions, if it is meaningful. Try to be brief and concise. +Thank you for taking the time to file this issue! Please follow the instructions and fill in the missing parts below the instructions, if it is meaningful. Try to be brief and concise. **In Case of Graphical or Performance Issues** @@ -26,4 +26,4 @@ List of programs you interact with as, `PROGRAM --version`: output cropped meani `alacritty --version`: alacritty 0.7.2 (5ac8060b) **Further information** -Reproduction steps, noticable behavior, related issues etc +Reproduction steps, noticeable behavior, related issues, etc diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e91ca6372..ecd9d79c06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Fix: pasted text performs much faster and doesn't kill Termion (https://github.com/zellij-org/zellij/pull/810) * Fix: resizing/scrolling through heavily wrapped panes no longer hangs (https://github.com/zellij-org/zellij/pull/814) * Terminal compatibility: properly handle HOME/END keys in eg. vim/zsh (https://github.com/zellij-org/zellij/pull/815) +* Fix: Typo (https://github.com/zellij-org/zellij/pull/821) +* Fix: Update `cargo-make` instructions post `v0.35.3` (https://github.com/zellij-org/zellij/pull/819) +* Fix: Unused import for darwin systems (https://github.com/zellij-org/zellij/pull/820) +* Add: `WriteChars` action (https://github.com/zellij-org/zellij/pull/825) +* Fix: typo and grammar (https://github.com/zellij-org/zellij/pull/826) +* Add: `rust-version' - msrv field to `Cargo.toml` (https://github.com/zellij-org/zellij/pull/828) +* Fix: improve memory utilization, reap both sides of pty properly and do not expose open FDs to child processes (https://github.com/zellij-org/zellij/pull/830) +* Fix: move from the deprecated `colors_transform` to `colorsys` (https://github.com/zellij-org/zellij/pull/832) +* Feature: plugins can now detect right mouse clicks (https://github.com/zellij-org/zellij/pull/801) +* Fix: open pane in cwd even when explicitly specifying shell (https://github.com/zellij-org/zellij/pull/834) ## [0.19.0] - 2021-10-20 * Fix: Prevent text overwrite when scrolled up (https://github.com/zellij-org/zellij/pull/655) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2a7039c506..11c6d1f33b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,7 +28,7 @@ cargo make build cargo make test # Run Zellij (optionally with additional arguments) cargo make run -cargo make run -- -l strider +cargo make run -l strider # Run Clippy (potentially with additional options) cargo make clippy cargo make clippy -W clippy::pedantic diff --git a/Cargo.lock b/Cargo.lock index 39d449fe17..a3ffff357c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -351,6 +351,16 @@ dependencies = [ "vec_map", ] +[[package]] +name = "close_fds" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bc416f33de9d59e79e57560f450d21ff8393adcf1cdfc3e6d8fb93d5f88a2ed" +dependencies = [ + "cfg-if 1.0.0", + "libc", +] + [[package]] name = "colored" version = "2.0.0" @@ -363,10 +373,10 @@ dependencies = [ ] [[package]] -name = "colors-transform" -version = "0.2.11" +name = "colorsys" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9226dbc05df4fb986f48d730b001532580883c4c06c5d1c213f4b34c1c157178" +checksum = "4dfdf9179d546b55ff3f88c9d93ecfaa3e9760163da5a1080af5243230dbbb70" [[package]] name = "concurrent-queue" @@ -2824,6 +2834,7 @@ dependencies = [ name = "zellij" version = "0.20.0" dependencies = [ + "anyhow", "insta", "log", "names", @@ -2857,6 +2868,7 @@ dependencies = [ "byteorder", "cassowary", "chrono", + "close_fds", "daemonize", "darwin-libproc", "highway", @@ -2895,7 +2907,7 @@ dependencies = [ "async-std", "backtrace", "bincode", - "colors-transform", + "colorsys", "crossbeam", "directories-next", "interprocess", diff --git a/Cargo.toml b/Cargo.toml index f205b5f0a5..1c33bae74f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,10 +9,12 @@ repository = "https://github.com/zellij-org/zellij" homepage = "https://zellij.dev" include = ["src/**/*", "assets/plugins/*", "assets/layouts/*", "assets/config/*", "LICENSE.md", "README.md", "!**/*_test.*", "!**/tests/**/*"] resolver = "2" +rust-version = "1.56" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0" names = "0.11.0" zellij-client = { path = "zellij-client/", version = "0.20.0" } zellij-server = { path = "zellij-server/", version = "0.20.0" } diff --git a/src/main.rs b/src/main.rs index 0cacc3759b..f12eb010be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,8 +5,9 @@ mod tests; use crate::install::populate_data_dir; use sessions::{ - assert_session, assert_session_ne, get_active_session, get_sessions, kill_session, - list_sessions, print_sessions, session_exists, ActiveSession, + assert_session, assert_session_ne, get_active_session, get_sessions, + get_sessions_sorted_by_creation_date, kill_session, list_sessions, print_sessions, + print_sessions_with_index, session_exists, ActiveSession, }; use std::process; use zellij_client::{os_input_output::get_client_os_input, start_client, ClientInfo}; @@ -62,7 +63,7 @@ pub fn main() { } } Err(e) => { - eprintln!("Error occured: {:?}", e); + eprintln!("Error occurred: {:?}", e); process::exit(1); } } @@ -111,6 +112,7 @@ pub fn main() { if let Some(Command::Sessions(Sessions::Attach { session_name, create, + index, options, })) = opts.command.clone() { @@ -119,47 +121,94 @@ pub fn main() { None => config_options, }; - let (client, attach_layout) = match session_name.as_ref() { - Some(session) => { - if create { - if !session_exists(session).unwrap() { - (ClientInfo::New(session_name.unwrap()), layout) + let (client, attach_layout) = if let Some(idx) = index { + // Ignore session_name when `--index` is provided + match get_sessions_sorted_by_creation_date() { + Ok(sessions) => { + if sessions.is_empty() { + if create { + ( + ClientInfo::New(names::Generator::default().next().unwrap()), + layout, + ) + } else { + println!("No active zellij sessions found."); + process::exit(1); + } } else { - ( - ClientInfo::Attach(session_name.unwrap(), config_options.clone()), - None, - ) + match sessions.get(idx) { + Some(session) => ( + ClientInfo::Attach(session.clone(), config_options.clone()), + None, + ), + None => { + if create { + ( + ClientInfo::New( + names::Generator::default().next().unwrap(), + ), + layout, + ) + } else { + println!("No session indexed by {} found. The following sessions are active:", idx); + print_sessions_with_index(sessions); + process::exit(1); + } + } + } } - } else { - assert_session(session); - ( - ClientInfo::Attach(session_name.unwrap(), config_options.clone()), - None, - ) + } + Err(e) => { + eprintln!("Error occurred: {:?}", e); + process::exit(1); } } - None => match get_active_session() { - ActiveSession::None => { + } else { + match session_name.as_ref() { + Some(session) => { if create { + if !session_exists(session).unwrap() { + (ClientInfo::New(session_name.unwrap()), layout) + } else { + ( + ClientInfo::Attach( + session_name.unwrap(), + config_options.clone(), + ), + None, + ) + } + } else { + assert_session(session); ( - ClientInfo::New(names::Generator::default().next().unwrap()), - layout, + ClientInfo::Attach(session_name.unwrap(), config_options.clone()), + None, ) - } else { - println!("No active zellij sessions found."); - process::exit(1); } } - ActiveSession::One(session_name) => ( - ClientInfo::Attach(session_name, config_options.clone()), - None, - ), - ActiveSession::Many => { - println!("Please specify the session name to attach to. The following sessions are active:"); - print_sessions(get_sessions().unwrap()); - process::exit(1); - } - }, + None => match get_active_session() { + ActiveSession::None => { + if create { + ( + ClientInfo::New(names::Generator::default().next().unwrap()), + layout, + ) + } else { + println!("No active zellij sessions found."); + process::exit(1); + } + } + ActiveSession::One(session_name) => ( + ClientInfo::Attach(session_name, config_options.clone()), + None, + ), + ActiveSession::Many => { + println!("Please specify the session name to attach to. The following sessions are active:"); + print_sessions(get_sessions().unwrap()); + process::exit(1); + } + }, + } }; start_client( diff --git a/src/sessions.rs b/src/sessions.rs index c2d8afea4c..bf44d08858 100644 --- a/src/sessions.rs +++ b/src/sessions.rs @@ -1,4 +1,5 @@ use std::os::unix::fs::FileTypeExt; +use std::time::SystemTime; use std::{fs, io, process}; use zellij_utils::{ consts::ZELLIJ_SOCK_DIR, @@ -29,6 +30,36 @@ pub(crate) fn get_sessions() -> Result, io::ErrorKind> { } } +pub(crate) fn get_sessions_sorted_by_creation_date() -> anyhow::Result> { + match fs::read_dir(&*ZELLIJ_SOCK_DIR) { + Ok(files) => { + let mut sessions_with_creation_date: Vec<(String, SystemTime)> = Vec::new(); + for file in files { + let file = file?; + let file_name = file.file_name().into_string().unwrap(); + let file_created_at = file.metadata()?.created()?; + if file.file_type()?.is_socket() && assert_socket(&file_name) { + sessions_with_creation_date.push((file_name, file_created_at)); + } + } + sessions_with_creation_date.sort_by_key(|x| x.1); // the oldest one will be the first + + let sessions = sessions_with_creation_date + .iter() + .map(|x| x.0.clone()) + .collect(); + Ok(sessions) + } + Err(err) => { + if let io::ErrorKind::NotFound = err.kind() { + Ok(Vec::with_capacity(0)) + } else { + Err(err.into()) + } + } + } +} + fn assert_socket(name: &str) -> bool { let path = &*ZELLIJ_SOCK_DIR.join(name); match LocalSocketStream::connect(path) { @@ -59,6 +90,18 @@ pub(crate) fn print_sessions(sessions: Vec) { }) } +pub(crate) fn print_sessions_with_index(sessions: Vec) { + let curr_session = std::env::var("ZELLIJ_SESSION_NAME").unwrap_or_else(|_| "".into()); + for (i, session) in sessions.iter().enumerate() { + let suffix = if curr_session == *session { + " (current)" + } else { + "" + }; + println!("{}: {}{}", i, session, suffix); + } +} + pub(crate) enum ActiveSession { None, One(String), @@ -78,7 +121,7 @@ pub(crate) fn get_active_session() -> ActiveSession { } } Err(e) => { - eprintln!("Error occured: {:?}", e); + eprintln!("Error occurred: {:?}", e); process::exit(1); } } @@ -91,7 +134,7 @@ pub(crate) fn kill_session(name: &str) { IpcSenderWithContext::new(stream).send(ClientToServerMsg::KillSession); } Err(e) => { - eprintln!("Error occured: {:?}", e); + eprintln!("Error occurred: {:?}", e); process::exit(1); } }; @@ -108,7 +151,7 @@ pub(crate) fn list_sessions() { 0 } Err(e) => { - eprintln!("Error occured: {:?}", e); + eprintln!("Error occurred: {:?}", e); 1 } }; @@ -137,7 +180,7 @@ pub(crate) fn assert_session(name: &str) { } } Err(e) => { - eprintln!("Error occured: {:?}", e); + eprintln!("Error occurred: {:?}", e); } }; process::exit(1); @@ -151,7 +194,7 @@ pub(crate) fn assert_session_ne(name: &str) { } println!("Session with name {:?} aleady exists. Use attach command to connect to it or specify a different name.", name); } - Err(e) => eprintln!("Error occured: {:?}", e), + Err(e) => eprintln!("Error occurred: {:?}", e), }; process::exit(1); } diff --git a/zellij-client/src/input_handler.rs b/zellij-client/src/input_handler.rs index 2b89741e06..d07c2caa88 100644 --- a/zellij-client/src/input_handler.rs +++ b/zellij-client/src/input_handler.rs @@ -135,6 +135,9 @@ impl InputHandler { MouseButton::Left => { self.dispatch_action(Action::LeftClick(point)); } + MouseButton::Right => { + self.dispatch_action(Action::RightClick(point)); + } _ => {} }, MouseEvent::Release(point) => { diff --git a/zellij-server/Cargo.toml b/zellij-server/Cargo.toml index 9d7c075e11..ea72f2d019 100644 --- a/zellij-server/Cargo.toml +++ b/zellij-server/Cargo.toml @@ -25,6 +25,7 @@ zellij-utils = { path = "../zellij-utils/", version = "0.20.0" } log = "0.4.14" typetag = "0.1.7" chrono = "0.4.19" +close_fds = "0.3.2" [target.'cfg(target_os = "macos")'.dependencies] darwin-libproc = "0.2.0" diff --git a/zellij-server/src/os_input_output.rs b/zellij-server/src/os_input_output.rs index 09c5e9d700..f8fe751a69 100644 --- a/zellij-server/src/os_input_output.rs +++ b/zellij-server/src/os_input_output.rs @@ -1,10 +1,14 @@ use std::collections::HashMap; +use crate::panes::PaneId; + #[cfg(target_os = "macos")] use darwin_libproc; -use std::env; +#[cfg(target_os = "linux")] use std::fs; + +use std::env; use std::os::unix::io::RawFd; use std::os::unix::process::CommandExt; use std::path::PathBuf; @@ -16,11 +20,12 @@ 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, ForkptyResult, Winsize}; + +use nix::pty::{openpty, OpenptyResult, Winsize}; use nix::sys::signal::{kill, Signal}; use nix::sys::termios; -use nix::sys::wait::waitpid; -use nix::unistd::{self, ForkResult}; + +use nix::unistd; use signal_hook::consts::*; use zellij_tile::data::Palette; use zellij_utils::{ @@ -31,7 +36,6 @@ use zellij_utils::{ use async_std::io::ReadExt; pub use async_trait::async_trait; -use byteorder::{BigEndian, ByteOrder}; pub use nix::unistd::Pid; @@ -97,96 +101,63 @@ fn handle_command_exit(mut child: Child) { } } -fn handle_fork_pty( - fork_pty_res: ForkptyResult, +fn handle_openpty( + open_pty_res: OpenptyResult, 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) + quit_cb: Box, +) -> (RawFd, RawFd) { + // primary side of pty and child fd + let pid_primary = open_pty_res.master; + let pid_secondary = open_pty_res.slave; + + let mut child = unsafe { + let command = &mut Command::new(cmd.command); + if let Some(current_dir) = cmd.cwd { + command.current_dir(current_dir); } - 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(move || -> std::io::Result<()> { + if libc::login_tty(pid_secondary) != 0 { + panic!("failed to set controlling terminal"); } - 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); - } + close_fds::close_open_fds(3, &[]); + Ok(()) + }) + .spawn() + .expect("failed to spawn") }; - ( - pid_primary, - ChildId { - primary: pid_secondary, - shell: pid_shell.map(|pid| Pid::from_raw(pid as i32)), - }, - ) + let child_id = child.id(); + std::thread::spawn(move || { + child.wait().unwrap(); + handle_command_exit(child); + let _ = nix::unistd::close(pid_primary); + let _ = nix::unistd::close(pid_secondary); + quit_cb(PaneId::Terminal(pid_primary)); + }); + + (pid_primary, child_id as RawFd) } /// Spawns a new terminal from the parent terminal with [`termios`](termios::Termios) /// `orig_termios`. /// -fn handle_terminal(cmd: RunCommand, orig_termios: termios::Termios) -> (RawFd, ChildId) { +fn handle_terminal( + cmd: RunCommand, + orig_termios: termios::Termios, + quit_cb: Box, +) -> (RawFd, RawFd) { // 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), + match openpty(None, Some(&orig_termios)) { + Ok(open_pty_res) => handle_openpty(open_pty_res, cmd, quit_cb), Err(e) => { - panic!("failed to fork {:?}", e); + panic!("failed to start pty{:?}", e); } } } -/// 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` /// (or `VISUAL`, if `EDITOR` is not set) will be started in the new terminal, with the given /// file open. @@ -202,7 +173,8 @@ fn read_from_pipe(parent_fd: RawFd, child_fd: RawFd) -> Option { pub fn spawn_terminal( terminal_action: TerminalAction, orig_termios: termios::Termios, -) -> (RawFd, ChildId) { + quit_cb: Box, +) -> (RawFd, RawFd) { let cmd = match terminal_action { TerminalAction::OpenFile(file_to_open) => { if env::var("EDITOR").is_err() && env::var("VISUAL").is_err() { @@ -224,7 +196,7 @@ pub fn spawn_terminal( TerminalAction::RunCommand(command) => command, }; - handle_terminal(cmd, orig_termios) + handle_terminal(cmd, orig_termios, quit_cb) } #[derive(Clone)] @@ -269,7 +241,11 @@ pub trait ServerOsApi: Send + Sync { /// 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); + fn spawn_terminal( + &self, + terminal_action: TerminalAction, + quit_cb: Box, + ) -> (RawFd, RawFd); /// 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 @@ -302,9 +278,13 @@ impl ServerOsApi for ServerOsInputOutput { set_terminal_size_using_fd(fd, cols, rows); } } - fn spawn_terminal(&self, terminal_action: TerminalAction) -> (RawFd, ChildId) { + fn spawn_terminal( + &self, + terminal_action: TerminalAction, + quit_cb: Box, + ) -> (RawFd, RawFd) { let orig_termios = self.orig_termios.lock().unwrap(); - spawn_terminal(terminal_action, orig_termios.clone()) + spawn_terminal(terminal_action, orig_termios.clone(), quit_cb) } fn read_from_tty_stdout(&self, fd: RawFd, buf: &mut [u8]) -> Result { unistd::read(fd, buf) @@ -322,8 +302,7 @@ impl ServerOsApi for ServerOsInputOutput { Box::new((*self).clone()) } fn kill(&self, pid: Pid) -> Result<(), nix::Error> { - kill(pid, Some(Signal::SIGTERM)).unwrap(); - waitpid(pid, None).unwrap(); + let _ = kill(pid, Some(Signal::SIGTERM)); Ok(()) } fn force_kill(&self, pid: Pid) -> Result<(), nix::Error> { diff --git a/zellij-server/src/panes/plugin_pane.rs b/zellij-server/src/panes/plugin_pane.rs index 8659ae1552..6b1cb41685 100644 --- a/zellij-server/src/panes/plugin_pane.rs +++ b/zellij-server/src/panes/plugin_pane.rs @@ -327,4 +327,12 @@ impl Pane for PluginPane { fn borderless(&self) -> bool { self.borderless } + fn handle_right_click(&mut self, to: &Position) { + self.send_plugin_instructions + .send(PluginInstruction::Update( + Some(self.pid), + Event::Mouse(Mouse::RightClick(to.line.0, to.column.0)), + )) + .unwrap(); + } } diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs index 277f572c8e..8c5a113379 100644 --- a/zellij-server/src/pty.rs +++ b/zellij-server/src/pty.rs @@ -1,5 +1,5 @@ use crate::{ - os_input_output::{AsyncReader, ChildId, ServerOsApi}, + os_input_output::{AsyncReader, ServerOsApi}, panes::PaneId, screen::ScreenInstruction, thread_bus::{Bus, ThreadSenders}, @@ -17,6 +17,7 @@ use std::{ path::PathBuf, time::{Duration, Instant}, }; +use zellij_utils::nix::unistd::Pid; use zellij_utils::{ async_std, errors::{get_current_ctx, ContextType, PtyContext}, @@ -66,7 +67,7 @@ impl From<&PtyInstruction> for PtyContext { pub(crate) struct Pty { pub active_panes: HashMap, pub bus: Bus, - pub id_to_child_pid: HashMap, + pub id_to_child_pid: HashMap, // pty_primary => child raw fd debug_to_file: bool, task_handles: HashMap>, } @@ -252,15 +253,6 @@ fn stream_terminal_bytes( } } async_send_to_screen(senders.clone(), ScreenInstruction::Render).await; - - // we send ClosePane here so that the screen knows to close this tab if the process - // inside the terminal exited on its own (eg. the user typed "exit" inside a - // bash shell) - async_send_to_screen( - senders, - ScreenInstruction::ClosePane(PaneId::Terminal(pid), None), - ) - .await; } }) } @@ -275,39 +267,65 @@ impl Pty { task_handles: HashMap::new(), } } - pub fn get_default_terminal(&self, client_id: Option) -> TerminalAction { + 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: client_id - .and_then(|client_id| self.active_panes.get(&client_id)) - .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(), + cwd: None, // this should be filled by the calling function, eg. spawn_terminal }) } + fn fill_cwd(&self, terminal_action: &mut TerminalAction, client_id: ClientId) { + if let TerminalAction::RunCommand(run_command) = terminal_action { + if run_command.cwd.is_none() { + run_command.cwd = self + .active_panes + .get(&client_id) + .and_then(|pane| match pane { + PaneId::Plugin(..) => None, + PaneId::Terminal(id) => self.id_to_child_pid.get(id), + }) + .and_then(|id| { + self.bus + .os_input + .as_ref() + .map(|input| input.get_cwd(Pid::from_raw(*id))) + }) + .flatten(); + }; + }; + } pub fn spawn_terminal( &mut self, terminal_action: Option, client_or_tab_index: ClientOrTabIndex, ) -> RawFd { + log::info!( + "spawn_terminal, client_or_tab_index: {:?}", + client_or_tab_index + ); let terminal_action = match client_or_tab_index { ClientOrTabIndex::ClientId(client_id) => { - terminal_action.unwrap_or_else(|| self.get_default_terminal(Some(client_id))) + let mut terminal_action = + terminal_action.unwrap_or_else(|| self.get_default_terminal()); + self.fill_cwd(&mut terminal_action, client_id); + terminal_action } ClientOrTabIndex::TabIndex(_) => { - terminal_action.unwrap_or_else(|| self.get_default_terminal(None)) + terminal_action.unwrap_or_else(|| self.get_default_terminal()) } }; - let (pid_primary, child_id): (RawFd, ChildId) = self + let quit_cb = Box::new({ + let senders = self.bus.senders.clone(); + move |pane_id| { + let _ = senders.send_to_screen(ScreenInstruction::ClosePane(pane_id, None)); + } + }); + let (pid_primary, child_fd): (RawFd, RawFd) = self .bus .os_input .as_mut() .unwrap() - .spawn_terminal(terminal_action); + .spawn_terminal(terminal_action, quit_cb); let task_handle = stream_terminal_bytes( pid_primary, self.bus.senders.clone(), @@ -315,7 +333,7 @@ impl Pty { self.debug_to_file, ); self.task_handles.insert(pid_primary, task_handle); - self.id_to_child_pid.insert(pid_primary, child_id); + self.id_to_child_pid.insert(pid_primary, child_fd); pid_primary } pub fn spawn_terminals_for_layout( @@ -324,27 +342,37 @@ impl Pty { default_shell: Option, client_id: ClientId, ) { - let default_shell = - default_shell.unwrap_or_else(|| self.get_default_terminal(Some(client_id))); + let mut default_shell = default_shell.unwrap_or_else(|| self.get_default_terminal()); + self.fill_cwd(&mut default_shell, client_id); let extracted_run_instructions = layout.extract_run_instructions(); let mut new_pane_pids = vec![]; for run_instruction in extracted_run_instructions { + let quit_cb = Box::new({ + let senders = self.bus.senders.clone(); + move |pane_id| { + let _ = senders.send_to_screen(ScreenInstruction::ClosePane(pane_id, None)); + } + }); match run_instruction { Some(Run::Command(command)) => { let cmd = TerminalAction::RunCommand(command); - 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); + let (pid_primary, child_fd): (RawFd, RawFd) = self + .bus + .os_input + .as_mut() + .unwrap() + .spawn_terminal(cmd, quit_cb); + self.id_to_child_pid.insert(pid_primary, child_fd); new_pane_pids.push(pid_primary); } None => { - let (pid_primary, child_id): (RawFd, ChildId) = self + let (pid_primary, child_fd): (RawFd, RawFd) = self .bus .os_input .as_mut() .unwrap() - .spawn_terminal(default_shell.clone()); - self.id_to_child_pid.insert(pid_primary, child_id); + .spawn_terminal(default_shell.clone(), quit_cb); + self.id_to_child_pid.insert(pid_primary, child_fd); new_pane_pids.push(pid_primary); } // Investigate moving plugin loading to here. @@ -372,27 +400,15 @@ impl Pty { pub fn close_pane(&mut self, id: PaneId) { match id { PaneId::Terminal(id) => { - let pids = self.id_to_child_pid.remove(&id).unwrap(); - let handle = self.task_handles.remove(&id).unwrap(); + let child_fd = self.id_to_child_pid.remove(&id).unwrap(); + self.task_handles.remove(&id).unwrap(); task::block_on(async { self.bus .os_input .as_mut() .unwrap() - .kill(pids.primary) + .kill(Pid::from_raw(child_fd)) .unwrap(); - let timeout = Duration::from_millis(100); - match async_timeout(timeout, handle.cancel()).await { - Ok(_) => {} - _ => { - self.bus - .os_input - .as_mut() - .unwrap() - .force_kill(pids.primary) - .unwrap(); - } - }; }); } PaneId::Plugin(pid) => drop( diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index 9b1201b6ac..57fea12472 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -50,6 +50,17 @@ fn route_action( .send_to_screen(ScreenInstruction::WriteCharacter(val, client_id)) .unwrap(); } + Action::WriteChars(val) => { + session + .senders + .send_to_screen(ScreenInstruction::ClearScroll(client_id)) + .unwrap(); + let val = Vec::from(val.as_bytes()); + session + .senders + .send_to_screen(ScreenInstruction::WriteCharacter(val, client_id)) + .unwrap(); + } Action::SwitchToMode(mode) => { let palette = session.palette; // TODO: use the palette from the client and remove it from the server os api @@ -285,6 +296,13 @@ fn route_action( .send_to_screen(ScreenInstruction::LeftClick(point, client_id)) .unwrap(); } + Action::RightClick(point) => { + session + .senders + .send_to_screen(ScreenInstruction::RightClick(point, client_id)) + .unwrap(); + } + Action::MouseRelease(point) => { session .senders diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 72c681c97e..4852b305b2 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -74,6 +74,7 @@ pub(crate) enum ScreenInstruction { TerminalResize(Size), ChangeMode(ModeInfo, ClientId), LeftClick(Position, ClientId), + RightClick(Position, ClientId), MouseRelease(Position, ClientId), MouseHold(Position, ClientId), Copy(ClientId), @@ -138,6 +139,7 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::ScrollUpAt(..) => ScreenContext::ScrollUpAt, ScreenInstruction::ScrollDownAt(..) => ScreenContext::ScrollDownAt, ScreenInstruction::LeftClick(..) => ScreenContext::LeftClick, + ScreenInstruction::RightClick(..) => ScreenContext::RightClick, ScreenInstruction::MouseRelease(..) => ScreenContext::MouseRelease, ScreenInstruction::MouseHold(..) => ScreenContext::MouseHold, ScreenInstruction::Copy(..) => ScreenContext::Copy, @@ -973,6 +975,14 @@ pub(crate) fn screen_thread_main( screen.render(); } + ScreenInstruction::RightClick(point, client_id) => { + screen + .get_active_tab_mut(client_id) + .unwrap() + .handle_right_click(&point); + + screen.render(); + } ScreenInstruction::MouseRelease(point, client_id) => { screen .get_active_tab_mut(client_id) diff --git a/zellij-server/src/tab.rs b/zellij-server/src/tab.rs index d50aba42cd..5c81b9c6e0 100644 --- a/zellij-server/src/tab.rs +++ b/zellij-server/src/tab.rs @@ -269,6 +269,7 @@ pub trait Pane { fn set_boundary_color(&mut self, _color: Option) {} fn set_borderless(&mut self, borderless: bool); fn borderless(&self) -> bool; + fn handle_right_click(&mut self, _to: &Position) {} } macro_rules! resize_pty { @@ -2586,6 +2587,14 @@ impl Tab { pane.start_selection(&relative_position); }; } + pub fn handle_right_click(&mut self, position: &Position) { + self.focus_pane_at(position); + + if let Some(pane) = self.get_pane_at(position, false) { + let relative_position = pane.relative_position(position); + pane.handle_right_click(&relative_position); + }; + } fn focus_pane_at(&mut self, point: &Position) { if let Some(clicked_pane) = self.get_pane_id_at(point, true) { self.active_terminal = Some(clicked_pane); diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index b4d367e4b0..f9f63195a4 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -1,7 +1,8 @@ use super::{Screen, ScreenInstruction}; +use crate::panes::PaneId; use crate::zellij_tile::data::{ModeInfo, Palette}; use crate::{ - os_input_output::{AsyncReader, ChildId, Pid, ServerOsApi}, + os_input_output::{AsyncReader, Pid, ServerOsApi}, thread_bus::Bus, ClientId, }; @@ -29,7 +30,11 @@ 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: TerminalAction) -> (RawFd, ChildId) { + fn spawn_terminal( + &self, + _file_to_open: TerminalAction, + _quit_db: Box, + ) -> (RawFd, RawFd) { unimplemented!() } fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result { diff --git a/zellij-server/src/unit/tab_tests.rs b/zellij-server/src/unit/tab_tests.rs index 41e243cd36..3d177ab8fb 100644 --- a/zellij-server/src/unit/tab_tests.rs +++ b/zellij-server/src/unit/tab_tests.rs @@ -1,7 +1,7 @@ use super::Tab; use crate::zellij_tile::data::{ModeInfo, Palette}; use crate::{ - os_input_output::{AsyncReader, ChildId, Pid, ServerOsApi}, + os_input_output::{AsyncReader, Pid, ServerOsApi}, panes::PaneId, thread_bus::ThreadSenders, ClientId, @@ -29,7 +29,11 @@ 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: TerminalAction) -> (RawFd, ChildId) { + fn spawn_terminal( + &self, + _file_to_open: TerminalAction, + _quit_cb: Box, + ) -> (RawFd, RawFd) { unimplemented!() } fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result { diff --git a/zellij-tile/src/data.rs b/zellij-tile/src/data.rs index 9696b3563d..872a78f9f3 100644 --- a/zellij-tile/src/data.rs +++ b/zellij-tile/src/data.rs @@ -34,6 +34,7 @@ pub enum Mouse { ScrollUp(usize), // number of lines ScrollDown(usize), // number of lines LeftClick(isize, usize), // line and column + RightClick(isize, usize), // line and column Hold(isize, usize), // line and column Release(Option<(isize, usize)>), // line and column } diff --git a/zellij-utils/Cargo.toml b/zellij-utils/Cargo.toml index eab045dde1..bf4654e9f0 100644 --- a/zellij-utils/Cargo.toml +++ b/zellij-utils/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" [dependencies] backtrace = "0.3.55" bincode = "1.3.1" -colors-transform = "0.2.5" +colorsys = "0.6.5" crossbeam = "0.8.0" directories-next = "2.0" interprocess = "1.1.1" diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs index 7ac9e958c8..78615d9291 100644 --- a/zellij-utils/src/cli.rs +++ b/zellij-utils/src/cli.rs @@ -85,6 +85,10 @@ pub enum Sessions { #[structopt(short, long)] create: bool, + /// Number of the session index in the active sessions ordered creation date. + #[structopt(long)] + index: Option, + /// Change the behaviour of zellij #[structopt(subcommand, name = "options")] options: Option, diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index be75e4adde..401cdc10b5 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -258,6 +258,7 @@ pub enum ScreenContext { TerminalResize, ChangeMode, LeftClick, + RightClick, MouseRelease, MouseHold, Copy, diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index 8446bf0d81..9dd2595808 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -28,6 +28,8 @@ pub enum Action { Quit, /// Write to the terminal. Write(Vec), + /// Write Characters to the terminal. + WriteChars(String), /// Switch to the specified input mode. SwitchToMode(InputMode), /// Resize focus pane in specified direction. @@ -85,6 +87,7 @@ pub enum Action { /// Detach session and exit Detach, LeftClick(Position), + RightClick(Position), MouseRelease(Position), MouseHold(Position), Copy, diff --git a/zellij-utils/src/input/config.rs b/zellij-utils/src/input/config.rs index ecfe64c7c8..66104016c5 100644 --- a/zellij-utils/src/input/config.rs +++ b/zellij-utils/src/input/config.rs @@ -5,7 +5,7 @@ use std::fs::File; use std::io::{self, Read}; use std::path::{Path, PathBuf}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use std::convert::{TryFrom, TryInto}; use super::keybinds::{Keybinds, KeybindsFromYaml}; @@ -20,7 +20,7 @@ const DEFAULT_CONFIG_FILE_NAME: &str = "config.yaml"; type ConfigResult = Result; /// Intermediate deserialization config struct -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct ConfigFromYaml { #[serde(flatten)] pub options: Option, @@ -31,7 +31,7 @@ pub struct ConfigFromYaml { } /// Main configuration. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Clone, PartialEq, Deserialize)] pub struct Config { pub keybinds: Keybinds, pub options: Options, @@ -106,7 +106,8 @@ impl TryFrom<&CliArgs> for Config { impl Config { /// Uses defaults, but lets config override them. pub fn from_yaml(yaml_config: &str) -> ConfigResult { - let config_from_yaml: Option = match serde_yaml::from_str(yaml_config) { + let maybe_config_from_yaml: Option = match serde_yaml::from_str(yaml_config) + { Err(e) => { // needs direct check, as `[ErrorImpl]` is private // https://github.com/dtolnay/serde-yaml/issues/121 @@ -118,20 +119,9 @@ impl Config { Ok(config) => config, }; - match config_from_yaml { + match maybe_config_from_yaml { None => Ok(Config::default()), - Some(config) => { - let keybinds = Keybinds::get_default_keybinds_with_config(config.keybinds); - let options = Options::from_yaml(config.options); - let themes = config.themes; - let plugins = PluginsConfig::get_plugins_with_default(config.plugins.try_into()?); - Ok(Config { - keybinds, - options, - plugins, - themes, - }) - } + Some(config) => config.try_into(), } } @@ -157,6 +147,23 @@ impl Config { } } +impl TryFrom for Config { + type Error = ConfigError; + + fn try_from(config_from_yaml: ConfigFromYaml) -> ConfigResult { + let keybinds = Keybinds::get_default_keybinds_with_config(config_from_yaml.keybinds); + let options = Options::from_yaml(config_from_yaml.options); + let themes = config_from_yaml.themes; + let plugins = PluginsConfig::get_plugins_with_default(config_from_yaml.plugins.try_into()?); + Ok(Self { + keybinds, + options, + plugins, + themes, + }) + } +} + // TODO: Split errors up into separate modules #[derive(Debug, Clone)] pub struct LayoutNameInTabError; diff --git a/zellij-utils/src/ipc.rs b/zellij-utils/src/ipc.rs index 995b89fb4e..b27a097a14 100644 --- a/zellij-utils/src/ipc.rs +++ b/zellij-utils/src/ipc.rs @@ -104,7 +104,7 @@ impl Display for ExitReason { f, "Session attached to another client. Use --force flag to force connect." ), - Self::Error(e) => write!(f, "Error occured in server:\n{}", e), + Self::Error(e) => write!(f, "Error occurred in server:\n{}", e), } } } diff --git a/zellij-utils/src/shared.rs b/zellij-utils/src/shared.rs index 06d1378db4..37dfc3be2f 100644 --- a/zellij-utils/src/shared.rs +++ b/zellij-utils/src/shared.rs @@ -2,7 +2,7 @@ use std::{iter, str::from_utf8}; -use colors_transform::{Color, Rgb}; +use colorsys::Rgb; use std::os::unix::fs::PermissionsExt; use std::path::Path; use std::{fs, io}; @@ -52,10 +52,9 @@ pub mod colors { } pub fn _hex_to_rgb(hex: &str) -> (u8, u8, u8) { - let rgb = Rgb::from_hex_str(hex) + Rgb::from_hex_str(hex) .expect("The passed argument must be a valid hex color") - .as_tuple(); - (rgb.0 as u8, rgb.1 as u8, rgb.2 as u8) + .into() } pub fn default_palette() -> Palette {