diff options
author | Kunal Mohan <44079328+kunalmohan@users.noreply.github.com> | 2021-05-25 17:30:40 +0530 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-05-25 17:30:40 +0530 |
commit | 2bca7e007aad9a74e6c48191b8a7a3edf85808d4 (patch) | |
tree | 61366b1e29601ad003fc53a715f4b925d8ef5871 | |
parent | 6755b6a88defe30c63fa3bc6d25aa89e545180c9 (diff) | |
parent | df6d6cb3a70cbea9d088c7af47f3540896b98089 (diff) |
Merge pull request #531 from zellij-org/detach-sessions
Feature: Detachable/Persistent sessions
-rw-r--r-- | Cargo.lock | 3 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | assets/config/default.yaml | 27 | ||||
-rw-r--r-- | default-plugins/status-bar/src/first_line.rs | 68 | ||||
-rw-r--r-- | src/main.rs | 45 | ||||
-rw-r--r-- | src/sessions.rs | 109 | ||||
-rw-r--r-- | src/tests/fakes.rs | 2 | ||||
-rw-r--r-- | src/tests/mod.rs | 4 | ||||
-rw-r--r-- | zellij-client/src/input_handler.rs | 8 | ||||
-rw-r--r-- | zellij-client/src/lib.rs | 111 | ||||
-rw-r--r-- | zellij-server/src/lib.rs | 162 | ||||
-rw-r--r-- | zellij-server/src/os_input_output.rs | 36 | ||||
-rw-r--r-- | zellij-server/src/route.rs | 55 | ||||
-rw-r--r-- | zellij-server/src/screen.rs | 24 | ||||
-rw-r--r-- | zellij-server/src/tab.rs | 12 | ||||
-rw-r--r-- | zellij-tile/src/data.rs | 3 | ||||
-rw-r--r-- | zellij-utils/Cargo.toml | 2 | ||||
-rw-r--r-- | zellij-utils/src/cli.rs | 33 | ||||
-rw-r--r-- | zellij-utils/src/consts.rs | 15 | ||||
-rw-r--r-- | zellij-utils/src/errors.rs | 2 | ||||
-rw-r--r-- | zellij-utils/src/input/actions.rs | 2 | ||||
-rw-r--r-- | zellij-utils/src/input/config.rs | 6 | ||||
-rw-r--r-- | zellij-utils/src/input/mod.rs | 3 | ||||
-rw-r--r-- | zellij-utils/src/input/options.rs | 6 | ||||
-rw-r--r-- | zellij-utils/src/ipc.rs | 36 |
25 files changed, 593 insertions, 182 deletions
diff --git a/Cargo.lock b/Cargo.lock index 0dda83734..596062e17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2301,6 +2301,7 @@ name = "zellij" version = "0.12.0" dependencies = [ "insta", + "names", "zellij-client", "zellij-server", "zellij-utils", @@ -2358,8 +2359,8 @@ dependencies = [ "interprocess", "lazy_static", "libc", - "names", "nix", + "once_cell", "serde", "serde_yaml", "signal-hook 0.3.8", diff --git a/Cargo.toml b/Cargo.toml index 9e7e31ab4..5f7afaaa6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ resolver = "2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +names = "0.11.0" zellij-client = { path = "zellij-client/", version = "0.12.0" } zellij-server = { path = "zellij-server/", version = "0.12.0" } zellij-utils = { path = "zellij-utils/", version = "0.12.0" } diff --git a/assets/config/default.yaml b/assets/config/default.yaml index 4582e79f6..42bc16886 100644 --- a/assets/config/default.yaml +++ b/assets/config/default.yaml @@ -12,6 +12,8 @@ keybinds: key: [Ctrl: 't',] - action: [SwitchToMode: Scroll,] key: [Ctrl: 's',] + - action: [SwitchToMode: Session,] + key: [Ctrl: 'o',] - action: [Quit,] key: [Ctrl: 'q',] - action: [NewPane: ] @@ -42,6 +44,8 @@ keybinds: key: [Ctrl: 'r', Char: "\n", Char: ' ',] - action: [SwitchToMode: Scroll,] key: [Ctrl: 's'] + - action: [SwitchToMode: Session,] + key: [Ctrl: 'o',] - action: [Quit] key: [Ctrl: 'q'] - action: [Resize: Left,] @@ -77,6 +81,8 @@ keybinds: key: [Ctrl: 'p', Char: "\n", Char: ' ',] - action: [SwitchToMode: Scroll,] key: [Ctrl: 's'] + - action: [SwitchToMode: Session,] + key: [Ctrl: 'o',] - action: [Quit,] key: [Ctrl: 'q',] - action: [MoveFocus: Left,] @@ -114,6 +120,8 @@ keybinds: key: [Ctrl: 't', Char: "\n", Char: ' ',] - action: [SwitchToMode: Scroll,] key: [Ctrl: 's'] + - action: [SwitchToMode: Session,] + key: [Ctrl: 'o',] - action: [SwitchToMode: RenameTab, TabNameInput: [0],] key: [Char: 'r'] - action: [Quit,] @@ -168,6 +176,8 @@ keybinds: key: [Ctrl: 'g',] - action: [SwitchToMode: Pane,] key: [Ctrl: 'p',] + - action: [SwitchToMode: Session,] + key: [Ctrl: 'o',] - action: [Quit,] key: [Ctrl: 'q',] - action: [ScrollDown,] @@ -213,3 +223,20 @@ keybinds: key: [ Alt: '[',] - action: [FocusNextPane,] key: [ Alt: ']',] + session: + - action: [SwitchToMode: Locked,] + key: [Ctrl: 'g'] + - action: [SwitchToMode: Resize,] + key: [Ctrl: 'r',] + - action: [SwitchToMode: Pane,] + key: [Ctrl: 'p',] + - action: [SwitchToMode: Tab,] + key: [Ctrl: 't',] + - action: [SwitchToMode: Normal,] + key: [Ctrl: 'o', Char: "\n", Char: ' ',] + - action: [SwitchToMode: Scroll,] + key: [Ctrl: 's'] + - action: [Quit,] + key: [Ctrl: 'q',] + - action: [Detach,] + key: [Char: 'd',] diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 37175e3fa..7a678cdc4 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -22,6 +22,7 @@ enum CtrlKeyAction { Resize, Scroll, Quit, + Session, } enum CtrlKeyMode { @@ -39,16 +40,7 @@ impl CtrlKeyShortcut { CtrlKeyAction::Resize => String::from("RESIZE"), CtrlKeyAction::Scroll => String::from("SCROLL"), CtrlKeyAction::Quit => String::from("QUIT"), - } - } - pub fn shortened_text(&self) -> String { - match self.action { - CtrlKeyAction::Lock => String::from("LOCK"), - CtrlKeyAction::Pane => String::from("ane"), - CtrlKeyAction::Tab => String::from("ab"), - CtrlKeyAction::Resize => String::from("esize"), - CtrlKeyAction::Scroll => String::from("croll"), - CtrlKeyAction::Quit => String::from("uit"), + CtrlKeyAction::Session => String::from("SESSION"), } } pub fn letter_shortcut(&self) -> char { @@ -59,6 +51,7 @@ impl CtrlKeyShortcut { CtrlKeyAction::Resize => 'r', CtrlKeyAction::Scroll => 's', CtrlKeyAction::Quit => 'q', + CtrlKeyAction::Session => 'o', } } } @@ -193,32 +186,6 @@ fn full_ctrl_key(key: &CtrlKeyShortcut, palette: ColoredElements, separator: &st } } -fn shortened_ctrl_key( - key: &CtrlKeyShortcut, - palette: ColoredElements, - separator: &str, -) -> LinePart { - let shortened_text = key.shortened_text(); - let letter_shortcut = key.letter_shortcut(); - let shortened_text = match key.action { - CtrlKeyAction::Lock => format!(" {}", shortened_text), - _ => shortened_text, - }; - match key.mode { - CtrlKeyMode::Unselected => { - unselected_mode_shortcut(letter_shortcut, &shortened_text, palette, separator) - } - CtrlKeyMode::Selected => { - selected_mode_shortcut(letter_shortcut, &shortened_text, palette, separator) - } - CtrlKeyMode::Disabled => disabled_mode_shortcut( - &format!(" <{}>{}", letter_shortcut, shortened_text), - palette, - separator, - ), - } -} - fn single_letter_ctrl_key( key: &CtrlKeyShortcut, palette: ColoredElements, @@ -255,15 +222,6 @@ fn key_indicators( } line_part = LinePart::default(); for ctrl_key in keys { - let key = shortened_ctrl_key(ctrl_key, palette, separator); - line_part.part = format!("{}{}", line_part.part, key.part); - line_part.len += key.len; - } - if line_part.len < max_len { - return line_part; - } - line_part = LinePart::default(); - for ctrl_key in keys { let key = single_letter_ctrl_key(ctrl_key, palette, separator); line_part.part = format!("{}{}", line_part.part, key.part); line_part.len += key.len; @@ -296,6 +254,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Quit), ], colored_elements, @@ -309,6 +268,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), ], colored_elements, @@ -322,6 +282,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), ], colored_elements, @@ -335,6 +296,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), ], colored_elements, @@ -348,6 +310,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), ], colored_elements, @@ -361,6 +324,21 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), + ], + colored_elements, + separator, + ), + InputMode::Session => key_indicators( + max_len, + &[ + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Lock), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Pane), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Session), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), ], colored_elements, diff --git a/src/main.rs b/src/main.rs index a970ce446..67b833988 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,14 @@ +mod sessions; #[cfg(test)] mod tests; +use sessions::{assert_session, assert_session_ne, list_sessions}; use std::convert::TryFrom; -use zellij_client::{os_input_output::get_client_os_input, start_client}; +use std::process; +use zellij_client::{os_input_output::get_client_os_input, start_client, ClientInfo}; use zellij_server::{os_input_output::get_server_os_input, start_server}; use zellij_utils::{ - cli::{CliArgs, ConfigCli}, + cli::{CliArgs, Command, Sessions}, consts::{ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR}, input::config::Config, logging::*, @@ -16,15 +19,17 @@ use zellij_utils::{ pub fn main() { let opts = CliArgs::from_args(); - if let Some(ConfigCli::Setup(setup)) = opts.option.clone() { - Setup::from_cli(&setup, &opts).expect("Failed to print to stdout"); + if let Some(Command::Sessions(Sessions::ListSessions)) = opts.command { + list_sessions(); + } else if let Some(Command::Setup(ref setup)) = opts.command { + Setup::from_cli(setup, &opts).expect("Failed to print to stdout"); } let config = match Config::try_from(&opts) { Ok(config) => config, Err(e) => { eprintln!("There was an error in the config file:\n{}", e); - std::process::exit(1); + process::exit(1); } }; atomic_create_dir(&*ZELLIJ_TMP_DIR).unwrap(); @@ -34,7 +39,7 @@ pub fn main() { Ok(server_os_input) => server_os_input, Err(e) => { eprintln!("failed to open terminal:\n{}", e); - std::process::exit(1); + process::exit(1); } }; start_server(Box::new(os_input), path); @@ -43,9 +48,33 @@ pub fn main() { Ok(os_input) => os_input, Err(e) => { eprintln!("failed to open terminal:\n{}", e); - std::process::exit(1); + process::exit(1); } }; - start_client(Box::new(os_input), opts, config); + if let Some(Command::Sessions(Sessions::Attach { + session_name, + force, + })) = opts.command.clone() + { + assert_session(&session_name); + start_client( + Box::new(os_input), + opts, + config, + ClientInfo::Attach(session_name, force), + ); + } else { + let session_name = opts + .session + .clone() + .unwrap_or_else(|| names::Generator::default().next().unwrap()); + assert_session_ne(&session_name); + start_client( + Box::new(os_input), + opts, + config, + ClientInfo::New(session_name), + ); + } } } diff --git a/src/sessions.rs b/src/sessions.rs new file mode 100644 index 000000000..fd834e408 --- /dev/null +++ b/src/sessions.rs @@ -0,0 +1,109 @@ +use std::os::unix::fs::FileTypeExt; +use std::{fs, io, process}; +use zellij_utils::{ + consts::ZELLIJ_SOCK_DIR, + interprocess::local_socket::LocalSocketStream, + ipc::{ClientToServerMsg, IpcSenderWithContext}, +}; + +fn get_sessions() -> Result<Vec<String>, io::ErrorKind> { + match fs::read_dir(&*ZELLIJ_SOCK_DIR) { + Ok(files) => { + let mut sessions = Vec::new(); + files.for_each(|file| { + let file = file.unwrap(); + let file_name = file.file_name().into_string().unwrap(); + if file.file_type().unwrap().is_socket() && assert_socket(&file_name) { + sessions.push(file_name); + } + }); + Ok(sessions) + } + Err(err) => { + if let io::ErrorKind::NotFound = err.kind() { + Ok(Vec::with_capacity(0)) + } else { + Err(err.kind()) + } + } + } +} + +pub(crate) fn list_sessions() { + let exit_code = match get_sessions() { + Ok(sessions) => { + if sessions.is_empty() { + println!("No active zellij sessions found."); + } else { + let curr_session = + std::env::var("ZELLIJ_SESSION_NAME").unwrap_or_else(|_| "".into()); + sessions.iter().for_each(|session| { + let suffix = if curr_session == *session { + " (current)" + } else { + "" + }; + println!("{}{}", session, suffix); + }) + } + 0 + } + Err(e) => { + eprintln!("Error occured: {:?}", e); + 1 + } + }; + process::exit(exit_code); +} + +pub(crate) fn assert_session(name: &str) { + let exit_code = match get_sessions() { + Ok(sessions) => { + if sessions.iter().any(|s| s == name) { + return; + } + println!("No session named {:?} found.", name); + 0 + } + Err(e) => { + eprintln!("Error occured: {:?}", e); + 1 + } + }; + process::exit(exit_code); +} + +pub(crate) fn assert_session_ne(name: &str) { + let exit_code = match get_sessions() { + Ok(sessions) => { + if sessions.iter().all(|s| s != name) { + return; + } + println!("Session with name {:?} aleady exists. Use attach command to connect to it or specify a different name.", name); + 0 + } + Err(e) => { + eprintln!("Error occured: {:?}", e); + 1 + } + }; + process::exit(exit_code); +} + +fn assert_socket(name: &str) -> bool { + let path = &*ZELLIJ_SOCK_DIR.join(name); + match LocalSocketStream::connect(path) { + Ok(stream) => { + IpcSenderWithContext::new(stream).send(ClientToServerMsg::ClientExited); + true + } + Err(e) => { + if e.kind() == io::ErrorKind::ConnectionRefused { + drop(fs::remove_file(path)); + false + } else { + true + } + } + } +} diff --git a/src/tests/fakes.rs b/src/tests/fakes.rs index a2f338ba6..23f9f1824 100644 --- a/src/tests/fakes.rs +++ b/src/tests/fakes.rs @@ -333,6 +333,8 @@ impl ServerOsApi for FakeInputOutput { self.send_instructions_to_client.send(msg).unwrap(); } fn add_client_sender(&self) {} + fn remove_client_sender(&self) {} + fn send_to_temp_client(&self, _msg: ServerToClientMsg) {} fn update_receiver(&mut self, _stream: LocalSocketStream) {} fn load_palette(&self) -> Palette { default_palette() diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 3e548db7c..e075ed49a 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -5,7 +5,7 @@ pub mod tty_inputs; pub mod utils; use std::path::PathBuf; -use zellij_client::{os_input_output::ClientOsApi, start_client}; +use zellij_client::{os_input_output::ClientOsApi, start_client, ClientInfo}; use zellij_server::{os_input_output::ServerOsApi, start_server}; use zellij_utils::{cli::CliArgs, input::config::Config}; @@ -21,6 +21,6 @@ pub fn start( start_server(server_os_input, PathBuf::from("")); }) .unwrap(); - start_client(client_os_input, opts, config); + start_client(client_os_input, opts, config, ClientInfo::New("".into())); let _ = server_thread.join(); } diff --git a/zellij-client/src/input_handler.rs b/zellij-client/src/input_handler.rs index 4e2cff94c..70f6e9824 100644 --- a/zellij-client/src/input_handler.rs +++ b/zellij-client/src/input_handler.rs @@ -7,7 +7,7 @@ use zellij_utils::{ channels::{SenderWithContext, OPENCALLS}, errors::ContextType, input::{actions::Action, cast_termion_key, config::Config, keybinds::Keybinds}, - ipc::ClientToServerMsg, + ipc::{ClientToServerMsg, ExitReason}, }; use termion::input::TermReadEventsAndRaw; @@ -132,7 +132,9 @@ impl InputHandler { let mut should_break = false; match action { - Action::Quit => { + Action::Quit | Action::Detach => { + self.os_input + .send_to_server(ClientToServerMsg::Action(action)); self.exit(); should_break = true; } @@ -167,7 +169,7 @@ impl InputHandler { /// same as quitting Zellij). fn exit(&mut self) { self.send_client_instructions - .send(ClientInstruction::Exit) + .send(ClientInstruction::Exit(ExitReason::Normal)) .unwrap(); } } diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index 9deb415e6..f3b0f11d3 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -17,30 +17,27 @@ use crate::{ use zellij_utils::cli::CliArgs; use zellij_utils::{ channels::{SenderType, SenderWithContext, SyncChannelWithContext}, - consts::ZELLIJ_IPC_PIPE, + consts::{SESSION_NAME, ZELLIJ_IPC_PIPE}, errors::{ClientContext, ContextType, ErrorInstruction}, - input::config::Config, - input::options::Options, - ipc::{ClientAttributes, ClientToServerMsg, ServerToClient |