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."
|
||||
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
|
||||
|
|
|
|||
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: 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)
|
||||
|
|
|
|||
|
|
@ -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
20
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
119
src/main.rs
119
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 {
|
||||
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(),
|
||||
),
|
||||
None,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
assert_session(session);
|
||||
(
|
||||
ClientInfo::Attach(session_name.unwrap(), config_options.clone()),
|
||||
None,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
assert_session(session);
|
||||
(
|
||||
ClientInfo::Attach(session_name.unwrap(), config_options.clone()),
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
None => match get_active_session() {
|
||||
ActiveSession::None => {
|
||||
if create {
|
||||
(
|
||||
ClientInfo::New(names::Generator::default().next().unwrap()),
|
||||
layout,
|
||||
)
|
||||
} else {
|
||||
println!("No active zellij sessions found.");
|
||||
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);
|
||||
}
|
||||
}
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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<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);
|
||||
}
|
||||
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<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> {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
.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<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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -258,6 +258,7 @@ pub enum ScreenContext {
|
|||
TerminalResize,
|
||||
ChangeMode,
|
||||
LeftClick,
|
||||
RightClick,
|
||||
MouseRelease,
|
||||
MouseHold,
|
||||
Copy,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue