feat(attach): Support --index option to choose specific session by provided number in active sessions ordered by creation date, resolve #823

feat(attach): Support `--first` option for `attach` sub-command to let zellij choose the alphabetically first session; resolve #823

fix(attach-first): Fix `--first` option to choose the first created session in the existent sessions

feat(attach): Support `--index` option to choose the session indexed by provided number like -t option of tmux

feat(attach): Support listing active sessions with index when a provided number is not found in the active sessions

feat(attach): Support listing active sessions with index when a provided number is not found in the active sessions

feat: Add anyhow to uniformly treat error types and avoid panics
This commit is contained in:
Ken Matsui 2021-11-01 03:14:12 +09:00
parent 03e62eb91c
commit 4acb2458d2
No known key found for this signature in database
GPG key ID: 103360B3298EE433
25 changed files with 394 additions and 210 deletions

View file

@ -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

View file

@ -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)

View file

@ -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

20
Cargo.lock generated
View file

@ -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",

View file

@ -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" }

View file

@ -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,14 +121,60 @@ pub fn main() {
None => config_options,
};
let (client, attach_layout) = match session_name.as_ref() {
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 {
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);
}
}
}
}
}
Err(e) => {
eprintln!("Error occurred: {:?}", e);
process::exit(1);
}
}
} 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()),
ClientInfo::Attach(
session_name.unwrap(),
config_options.clone(),
),
None,
)
}
@ -160,6 +208,7 @@ pub fn main() {
process::exit(1);
}
},
}
};
start_client(

View file

@ -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<Vec<String>, io::ErrorKind> {
}
}
pub(crate) fn get_sessions_sorted_by_creation_date() -> anyhow::Result<Vec<String>> {
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<String>) {
})
}
pub(crate) fn print_sessions_with_index(sessions: Vec<String>) {
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);
}

View file

@ -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) => {

View file

@ -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"

View file

@ -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)
}
ForkResult::Child => {
let child = unsafe {
quit_cb: Box<dyn Fn(PaneId) + Send>,
) -> (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);
}
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");
.pre_exec(move || -> std::io::Result<()> {
if libc::login_tty(pid_secondary) != 0 {
panic!("failed to set controlling terminal");
}
close_fds::close_open_fds(3, &[]);
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)),
},
)
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<dyn Fn(PaneId) + Send>,
) -> (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<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`
/// (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<u32> {
pub fn spawn_terminal(
terminal_action: TerminalAction,
orig_termios: termios::Termios,
) -> (RawFd, ChildId) {
quit_cb: Box<dyn Fn(PaneId) + Send>,
) -> (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<dyn Fn(PaneId) + Send>,
) -> (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<usize, nix::Error>;
/// 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<dyn Fn(PaneId) + Send>,
) -> (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<usize, nix::Error> {
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> {

View file

@ -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();
}
}

View file

@ -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<ClientId, PaneId>,
pub bus: Bus<PtyInstruction>,
pub id_to_child_pid: HashMap<RawFd, ChildId>,
pub id_to_child_pid: HashMap<RawFd, RawFd>, // pty_primary => child raw fd
debug_to_file: bool,
task_handles: HashMap<RawFd, JoinHandle<()>>,
}
@ -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<ENTER>" 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<ClientId>) -> 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))
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| id.shell),
PaneId::Terminal(id) => self.id_to_child_pid.get(id),
})
.and_then(|id| self.bus.os_input.as_ref().map(|input| input.get_cwd(id)))
.flatten(),
.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<TerminalAction>,
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<TerminalAction>,
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);
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(cmd, quit_cb);
self.id_to_child_pid.insert(pid_primary, child_fd);
new_pane_pids.push(pid_primary);
}
None => {
let (pid_primary, child_fd): (RawFd, RawFd) = self
.bus
.os_input
.as_mut()
.unwrap()
.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(

View file

@ -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

View file

@ -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)

View file

@ -269,6 +269,7 @@ pub trait Pane {
fn set_boundary_color(&mut self, _color: Option<PaletteColor>) {}
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);

View file

@ -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<dyn Fn(PaneId) + Send>,
) -> (RawFd, RawFd) {
unimplemented!()
}
fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result<usize, nix::Error> {

View file

@ -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<dyn Fn(PaneId) + Send>,
) -> (RawFd, RawFd) {
unimplemented!()
}
fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result<usize, nix::Error> {

View file

@ -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
}

View file

@ -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"

View file

@ -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<usize>,
/// Change the behaviour of zellij
#[structopt(subcommand, name = "options")]
options: Option<SessionCommand>,

View file

@ -258,6 +258,7 @@ pub enum ScreenContext {
TerminalResize,
ChangeMode,
LeftClick,
RightClick,
MouseRelease,
MouseHold,
Copy,

View file

@ -28,6 +28,8 @@ pub enum Action {
Quit,
/// Write to the terminal.
Write(Vec<u8>),
/// 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,

View file

@ -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<Config, ConfigError>;
/// Intermediate deserialization config struct
#[derive(Debug, Deserialize)]
#[derive(Clone, Debug, Deserialize)]
pub struct ConfigFromYaml {
#[serde(flatten)]
pub options: Option<Options>,
@ -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<ConfigFromYaml> = match serde_yaml::from_str(yaml_config) {
let maybe_config_from_yaml: Option<ConfigFromYaml> = 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<ConfigFromYaml> 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;

View file

@ -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),
}
}
}

View file

@ -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 {