feat(cli): allow starting a session detached (#3257)
* feat(cli): allow starting a session detached * fix tests
This commit is contained in:
parent
a0f48c6731
commit
e68bc649d6
7 changed files with 109 additions and 13 deletions
|
|
@ -407,6 +407,7 @@ pub(crate) fn start_client(opts: CliArgs) {
|
||||||
let mut config_options = config_options.clone();
|
let mut config_options = config_options.clone();
|
||||||
let mut opts = opts.clone();
|
let mut opts = opts.clone();
|
||||||
let mut is_a_reconnect = false;
|
let mut is_a_reconnect = false;
|
||||||
|
let mut should_create_detached = false;
|
||||||
|
|
||||||
if let Some(reconnect_to_session) = &reconnect_to_session {
|
if let Some(reconnect_to_session) = &reconnect_to_session {
|
||||||
// this is integration code to make session reconnects work with this existing,
|
// this is integration code to make session reconnects work with this existing,
|
||||||
|
|
@ -417,6 +418,7 @@ pub(crate) fn start_client(opts: CliArgs) {
|
||||||
opts.command = Some(Command::Sessions(Sessions::Attach {
|
opts.command = Some(Command::Sessions(Sessions::Attach {
|
||||||
session_name: reconnect_to_session.name.clone(),
|
session_name: reconnect_to_session.name.clone(),
|
||||||
create: true,
|
create: true,
|
||||||
|
background: false,
|
||||||
force_run_commands: false,
|
force_run_commands: false,
|
||||||
index: None,
|
index: None,
|
||||||
options: None,
|
options: None,
|
||||||
|
|
@ -476,6 +478,7 @@ pub(crate) fn start_client(opts: CliArgs) {
|
||||||
if let Some(Command::Sessions(Sessions::Attach {
|
if let Some(Command::Sessions(Sessions::Attach {
|
||||||
session_name,
|
session_name,
|
||||||
create,
|
create,
|
||||||
|
background,
|
||||||
force_run_commands,
|
force_run_commands,
|
||||||
index,
|
index,
|
||||||
options,
|
options,
|
||||||
|
|
@ -487,9 +490,14 @@ pub(crate) fn start_client(opts: CliArgs) {
|
||||||
},
|
},
|
||||||
None => config_options,
|
None => config_options,
|
||||||
};
|
};
|
||||||
|
should_create_detached = background;
|
||||||
|
|
||||||
let client = if let Some(idx) = index {
|
let client = if let Some(idx) = index {
|
||||||
attach_with_session_index(config_options.clone(), idx, create)
|
attach_with_session_index(
|
||||||
|
config_options.clone(),
|
||||||
|
idx,
|
||||||
|
create || should_create_detached,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
let session_exists = session_name
|
let session_exists = session_name
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -497,7 +505,10 @@ pub(crate) fn start_client(opts: CliArgs) {
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
let resurrection_layout =
|
let resurrection_layout =
|
||||||
session_name.as_ref().and_then(|s| resurrection_layout(&s));
|
session_name.as_ref().and_then(|s| resurrection_layout(&s));
|
||||||
if create && !session_exists && resurrection_layout.is_none() {
|
if (create || should_create_detached)
|
||||||
|
&& !session_exists
|
||||||
|
&& resurrection_layout.is_none()
|
||||||
|
{
|
||||||
session_name.clone().map(start_client_plan);
|
session_name.clone().map(start_client_plan);
|
||||||
}
|
}
|
||||||
match (session_name.as_ref(), resurrection_layout) {
|
match (session_name.as_ref(), resurrection_layout) {
|
||||||
|
|
@ -507,7 +518,11 @@ pub(crate) fn start_client(opts: CliArgs) {
|
||||||
}
|
}
|
||||||
ClientInfo::Resurrect(session_name.clone(), resurrection_layout)
|
ClientInfo::Resurrect(session_name.clone(), resurrection_layout)
|
||||||
},
|
},
|
||||||
_ => attach_with_session_name(session_name, config_options.clone(), create),
|
_ => attach_with_session_name(
|
||||||
|
session_name,
|
||||||
|
config_options.clone(),
|
||||||
|
create || should_create_detached,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -541,6 +556,7 @@ pub(crate) fn start_client(opts: CliArgs) {
|
||||||
tab_position_to_focus,
|
tab_position_to_focus,
|
||||||
pane_id_to_focus,
|
pane_id_to_focus,
|
||||||
is_a_reconnect,
|
is_a_reconnect,
|
||||||
|
should_create_detached,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if let Some(session_name) = opts.session.clone() {
|
if let Some(session_name) = opts.session.clone() {
|
||||||
|
|
@ -555,6 +571,7 @@ pub(crate) fn start_client(opts: CliArgs) {
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
is_a_reconnect,
|
is_a_reconnect,
|
||||||
|
should_create_detached,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if let Some(session_name) = config_options.session_name.as_ref() {
|
if let Some(session_name) = config_options.session_name.as_ref() {
|
||||||
|
|
@ -595,6 +612,7 @@ pub(crate) fn start_client(opts: CliArgs) {
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
is_a_reconnect,
|
is_a_reconnect,
|
||||||
|
should_create_detached,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
|
|
@ -609,6 +627,7 @@ pub(crate) fn start_client(opts: CliArgs) {
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
is_a_reconnect,
|
is_a_reconnect,
|
||||||
|
should_create_detached,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -632,6 +651,7 @@ pub(crate) fn start_client(opts: CliArgs) {
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
is_a_reconnect,
|
is_a_reconnect,
|
||||||
|
should_create_detached,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -138,8 +138,8 @@ fn pipe_client(
|
||||||
let mut buffer = String::new();
|
let mut buffer = String::new();
|
||||||
let _ = stdin.read_line(&mut buffer);
|
let _ = stdin.read_line(&mut buffer);
|
||||||
if buffer.is_empty() {
|
if buffer.is_empty() {
|
||||||
// TODO: consider notifying the relevant plugin that the pipe has ended with a
|
let msg = create_msg(None);
|
||||||
// specialized message
|
os_input.send_to_server(msg);
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
// we've got data! send it down the pipe (most common)
|
// we've got data! send it down the pipe (most common)
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ use zellij_utils::{
|
||||||
errors::{ClientContext, ContextType, ErrorInstruction},
|
errors::{ClientContext, ContextType, ErrorInstruction},
|
||||||
input::{config::Config, options::Options},
|
input::{config::Config, options::Options},
|
||||||
ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg},
|
ipc::{ClientAttributes, ClientToServerMsg, ExitReason, ServerToClientMsg},
|
||||||
|
pane_size::Size,
|
||||||
termwiz::input::InputEvent,
|
termwiz::input::InputEvent,
|
||||||
};
|
};
|
||||||
use zellij_utils::{cli::CliArgs, input::layout::Layout};
|
use zellij_utils::{cli::CliArgs, input::layout::Layout};
|
||||||
|
|
@ -168,7 +169,12 @@ pub fn start_client(
|
||||||
tab_position_to_focus: Option<usize>,
|
tab_position_to_focus: Option<usize>,
|
||||||
pane_id_to_focus: Option<(u32, bool)>, // (pane_id, is_plugin)
|
pane_id_to_focus: Option<(u32, bool)>, // (pane_id, is_plugin)
|
||||||
is_a_reconnect: bool,
|
is_a_reconnect: bool,
|
||||||
|
start_detached_and_exit: bool,
|
||||||
) -> Option<ConnectToSession> {
|
) -> Option<ConnectToSession> {
|
||||||
|
if start_detached_and_exit {
|
||||||
|
start_server_detached(os_input, opts, config, config_options, info, layout);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
info!("Starting Zellij client!");
|
info!("Starting Zellij client!");
|
||||||
|
|
||||||
let mut reconnect_to_session = None;
|
let mut reconnect_to_session = None;
|
||||||
|
|
@ -541,6 +547,69 @@ pub fn start_client(
|
||||||
reconnect_to_session
|
reconnect_to_session
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn start_server_detached(
|
||||||
|
mut os_input: Box<dyn ClientOsApi>,
|
||||||
|
opts: CliArgs,
|
||||||
|
config: Config,
|
||||||
|
config_options: Options,
|
||||||
|
info: ClientInfo,
|
||||||
|
layout: Option<Layout>,
|
||||||
|
) {
|
||||||
|
envs::set_zellij("0".to_string());
|
||||||
|
config.env.set_vars();
|
||||||
|
|
||||||
|
let palette = config
|
||||||
|
.theme_config(&config_options)
|
||||||
|
.unwrap_or_else(|| os_input.load_palette());
|
||||||
|
|
||||||
|
let client_attributes = ClientAttributes {
|
||||||
|
size: Size { rows: 50, cols: 50 }, // just so size is not 0, it doesn't matter because we
|
||||||
|
// immediately detach
|
||||||
|
style: Style {
|
||||||
|
colors: palette,
|
||||||
|
rounded_corners: config.ui.pane_frames.rounded_corners,
|
||||||
|
hide_session_name: config.ui.pane_frames.hide_session_name,
|
||||||
|
},
|
||||||
|
keybinds: config.keybinds.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let create_ipc_pipe = || -> std::path::PathBuf {
|
||||||
|
let mut sock_dir = ZELLIJ_SOCK_DIR.clone();
|
||||||
|
std::fs::create_dir_all(&sock_dir).unwrap();
|
||||||
|
set_permissions(&sock_dir, 0o700).unwrap();
|
||||||
|
sock_dir.push(envs::get_session_name().unwrap());
|
||||||
|
sock_dir
|
||||||
|
};
|
||||||
|
|
||||||
|
let (first_msg, ipc_pipe) = match info {
|
||||||
|
ClientInfo::New(name) | ClientInfo::Resurrect(name, _) => {
|
||||||
|
envs::set_session_name(name.clone());
|
||||||
|
os_input.update_session_name(name);
|
||||||
|
let ipc_pipe = create_ipc_pipe();
|
||||||
|
|
||||||
|
spawn_server(&*ipc_pipe, opts.debug).unwrap();
|
||||||
|
|
||||||
|
(
|
||||||
|
ClientToServerMsg::NewClient(
|
||||||
|
client_attributes,
|
||||||
|
Box::new(opts),
|
||||||
|
Box::new(config_options.clone()),
|
||||||
|
Box::new(layout.unwrap()),
|
||||||
|
Box::new(config.plugins.clone()),
|
||||||
|
),
|
||||||
|
ipc_pipe,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
eprintln!("Session already exists");
|
||||||
|
std::process::exit(1);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
os_input.connect_to_server(&*ipc_pipe);
|
||||||
|
os_input.send_to_server(first_msg);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "./unit/stdin_tests.rs"]
|
#[path = "./unit/stdin_tests.rs"]
|
||||||
mod stdin_tests;
|
mod stdin_tests;
|
||||||
|
|
|
||||||
|
|
@ -317,8 +317,8 @@ impl Clone for Box<dyn ClientOsApi> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_client_os_input() -> Result<ClientOsInputOutput, nix::Error> {
|
pub fn get_client_os_input() -> Result<ClientOsInputOutput, nix::Error> {
|
||||||
let current_termios = termios::tcgetattr(0)?;
|
let current_termios = termios::tcgetattr(0).ok();
|
||||||
let orig_termios = Some(Arc::new(Mutex::new(current_termios)));
|
let orig_termios = current_termios.map(|termios| Arc::new(Mutex::new(termios)));
|
||||||
let reading_from_stdin = Arc::new(Mutex::new(None));
|
let reading_from_stdin = Arc::new(Mutex::new(None));
|
||||||
Ok(ClientOsInputOutput {
|
Ok(ClientOsInputOutput {
|
||||||
orig_termios,
|
orig_termios,
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,7 @@ fn handle_openpty(
|
||||||
fn handle_terminal(
|
fn handle_terminal(
|
||||||
cmd: RunCommand,
|
cmd: RunCommand,
|
||||||
failover_cmd: Option<RunCommand>,
|
failover_cmd: Option<RunCommand>,
|
||||||
orig_termios: termios::Termios,
|
orig_termios: Option<termios::Termios>,
|
||||||
quit_cb: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>,
|
quit_cb: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>,
|
||||||
terminal_id: u32,
|
terminal_id: u32,
|
||||||
) -> Result<(RawFd, RawFd)> {
|
) -> Result<(RawFd, RawFd)> {
|
||||||
|
|
@ -229,7 +229,7 @@ fn handle_terminal(
|
||||||
|
|
||||||
// Create a pipe to allow the child the communicate the shell's pid to its
|
// Create a pipe to allow the child the communicate the shell's pid to its
|
||||||
// parent.
|
// parent.
|
||||||
match openpty(None, Some(&orig_termios)) {
|
match openpty(None, &orig_termios) {
|
||||||
Ok(open_pty_res) => handle_openpty(open_pty_res, cmd, quit_cb, terminal_id),
|
Ok(open_pty_res) => handle_openpty(open_pty_res, cmd, quit_cb, terminal_id),
|
||||||
Err(e) => match failover_cmd {
|
Err(e) => match failover_cmd {
|
||||||
Some(failover_cmd) => {
|
Some(failover_cmd) => {
|
||||||
|
|
@ -279,7 +279,7 @@ fn separate_command_arguments(command: &mut PathBuf, args: &mut Vec<String>) {
|
||||||
/// set.
|
/// set.
|
||||||
fn spawn_terminal(
|
fn spawn_terminal(
|
||||||
terminal_action: TerminalAction,
|
terminal_action: TerminalAction,
|
||||||
orig_termios: termios::Termios,
|
orig_termios: Option<termios::Termios>,
|
||||||
quit_cb: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>, // u32 is the exit_status
|
quit_cb: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>, // u32 is the exit_status
|
||||||
default_editor: Option<PathBuf>,
|
default_editor: Option<PathBuf>,
|
||||||
terminal_id: u32,
|
terminal_id: u32,
|
||||||
|
|
@ -418,7 +418,7 @@ impl ClientSender {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ServerOsInputOutput {
|
pub struct ServerOsInputOutput {
|
||||||
orig_termios: Arc<Mutex<termios::Termios>>,
|
orig_termios: Arc<Mutex<Option<termios::Termios>>>,
|
||||||
client_senders: Arc<Mutex<HashMap<ClientId, ClientSender>>>,
|
client_senders: Arc<Mutex<HashMap<ClientId, ClientSender>>>,
|
||||||
terminal_id_to_raw_fd: Arc<Mutex<BTreeMap<u32, Option<RawFd>>>>, // A value of None means the
|
terminal_id_to_raw_fd: Arc<Mutex<BTreeMap<u32, Option<RawFd>>>>, // A value of None means the
|
||||||
// terminal_id exists but is
|
// terminal_id exists but is
|
||||||
|
|
@ -876,7 +876,10 @@ impl Clone for Box<dyn ServerOsApi> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_server_os_input() -> Result<ServerOsInputOutput, nix::Error> {
|
pub fn get_server_os_input() -> Result<ServerOsInputOutput, nix::Error> {
|
||||||
let current_termios = termios::tcgetattr(0)?;
|
let current_termios = termios::tcgetattr(0).ok();
|
||||||
|
if current_termios.is_none() {
|
||||||
|
log::warn!("Starting a server without a controlling terminal, using the default termios configuration.");
|
||||||
|
}
|
||||||
let orig_termios = Arc::new(Mutex::new(current_termios));
|
let orig_termios = Arc::new(Mutex::new(current_termios));
|
||||||
Ok(ServerOsInputOutput {
|
Ok(ServerOsInputOutput {
|
||||||
orig_termios,
|
orig_termios,
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ fn get_cwd() {
|
||||||
termios::tcgetattr(test_terminal.slave()).expect("Could not configure the termios");
|
termios::tcgetattr(test_terminal.slave()).expect("Could not configure the termios");
|
||||||
|
|
||||||
let server = ServerOsInputOutput {
|
let server = ServerOsInputOutput {
|
||||||
orig_termios: Arc::new(Mutex::new(test_termios)),
|
orig_termios: Arc::new(Mutex::new(Some(test_termios))),
|
||||||
client_senders: Arc::default(),
|
client_senders: Arc::default(),
|
||||||
terminal_id_to_raw_fd: Arc::default(),
|
terminal_id_to_raw_fd: Arc::default(),
|
||||||
cached_resizes: Arc::default(),
|
cached_resizes: Arc::default(),
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,10 @@ pub enum Sessions {
|
||||||
#[clap(short, long, value_parser)]
|
#[clap(short, long, value_parser)]
|
||||||
create: bool,
|
create: bool,
|
||||||
|
|
||||||
|
/// Create a detached session in the background if one does not exist
|
||||||
|
#[clap(short, long, value_parser)]
|
||||||
|
background: bool,
|
||||||
|
|
||||||
/// Number of the session index in the active sessions ordered creation date.
|
/// Number of the session index in the active sessions ordered creation date.
|
||||||
#[clap(long, value_parser)]
|
#[clap(long, value_parser)]
|
||||||
index: Option<usize>,
|
index: Option<usize>,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue