use std::env;
use std::ffi::OsString;
use std::path::Path;
use std::process::Command;
use anyhow::{Context, Result};
use crate::ssh::{SshContext, SshProjectOpenMode};
const LOCAL_BINARY_ENV: &str = "QUINJET_LOCAL_BINARY";
pub(crate) fn run_selected_terminal(
target: &str,
folder: &Path,
context: SshContext,
mode: SshProjectOpenMode,
) -> Result<u8> {
super::validate_target(target)?;
let binary = env::var(super::REMOTE_BINARY_ENV).unwrap_or_else(|_| "quinjet".to_owned());
let original_arguments = wild::args_os().skip(1).collect::<Vec<_>>();
run_terminal_loop(
target,
folder,
&binary,
&original_arguments,
(false, true, Some(context)),
Some(mode),
)
}
pub(super) fn run_terminal_loop(
target: &str,
folder: &Path,
binary: &str,
original_arguments: &[OsString],
handoff: (bool, bool, Option<SshContext>),
initial_project_mode: Option<SshProjectOpenMode>,
) -> Result<u8> {
let (implicit_terminal, mut switched, context) = handoff;
let (mut current_target, mut current_folder) = (target.to_owned(), folder.to_path_buf());
let mut current_local = false;
let mut project_mode = initial_project_mode;
let mut context =
context.unwrap_or_else(|| super::ssh_context(¤t_target, ¤t_folder));
loop {
let arguments = if implicit_terminal || switched {
super::switched_terminal_arguments(original_arguments.to_owned(), ¤t_folder)?
} else {
super::forwarded_arguments(original_arguments.to_owned())?
};
let status = if current_local {
local_status(
&arguments,
&context,
project_mode.unwrap_or(SshProjectOpenMode::CurrentTab),
)?
} else {
super::ssh_status(
¤t_target,
binary,
&arguments,
Some(&context),
super::TerminalRelay {
allocate: true,
inherited: switched,
project_mode,
},
)?
};
let code = status
.code()
.unwrap_or_else(|| i32::from(super::super::EXIT_FAILURE));
if let Some(request) = crate::ssh::switch_request(code)
&& let Some(machine) = context.machines.get(request.index)
{
if !current_local {
crate::state::record_recent_remote(¤t_target, ¤t_folder);
}
current_target.clone_from(&machine.target);
current_folder.clone_from(&machine.folder);
current_local = machine.local;
context.current.clone_from(¤t_target);
project_mode = Some(request.mode);
switched = true;
continue;
}
if status.success() && !current_local {
crate::state::record_recent_remote(¤t_target, ¤t_folder);
}
if switched {
crate::terminal::restore_inherited_terminal();
}
return Ok(super::exit_code(status));
}
}
fn local_status(
arguments: &[OsString],
context: &SshContext,
mode: SshProjectOpenMode,
) -> Result<std::process::ExitStatus> {
let executable = env::var_os(LOCAL_BINARY_ENV).map_or_else(
|| {
env::current_exe() .context("failed to locate the local Quinjet binary")
},
|path| Ok(path.into()),
)?;
let serialized = serde_json::to_string(context)?;
Command::new(executable)
.args(arguments)
.env("QUINJET_SSH_CONTEXT", serialized)
.env(crate::terminal::INHERITED_TERMINAL_ENV, "1")
.env(crate::ssh::OPEN_PROJECTS_ENV, mode.environment_value())
.status()
.context("failed to return to the local Quinjet session")
}
pub(super) fn remote_command(
binary: &str,
arguments: &[OsString],
context: Option<&SshContext>,
inherited_terminal: bool,
project_mode: Option<SshProjectOpenMode>,
) -> Result<String> {
let mut command = super::quote(binary)?;
for argument in arguments {
command.push(' ');
command.push_str(&super::quote(&argument.to_string_lossy())?);
}
if let Some(context) = context {
let serialized = serde_json::to_string(context)?;
command = format!(
"QUINJET_SSH_CONTEXT={} {command}",
super::quote(&serialized)?
);
}
if inherited_terminal {
let mode = project_mode.unwrap_or(SshProjectOpenMode::CurrentTab);
command = format!(
"{}=1 {}={} {command}",
crate::terminal::INHERITED_TERMINAL_ENV,
crate::ssh::OPEN_PROJECTS_ENV,
mode.environment_value()
);
}
Ok(command)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn remote_command_quotes_every_argument() {
let command = remote_command(
"quinjet test",
&[OsString::from("--path"), OsString::from("a'b c")],
None,
false,
None,
)
.unwrap();
assert_eq!(command, "'quinjet test' --path \"a'b c\"");
}
#[test]
fn switched_terminal_inherits_the_existing_alternate_screen() {
let command =
remote_command("quinjet", &[], None, true, Some(SshProjectOpenMode::NewTab)).unwrap();
assert_eq!(
command,
"QUINJET_INHERITED_TERMINAL=1 QUINJET_OPEN_PROJECTS=new-tab quinjet"
);
}
}