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:
parent
03e62eb91c
commit
4acb2458d2
25 changed files with 394 additions and 210 deletions
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
|
@ -3,7 +3,7 @@ name: "\U0001F41B Bug Report"
|
||||||
about: "If something isn't working as expected."
|
about: "If something isn't working as expected."
|
||||||
labels: bug
|
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**
|
**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)
|
`alacritty --version`: alacritty 0.7.2 (5ac8060b)
|
||||||
|
|
||||||
**Further information**
|
**Further information**
|
||||||
Reproduction steps, noticable behavior, related issues etc
|
Reproduction steps, noticeable behavior, related issues, etc
|
||||||
|
|
|
||||||
10
CHANGELOG.md
10
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: 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)
|
* 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)
|
* 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
|
## [0.19.0] - 2021-10-20
|
||||||
* Fix: Prevent text overwrite when scrolled up (https://github.com/zellij-org/zellij/pull/655)
|
* Fix: Prevent text overwrite when scrolled up (https://github.com/zellij-org/zellij/pull/655)
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ cargo make build
|
||||||
cargo make test
|
cargo make test
|
||||||
# Run Zellij (optionally with additional arguments)
|
# Run Zellij (optionally with additional arguments)
|
||||||
cargo make run
|
cargo make run
|
||||||
cargo make run -- -l strider
|
cargo make run -l strider
|
||||||
# Run Clippy (potentially with additional options)
|
# Run Clippy (potentially with additional options)
|
||||||
cargo make clippy
|
cargo make clippy
|
||||||
cargo make clippy -W clippy::pedantic
|
cargo make clippy -W clippy::pedantic
|
||||||
|
|
|
||||||
20
Cargo.lock
generated
20
Cargo.lock
generated
|
|
@ -351,6 +351,16 @@ dependencies = [
|
||||||
"vec_map",
|
"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]]
|
[[package]]
|
||||||
name = "colored"
|
name = "colored"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
|
|
@ -363,10 +373,10 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colors-transform"
|
name = "colorsys"
|
||||||
version = "0.2.11"
|
version = "0.6.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9226dbc05df4fb986f48d730b001532580883c4c06c5d1c213f4b34c1c157178"
|
checksum = "4dfdf9179d546b55ff3f88c9d93ecfaa3e9760163da5a1080af5243230dbbb70"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "concurrent-queue"
|
name = "concurrent-queue"
|
||||||
|
|
@ -2824,6 +2834,7 @@ dependencies = [
|
||||||
name = "zellij"
|
name = "zellij"
|
||||||
version = "0.20.0"
|
version = "0.20.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"insta",
|
"insta",
|
||||||
"log",
|
"log",
|
||||||
"names",
|
"names",
|
||||||
|
|
@ -2857,6 +2868,7 @@ dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"cassowary",
|
"cassowary",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"close_fds",
|
||||||
"daemonize",
|
"daemonize",
|
||||||
"darwin-libproc",
|
"darwin-libproc",
|
||||||
"highway",
|
"highway",
|
||||||
|
|
@ -2895,7 +2907,7 @@ dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bincode",
|
"bincode",
|
||||||
"colors-transform",
|
"colorsys",
|
||||||
"crossbeam",
|
"crossbeam",
|
||||||
"directories-next",
|
"directories-next",
|
||||||
"interprocess",
|
"interprocess",
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,12 @@ repository = "https://github.com/zellij-org/zellij"
|
||||||
homepage = "https://zellij.dev"
|
homepage = "https://zellij.dev"
|
||||||
include = ["src/**/*", "assets/plugins/*", "assets/layouts/*", "assets/config/*", "LICENSE.md", "README.md", "!**/*_test.*", "!**/tests/**/*"]
|
include = ["src/**/*", "assets/plugins/*", "assets/layouts/*", "assets/config/*", "LICENSE.md", "README.md", "!**/*_test.*", "!**/tests/**/*"]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
rust-version = "1.56"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0"
|
||||||
names = "0.11.0"
|
names = "0.11.0"
|
||||||
zellij-client = { path = "zellij-client/", version = "0.20.0" }
|
zellij-client = { path = "zellij-client/", version = "0.20.0" }
|
||||||
zellij-server = { path = "zellij-server/", version = "0.20.0" }
|
zellij-server = { path = "zellij-server/", version = "0.20.0" }
|
||||||
|
|
|
||||||
59
src/main.rs
59
src/main.rs
|
|
@ -5,8 +5,9 @@ mod tests;
|
||||||
|
|
||||||
use crate::install::populate_data_dir;
|
use crate::install::populate_data_dir;
|
||||||
use sessions::{
|
use sessions::{
|
||||||
assert_session, assert_session_ne, get_active_session, get_sessions, kill_session,
|
assert_session, assert_session_ne, get_active_session, get_sessions,
|
||||||
list_sessions, print_sessions, session_exists, ActiveSession,
|
get_sessions_sorted_by_creation_date, kill_session, list_sessions, print_sessions,
|
||||||
|
print_sessions_with_index, session_exists, ActiveSession,
|
||||||
};
|
};
|
||||||
use std::process;
|
use std::process;
|
||||||
use zellij_client::{os_input_output::get_client_os_input, start_client, ClientInfo};
|
use zellij_client::{os_input_output::get_client_os_input, start_client, ClientInfo};
|
||||||
|
|
@ -62,7 +63,7 @@ pub fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Error occured: {:?}", e);
|
eprintln!("Error occurred: {:?}", e);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -111,6 +112,7 @@ pub fn main() {
|
||||||
if let Some(Command::Sessions(Sessions::Attach {
|
if let Some(Command::Sessions(Sessions::Attach {
|
||||||
session_name,
|
session_name,
|
||||||
create,
|
create,
|
||||||
|
index,
|
||||||
options,
|
options,
|
||||||
})) = opts.command.clone()
|
})) = opts.command.clone()
|
||||||
{
|
{
|
||||||
|
|
@ -119,14 +121,60 @@ pub fn main() {
|
||||||
None => config_options,
|
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) => {
|
Some(session) => {
|
||||||
if create {
|
if create {
|
||||||
if !session_exists(session).unwrap() {
|
if !session_exists(session).unwrap() {
|
||||||
(ClientInfo::New(session_name.unwrap()), layout)
|
(ClientInfo::New(session_name.unwrap()), layout)
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
ClientInfo::Attach(session_name.unwrap(), config_options.clone()),
|
ClientInfo::Attach(
|
||||||
|
session_name.unwrap(),
|
||||||
|
config_options.clone(),
|
||||||
|
),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -160,6 +208,7 @@ pub fn main() {
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
start_client(
|
start_client(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use std::os::unix::fs::FileTypeExt;
|
use std::os::unix::fs::FileTypeExt;
|
||||||
|
use std::time::SystemTime;
|
||||||
use std::{fs, io, process};
|
use std::{fs, io, process};
|
||||||
use zellij_utils::{
|
use zellij_utils::{
|
||||||
consts::ZELLIJ_SOCK_DIR,
|
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 {
|
fn assert_socket(name: &str) -> bool {
|
||||||
let path = &*ZELLIJ_SOCK_DIR.join(name);
|
let path = &*ZELLIJ_SOCK_DIR.join(name);
|
||||||
match LocalSocketStream::connect(path) {
|
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 {
|
pub(crate) enum ActiveSession {
|
||||||
None,
|
None,
|
||||||
One(String),
|
One(String),
|
||||||
|
|
@ -78,7 +121,7 @@ pub(crate) fn get_active_session() -> ActiveSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Error occured: {:?}", e);
|
eprintln!("Error occurred: {:?}", e);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -91,7 +134,7 @@ pub(crate) fn kill_session(name: &str) {
|
||||||
IpcSenderWithContext::new(stream).send(ClientToServerMsg::KillSession);
|
IpcSenderWithContext::new(stream).send(ClientToServerMsg::KillSession);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Error occured: {:?}", e);
|
eprintln!("Error occurred: {:?}", e);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -108,7 +151,7 @@ pub(crate) fn list_sessions() {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Error occured: {:?}", e);
|
eprintln!("Error occurred: {:?}", e);
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -137,7 +180,7 @@ pub(crate) fn assert_session(name: &str) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Error occured: {:?}", e);
|
eprintln!("Error occurred: {:?}", e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
process::exit(1);
|
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);
|
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);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,9 @@ impl InputHandler {
|
||||||
MouseButton::Left => {
|
MouseButton::Left => {
|
||||||
self.dispatch_action(Action::LeftClick(point));
|
self.dispatch_action(Action::LeftClick(point));
|
||||||
}
|
}
|
||||||
|
MouseButton::Right => {
|
||||||
|
self.dispatch_action(Action::RightClick(point));
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
MouseEvent::Release(point) => {
|
MouseEvent::Release(point) => {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ zellij-utils = { path = "../zellij-utils/", version = "0.20.0" }
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
typetag = "0.1.7"
|
typetag = "0.1.7"
|
||||||
chrono = "0.4.19"
|
chrono = "0.4.19"
|
||||||
|
close_fds = "0.3.2"
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
darwin-libproc = "0.2.0"
|
darwin-libproc = "0.2.0"
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::panes::PaneId;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use darwin_libproc;
|
use darwin_libproc;
|
||||||
|
|
||||||
use std::env;
|
#[cfg(target_os = "linux")]
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
|
use std::env;
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
use std::os::unix::process::CommandExt;
|
use std::os::unix::process::CommandExt;
|
||||||
use std::path::PathBuf;
|
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::fs::File as AsyncFile;
|
||||||
use async_std::os::unix::io::FromRawFd;
|
use async_std::os::unix::io::FromRawFd;
|
||||||
use interprocess::local_socket::LocalSocketStream;
|
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::signal::{kill, Signal};
|
||||||
use nix::sys::termios;
|
use nix::sys::termios;
|
||||||
use nix::sys::wait::waitpid;
|
|
||||||
use nix::unistd::{self, ForkResult};
|
use nix::unistd;
|
||||||
use signal_hook::consts::*;
|
use signal_hook::consts::*;
|
||||||
use zellij_tile::data::Palette;
|
use zellij_tile::data::Palette;
|
||||||
use zellij_utils::{
|
use zellij_utils::{
|
||||||
|
|
@ -31,7 +36,6 @@ use zellij_utils::{
|
||||||
|
|
||||||
use async_std::io::ReadExt;
|
use async_std::io::ReadExt;
|
||||||
pub use async_trait::async_trait;
|
pub use async_trait::async_trait;
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
|
||||||
|
|
||||||
pub use nix::unistd::Pid;
|
pub use nix::unistd::Pid;
|
||||||
|
|
||||||
|
|
@ -97,96 +101,63 @@ fn handle_command_exit(mut child: Child) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_fork_pty(
|
fn handle_openpty(
|
||||||
fork_pty_res: ForkptyResult,
|
open_pty_res: OpenptyResult,
|
||||||
cmd: RunCommand,
|
cmd: RunCommand,
|
||||||
parent_fd: RawFd,
|
quit_cb: Box<dyn Fn(PaneId) + Send>,
|
||||||
child_fd: RawFd,
|
) -> (RawFd, RawFd) {
|
||||||
) -> (RawFd, ChildId) {
|
// primary side of pty and child fd
|
||||||
let pid_primary = fork_pty_res.master;
|
let pid_primary = open_pty_res.master;
|
||||||
let (pid_secondary, pid_shell) = match fork_pty_res.fork_result {
|
let pid_secondary = open_pty_res.slave;
|
||||||
ForkResult::Parent { child } => {
|
|
||||||
let pid_shell = read_from_pipe(parent_fd, child_fd);
|
let mut child = unsafe {
|
||||||
(child, pid_shell)
|
|
||||||
}
|
|
||||||
ForkResult::Child => {
|
|
||||||
let child = unsafe {
|
|
||||||
let command = &mut Command::new(cmd.command);
|
let command = &mut Command::new(cmd.command);
|
||||||
if let Some(current_dir) = cmd.cwd {
|
if let Some(current_dir) = cmd.cwd {
|
||||||
command.current_dir(current_dir);
|
command.current_dir(current_dir);
|
||||||
}
|
}
|
||||||
command
|
command
|
||||||
.args(&cmd.args)
|
.args(&cmd.args)
|
||||||
.pre_exec(|| -> std::io::Result<()> {
|
.pre_exec(move || -> std::io::Result<()> {
|
||||||
// this is the "unsafe" part, for more details please see:
|
if libc::login_tty(pid_secondary) != 0 {
|
||||||
// https://doc.rust-lang.org/std/os/unix/process/trait.CommandExt.html#notes-and-safety
|
panic!("failed to set controlling terminal");
|
||||||
unistd::setpgid(Pid::from_raw(0), Pid::from_raw(0))
|
}
|
||||||
.expect("failed to create a new process group");
|
close_fds::close_open_fds(3, &[]);
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.spawn()
|
.spawn()
|
||||||
.expect("failed to 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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
(
|
let child_id = child.id();
|
||||||
pid_primary,
|
std::thread::spawn(move || {
|
||||||
ChildId {
|
child.wait().unwrap();
|
||||||
primary: pid_secondary,
|
handle_command_exit(child);
|
||||||
shell: pid_shell.map(|pid| Pid::from_raw(pid as i32)),
|
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)
|
/// Spawns a new terminal from the parent terminal with [`termios`](termios::Termios)
|
||||||
/// `orig_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
|
// Create a pipe to allow the child the communicate the shell's pid to it's
|
||||||
// parent.
|
// parent.
|
||||||
let (parent_fd, child_fd) = unistd::pipe().expect("failed to create pipe");
|
match openpty(None, Some(&orig_termios)) {
|
||||||
match forkpty(None, Some(&orig_termios)) {
|
Ok(open_pty_res) => handle_openpty(open_pty_res, cmd, quit_cb),
|
||||||
Ok(fork_pty_res) => handle_fork_pty(fork_pty_res, cmd, parent_fd, child_fd),
|
|
||||||
Err(e) => {
|
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`
|
/// 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
|
/// (or `VISUAL`, if `EDITOR` is not set) will be started in the new terminal, with the given
|
||||||
/// file open.
|
/// file open.
|
||||||
|
|
@ -202,7 +173,8 @@ fn read_from_pipe(parent_fd: RawFd, child_fd: RawFd) -> Option<u32> {
|
||||||
pub fn spawn_terminal(
|
pub fn spawn_terminal(
|
||||||
terminal_action: TerminalAction,
|
terminal_action: TerminalAction,
|
||||||
orig_termios: termios::Termios,
|
orig_termios: termios::Termios,
|
||||||
) -> (RawFd, ChildId) {
|
quit_cb: Box<dyn Fn(PaneId) + Send>,
|
||||||
|
) -> (RawFd, RawFd) {
|
||||||
let cmd = match terminal_action {
|
let cmd = match terminal_action {
|
||||||
TerminalAction::OpenFile(file_to_open) => {
|
TerminalAction::OpenFile(file_to_open) => {
|
||||||
if env::var("EDITOR").is_err() && env::var("VISUAL").is_err() {
|
if env::var("EDITOR").is_err() && env::var("VISUAL").is_err() {
|
||||||
|
|
@ -224,7 +196,7 @@ pub fn spawn_terminal(
|
||||||
TerminalAction::RunCommand(command) => command,
|
TerminalAction::RunCommand(command) => command,
|
||||||
};
|
};
|
||||||
|
|
||||||
handle_terminal(cmd, orig_termios)
|
handle_terminal(cmd, orig_termios, quit_cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[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
|
/// 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
|
/// descriptor of the forked psuedo terminal and a [ChildId] struct containing process id's for
|
||||||
/// the forked child process.
|
/// 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`.
|
/// 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>;
|
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
|
/// 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);
|
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();
|
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> {
|
fn read_from_tty_stdout(&self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
|
||||||
unistd::read(fd, buf)
|
unistd::read(fd, buf)
|
||||||
|
|
@ -322,8 +302,7 @@ impl ServerOsApi for ServerOsInputOutput {
|
||||||
Box::new((*self).clone())
|
Box::new((*self).clone())
|
||||||
}
|
}
|
||||||
fn kill(&self, pid: Pid) -> Result<(), nix::Error> {
|
fn kill(&self, pid: Pid) -> Result<(), nix::Error> {
|
||||||
kill(pid, Some(Signal::SIGTERM)).unwrap();
|
let _ = kill(pid, Some(Signal::SIGTERM));
|
||||||
waitpid(pid, None).unwrap();
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn force_kill(&self, pid: Pid) -> Result<(), nix::Error> {
|
fn force_kill(&self, pid: Pid) -> Result<(), nix::Error> {
|
||||||
|
|
|
||||||
|
|
@ -327,4 +327,12 @@ impl Pane for PluginPane {
|
||||||
fn borderless(&self) -> bool {
|
fn borderless(&self) -> bool {
|
||||||
self.borderless
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
os_input_output::{AsyncReader, ChildId, ServerOsApi},
|
os_input_output::{AsyncReader, ServerOsApi},
|
||||||
panes::PaneId,
|
panes::PaneId,
|
||||||
screen::ScreenInstruction,
|
screen::ScreenInstruction,
|
||||||
thread_bus::{Bus, ThreadSenders},
|
thread_bus::{Bus, ThreadSenders},
|
||||||
|
|
@ -17,6 +17,7 @@ use std::{
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
use zellij_utils::nix::unistd::Pid;
|
||||||
use zellij_utils::{
|
use zellij_utils::{
|
||||||
async_std,
|
async_std,
|
||||||
errors::{get_current_ctx, ContextType, PtyContext},
|
errors::{get_current_ctx, ContextType, PtyContext},
|
||||||
|
|
@ -66,7 +67,7 @@ impl From<&PtyInstruction> for PtyContext {
|
||||||
pub(crate) struct Pty {
|
pub(crate) struct Pty {
|
||||||
pub active_panes: HashMap<ClientId, PaneId>,
|
pub active_panes: HashMap<ClientId, PaneId>,
|
||||||
pub bus: Bus<PtyInstruction>,
|
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,
|
debug_to_file: bool,
|
||||||
task_handles: HashMap<RawFd, JoinHandle<()>>,
|
task_handles: HashMap<RawFd, JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
|
@ -252,15 +253,6 @@ fn stream_terminal_bytes(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async_send_to_screen(senders.clone(), ScreenInstruction::Render).await;
|
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(),
|
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 {
|
TerminalAction::RunCommand(RunCommand {
|
||||||
args: vec![],
|
args: vec![],
|
||||||
command: PathBuf::from(env::var("SHELL").expect("Could not find the SHELL variable")),
|
command: PathBuf::from(env::var("SHELL").expect("Could not find the SHELL variable")),
|
||||||
cwd: client_id
|
cwd: None, // this should be filled by the calling function, eg. spawn_terminal
|
||||||
.and_then(|client_id| self.active_panes.get(&client_id))
|
})
|
||||||
|
}
|
||||||
|
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 {
|
.and_then(|pane| match pane {
|
||||||
PaneId::Plugin(..) => None,
|
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)))
|
.and_then(|id| {
|
||||||
.flatten(),
|
self.bus
|
||||||
|
.os_input
|
||||||
|
.as_ref()
|
||||||
|
.map(|input| input.get_cwd(Pid::from_raw(*id)))
|
||||||
})
|
})
|
||||||
|
.flatten();
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
pub fn spawn_terminal(
|
pub fn spawn_terminal(
|
||||||
&mut self,
|
&mut self,
|
||||||
terminal_action: Option<TerminalAction>,
|
terminal_action: Option<TerminalAction>,
|
||||||
client_or_tab_index: ClientOrTabIndex,
|
client_or_tab_index: ClientOrTabIndex,
|
||||||
) -> RawFd {
|
) -> RawFd {
|
||||||
|
log::info!(
|
||||||
|
"spawn_terminal, client_or_tab_index: {:?}",
|
||||||
|
client_or_tab_index
|
||||||
|
);
|
||||||
let terminal_action = match client_or_tab_index {
|
let terminal_action = match client_or_tab_index {
|
||||||
ClientOrTabIndex::ClientId(client_id) => {
|
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(_) => {
|
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
|
.bus
|
||||||
.os_input
|
.os_input
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.spawn_terminal(terminal_action);
|
.spawn_terminal(terminal_action, quit_cb);
|
||||||
let task_handle = stream_terminal_bytes(
|
let task_handle = stream_terminal_bytes(
|
||||||
pid_primary,
|
pid_primary,
|
||||||
self.bus.senders.clone(),
|
self.bus.senders.clone(),
|
||||||
|
|
@ -315,7 +333,7 @@ impl Pty {
|
||||||
self.debug_to_file,
|
self.debug_to_file,
|
||||||
);
|
);
|
||||||
self.task_handles.insert(pid_primary, task_handle);
|
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
|
pid_primary
|
||||||
}
|
}
|
||||||
pub fn spawn_terminals_for_layout(
|
pub fn spawn_terminals_for_layout(
|
||||||
|
|
@ -324,27 +342,37 @@ impl Pty {
|
||||||
default_shell: Option<TerminalAction>,
|
default_shell: Option<TerminalAction>,
|
||||||
client_id: ClientId,
|
client_id: ClientId,
|
||||||
) {
|
) {
|
||||||
let default_shell =
|
let mut default_shell = default_shell.unwrap_or_else(|| self.get_default_terminal());
|
||||||
default_shell.unwrap_or_else(|| self.get_default_terminal(Some(client_id)));
|
self.fill_cwd(&mut default_shell, client_id);
|
||||||
let extracted_run_instructions = layout.extract_run_instructions();
|
let extracted_run_instructions = layout.extract_run_instructions();
|
||||||
let mut new_pane_pids = vec![];
|
let mut new_pane_pids = vec![];
|
||||||
for run_instruction in extracted_run_instructions {
|
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 {
|
match run_instruction {
|
||||||
Some(Run::Command(command)) => {
|
Some(Run::Command(command)) => {
|
||||||
let cmd = TerminalAction::RunCommand(command);
|
let cmd = TerminalAction::RunCommand(command);
|
||||||
let (pid_primary, child_id): (RawFd, ChildId) =
|
let (pid_primary, child_fd): (RawFd, RawFd) = self
|
||||||
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
|
|
||||||
.bus
|
.bus
|
||||||
.os_input
|
.os_input
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.spawn_terminal(default_shell.clone());
|
.spawn_terminal(cmd, quit_cb);
|
||||||
self.id_to_child_pid.insert(pid_primary, child_id);
|
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);
|
new_pane_pids.push(pid_primary);
|
||||||
}
|
}
|
||||||
// Investigate moving plugin loading to here.
|
// Investigate moving plugin loading to here.
|
||||||
|
|
@ -372,27 +400,15 @@ impl Pty {
|
||||||
pub fn close_pane(&mut self, id: PaneId) {
|
pub fn close_pane(&mut self, id: PaneId) {
|
||||||
match id {
|
match id {
|
||||||
PaneId::Terminal(id) => {
|
PaneId::Terminal(id) => {
|
||||||
let pids = self.id_to_child_pid.remove(&id).unwrap();
|
let child_fd = self.id_to_child_pid.remove(&id).unwrap();
|
||||||
let handle = self.task_handles.remove(&id).unwrap();
|
self.task_handles.remove(&id).unwrap();
|
||||||
task::block_on(async {
|
task::block_on(async {
|
||||||
self.bus
|
self.bus
|
||||||
.os_input
|
.os_input
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.kill(pids.primary)
|
.kill(Pid::from_raw(child_fd))
|
||||||
.unwrap();
|
.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(
|
PaneId::Plugin(pid) => drop(
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,17 @@ fn route_action(
|
||||||
.send_to_screen(ScreenInstruction::WriteCharacter(val, client_id))
|
.send_to_screen(ScreenInstruction::WriteCharacter(val, client_id))
|
||||||
.unwrap();
|
.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) => {
|
Action::SwitchToMode(mode) => {
|
||||||
let palette = session.palette;
|
let palette = session.palette;
|
||||||
// TODO: use the palette from the client and remove it from the server os api
|
// 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))
|
.send_to_screen(ScreenInstruction::LeftClick(point, client_id))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
Action::RightClick(point) => {
|
||||||
|
session
|
||||||
|
.senders
|
||||||
|
.send_to_screen(ScreenInstruction::RightClick(point, client_id))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
Action::MouseRelease(point) => {
|
Action::MouseRelease(point) => {
|
||||||
session
|
session
|
||||||
.senders
|
.senders
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ pub(crate) enum ScreenInstruction {
|
||||||
TerminalResize(Size),
|
TerminalResize(Size),
|
||||||
ChangeMode(ModeInfo, ClientId),
|
ChangeMode(ModeInfo, ClientId),
|
||||||
LeftClick(Position, ClientId),
|
LeftClick(Position, ClientId),
|
||||||
|
RightClick(Position, ClientId),
|
||||||
MouseRelease(Position, ClientId),
|
MouseRelease(Position, ClientId),
|
||||||
MouseHold(Position, ClientId),
|
MouseHold(Position, ClientId),
|
||||||
Copy(ClientId),
|
Copy(ClientId),
|
||||||
|
|
@ -138,6 +139,7 @@ impl From<&ScreenInstruction> for ScreenContext {
|
||||||
ScreenInstruction::ScrollUpAt(..) => ScreenContext::ScrollUpAt,
|
ScreenInstruction::ScrollUpAt(..) => ScreenContext::ScrollUpAt,
|
||||||
ScreenInstruction::ScrollDownAt(..) => ScreenContext::ScrollDownAt,
|
ScreenInstruction::ScrollDownAt(..) => ScreenContext::ScrollDownAt,
|
||||||
ScreenInstruction::LeftClick(..) => ScreenContext::LeftClick,
|
ScreenInstruction::LeftClick(..) => ScreenContext::LeftClick,
|
||||||
|
ScreenInstruction::RightClick(..) => ScreenContext::RightClick,
|
||||||
ScreenInstruction::MouseRelease(..) => ScreenContext::MouseRelease,
|
ScreenInstruction::MouseRelease(..) => ScreenContext::MouseRelease,
|
||||||
ScreenInstruction::MouseHold(..) => ScreenContext::MouseHold,
|
ScreenInstruction::MouseHold(..) => ScreenContext::MouseHold,
|
||||||
ScreenInstruction::Copy(..) => ScreenContext::Copy,
|
ScreenInstruction::Copy(..) => ScreenContext::Copy,
|
||||||
|
|
@ -973,6 +975,14 @@ pub(crate) fn screen_thread_main(
|
||||||
|
|
||||||
screen.render();
|
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) => {
|
ScreenInstruction::MouseRelease(point, client_id) => {
|
||||||
screen
|
screen
|
||||||
.get_active_tab_mut(client_id)
|
.get_active_tab_mut(client_id)
|
||||||
|
|
|
||||||
|
|
@ -269,6 +269,7 @@ pub trait Pane {
|
||||||
fn set_boundary_color(&mut self, _color: Option<PaletteColor>) {}
|
fn set_boundary_color(&mut self, _color: Option<PaletteColor>) {}
|
||||||
fn set_borderless(&mut self, borderless: bool);
|
fn set_borderless(&mut self, borderless: bool);
|
||||||
fn borderless(&self) -> bool;
|
fn borderless(&self) -> bool;
|
||||||
|
fn handle_right_click(&mut self, _to: &Position) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! resize_pty {
|
macro_rules! resize_pty {
|
||||||
|
|
@ -2586,6 +2587,14 @@ impl Tab {
|
||||||
pane.start_selection(&relative_position);
|
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) {
|
fn focus_pane_at(&mut self, point: &Position) {
|
||||||
if let Some(clicked_pane) = self.get_pane_id_at(point, true) {
|
if let Some(clicked_pane) = self.get_pane_id_at(point, true) {
|
||||||
self.active_terminal = Some(clicked_pane);
|
self.active_terminal = Some(clicked_pane);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
use super::{Screen, ScreenInstruction};
|
use super::{Screen, ScreenInstruction};
|
||||||
|
use crate::panes::PaneId;
|
||||||
use crate::zellij_tile::data::{ModeInfo, Palette};
|
use crate::zellij_tile::data::{ModeInfo, Palette};
|
||||||
use crate::{
|
use crate::{
|
||||||
os_input_output::{AsyncReader, ChildId, Pid, ServerOsApi},
|
os_input_output::{AsyncReader, Pid, ServerOsApi},
|
||||||
thread_bus::Bus,
|
thread_bus::Bus,
|
||||||
ClientId,
|
ClientId,
|
||||||
};
|
};
|
||||||
|
|
@ -29,7 +30,11 @@ impl ServerOsApi for FakeInputOutput {
|
||||||
fn set_terminal_size_using_fd(&self, _fd: RawFd, _cols: u16, _rows: u16) {
|
fn set_terminal_size_using_fd(&self, _fd: RawFd, _cols: u16, _rows: u16) {
|
||||||
// noop
|
// 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!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result<usize, nix::Error> {
|
fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result<usize, nix::Error> {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use super::Tab;
|
use super::Tab;
|
||||||
use crate::zellij_tile::data::{ModeInfo, Palette};
|
use crate::zellij_tile::data::{ModeInfo, Palette};
|
||||||
use crate::{
|
use crate::{
|
||||||
os_input_output::{AsyncReader, ChildId, Pid, ServerOsApi},
|
os_input_output::{AsyncReader, Pid, ServerOsApi},
|
||||||
panes::PaneId,
|
panes::PaneId,
|
||||||
thread_bus::ThreadSenders,
|
thread_bus::ThreadSenders,
|
||||||
ClientId,
|
ClientId,
|
||||||
|
|
@ -29,7 +29,11 @@ impl ServerOsApi for FakeInputOutput {
|
||||||
fn set_terminal_size_using_fd(&self, _fd: RawFd, _cols: u16, _rows: u16) {
|
fn set_terminal_size_using_fd(&self, _fd: RawFd, _cols: u16, _rows: u16) {
|
||||||
// noop
|
// 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!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result<usize, nix::Error> {
|
fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result<usize, nix::Error> {
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ pub enum Mouse {
|
||||||
ScrollUp(usize), // number of lines
|
ScrollUp(usize), // number of lines
|
||||||
ScrollDown(usize), // number of lines
|
ScrollDown(usize), // number of lines
|
||||||
LeftClick(isize, usize), // line and column
|
LeftClick(isize, usize), // line and column
|
||||||
|
RightClick(isize, usize), // line and column
|
||||||
Hold(isize, usize), // line and column
|
Hold(isize, usize), // line and column
|
||||||
Release(Option<(isize, usize)>), // line and column
|
Release(Option<(isize, usize)>), // line and column
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ license = "MIT"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
backtrace = "0.3.55"
|
backtrace = "0.3.55"
|
||||||
bincode = "1.3.1"
|
bincode = "1.3.1"
|
||||||
colors-transform = "0.2.5"
|
colorsys = "0.6.5"
|
||||||
crossbeam = "0.8.0"
|
crossbeam = "0.8.0"
|
||||||
directories-next = "2.0"
|
directories-next = "2.0"
|
||||||
interprocess = "1.1.1"
|
interprocess = "1.1.1"
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,10 @@ pub enum Sessions {
|
||||||
#[structopt(short, long)]
|
#[structopt(short, long)]
|
||||||
create: bool,
|
create: bool,
|
||||||
|
|
||||||
|
/// Number of the session index in the active sessions ordered creation date.
|
||||||
|
#[structopt(long)]
|
||||||
|
index: Option<usize>,
|
||||||
|
|
||||||
/// Change the behaviour of zellij
|
/// Change the behaviour of zellij
|
||||||
#[structopt(subcommand, name = "options")]
|
#[structopt(subcommand, name = "options")]
|
||||||
options: Option<SessionCommand>,
|
options: Option<SessionCommand>,
|
||||||
|
|
|
||||||
|
|
@ -258,6 +258,7 @@ pub enum ScreenContext {
|
||||||
TerminalResize,
|
TerminalResize,
|
||||||
ChangeMode,
|
ChangeMode,
|
||||||
LeftClick,
|
LeftClick,
|
||||||
|
RightClick,
|
||||||
MouseRelease,
|
MouseRelease,
|
||||||
MouseHold,
|
MouseHold,
|
||||||
Copy,
|
Copy,
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ pub enum Action {
|
||||||
Quit,
|
Quit,
|
||||||
/// Write to the terminal.
|
/// Write to the terminal.
|
||||||
Write(Vec<u8>),
|
Write(Vec<u8>),
|
||||||
|
/// Write Characters to the terminal.
|
||||||
|
WriteChars(String),
|
||||||
/// Switch to the specified input mode.
|
/// Switch to the specified input mode.
|
||||||
SwitchToMode(InputMode),
|
SwitchToMode(InputMode),
|
||||||
/// Resize focus pane in specified direction.
|
/// Resize focus pane in specified direction.
|
||||||
|
|
@ -85,6 +87,7 @@ pub enum Action {
|
||||||
/// Detach session and exit
|
/// Detach session and exit
|
||||||
Detach,
|
Detach,
|
||||||
LeftClick(Position),
|
LeftClick(Position),
|
||||||
|
RightClick(Position),
|
||||||
MouseRelease(Position),
|
MouseRelease(Position),
|
||||||
MouseHold(Position),
|
MouseHold(Position),
|
||||||
Copy,
|
Copy,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use std::fs::File;
|
||||||
use std::io::{self, Read};
|
use std::io::{self, Read};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Deserialize;
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
|
||||||
use super::keybinds::{Keybinds, KeybindsFromYaml};
|
use super::keybinds::{Keybinds, KeybindsFromYaml};
|
||||||
|
|
@ -20,7 +20,7 @@ const DEFAULT_CONFIG_FILE_NAME: &str = "config.yaml";
|
||||||
type ConfigResult = Result<Config, ConfigError>;
|
type ConfigResult = Result<Config, ConfigError>;
|
||||||
|
|
||||||
/// Intermediate deserialization config struct
|
/// Intermediate deserialization config struct
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
pub struct ConfigFromYaml {
|
pub struct ConfigFromYaml {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub options: Option<Options>,
|
pub options: Option<Options>,
|
||||||
|
|
@ -31,7 +31,7 @@ pub struct ConfigFromYaml {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Main configuration.
|
/// Main configuration.
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub keybinds: Keybinds,
|
pub keybinds: Keybinds,
|
||||||
pub options: Options,
|
pub options: Options,
|
||||||
|
|
@ -106,7 +106,8 @@ impl TryFrom<&CliArgs> for Config {
|
||||||
impl Config {
|
impl Config {
|
||||||
/// Uses defaults, but lets config override them.
|
/// Uses defaults, but lets config override them.
|
||||||
pub fn from_yaml(yaml_config: &str) -> ConfigResult {
|
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) => {
|
Err(e) => {
|
||||||
// needs direct check, as `[ErrorImpl]` is private
|
// needs direct check, as `[ErrorImpl]` is private
|
||||||
// https://github.com/dtolnay/serde-yaml/issues/121
|
// https://github.com/dtolnay/serde-yaml/issues/121
|
||||||
|
|
@ -118,20 +119,9 @@ impl Config {
|
||||||
Ok(config) => config,
|
Ok(config) => config,
|
||||||
};
|
};
|
||||||
|
|
||||||
match config_from_yaml {
|
match maybe_config_from_yaml {
|
||||||
None => Ok(Config::default()),
|
None => Ok(Config::default()),
|
||||||
Some(config) => {
|
Some(config) => config.try_into(),
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -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
|
// TODO: Split errors up into separate modules
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LayoutNameInTabError;
|
pub struct LayoutNameInTabError;
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ impl Display for ExitReason {
|
||||||
f,
|
f,
|
||||||
"Session attached to another client. Use --force flag to force connect."
|
"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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use std::{iter, str::from_utf8};
|
use std::{iter, str::from_utf8};
|
||||||
|
|
||||||
use colors_transform::{Color, Rgb};
|
use colorsys::Rgb;
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::{fs, io};
|
use std::{fs, io};
|
||||||
|
|
@ -52,10 +52,9 @@ pub mod colors {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _hex_to_rgb(hex: &str) -> (u8, u8, u8) {
|
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")
|
.expect("The passed argument must be a valid hex color")
|
||||||
.as_tuple();
|
.into()
|
||||||
(rgb.0 as u8, rgb.1 as u8, rgb.2 as u8)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default_palette() -> Palette {
|
pub fn default_palette() -> Palette {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue