rust: use terminalenv for SSH and local shell env
This commit is contained in:
@@ -17,34 +17,6 @@ pub fn default_shell() -> String {
|
|||||||
std::env::var("SHELL").ok().filter(|s| !s.is_empty()).unwrap_or_else(|| "/bin/sh".into())
|
std::env::var("SHELL").ok().filter(|s| !s.is_empty()).unwrap_or_else(|| "/bin/sh".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_utf8_locale(v: &str) -> bool {
|
|
||||||
let v = v.to_lowercase();
|
|
||||||
v.contains("utf-8") || v.contains("utf8")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// TERM=xterm-256color and a UTF-8 character locale, keeping the user's environment otherwise.
|
|
||||||
pub fn local_shell_env(env: &mut Vec<(String, String)>) {
|
|
||||||
let set = |env: &mut Vec<(String, String)>, k: &str, v: &str| {
|
|
||||||
env.retain(|(ek, _)| ek != k);
|
|
||||||
env.push((k.into(), v.into()));
|
|
||||||
};
|
|
||||||
set(env, "TERM", "xterm-256color");
|
|
||||||
let get = |k: &str| env.iter().find(|(ek, _)| ek == k).map(|(_, v)| v.clone());
|
|
||||||
let effective = get("LC_ALL").or_else(|| get("LC_CTYPE")).or_else(|| get("LANG"));
|
|
||||||
if effective.as_deref().is_some_and(is_utf8_locale) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let locale = if cfg!(target_os = "macos") { "en_US.UTF-8" } else { "C.UTF-8" };
|
|
||||||
let key = if get("LC_ALL").is_some() {
|
|
||||||
"LC_ALL"
|
|
||||||
} else if get("LC_CTYPE").is_some() || get("LANG").is_some() {
|
|
||||||
"LC_CTYPE"
|
|
||||||
} else {
|
|
||||||
"LANG"
|
|
||||||
};
|
|
||||||
set(env, key, locale);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Running {
|
struct Running {
|
||||||
master: Box<dyn MasterPty + Send>,
|
master: Box<dyn MasterPty + Send>,
|
||||||
writer: Box<dyn Write + Send>,
|
writer: Box<dyn Write + Send>,
|
||||||
@@ -77,11 +49,12 @@ impl LocalShell {
|
|||||||
.openpty(PtySize { rows: st.3, cols: st.2, pixel_width: 0, pixel_height: 0 })
|
.openpty(PtySize { rows: st.3, cols: st.2, pixel_width: 0, pixel_height: 0 })
|
||||||
.map_err(|e| format!("failed to start shell {:?}: {e}", self.shell))?;
|
.map_err(|e| format!("failed to start shell {:?}: {e}", self.shell))?;
|
||||||
let mut cmd = CommandBuilder::new(&self.shell);
|
let mut cmd = CommandBuilder::new(&self.shell);
|
||||||
let mut env: Vec<(String, String)> = std::env::vars().collect();
|
|
||||||
local_shell_env(&mut env);
|
|
||||||
cmd.env_clear();
|
cmd.env_clear();
|
||||||
for (k, v) in env {
|
let env = crate::terminalenv::local_shell_env(std::env::vars().map(|(k, v)| format!("{k}={v}")).collect());
|
||||||
cmd.env(k, v);
|
for kv in env {
|
||||||
|
if let Some((k, v)) = kv.split_once('=') {
|
||||||
|
cmd.env(k, v);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cmd.cwd(&st.1);
|
cmd.cwd(&st.1);
|
||||||
let mut child = pair.slave.spawn_command(cmd).map_err(|e| format!("failed to start shell {:?}: {e}", self.shell))?;
|
let mut child = pair.slave.spawn_command(cmd).map_err(|e| format!("failed to start shell {:?}: {e}", self.shell))?;
|
||||||
@@ -215,12 +188,4 @@ mod tests {
|
|||||||
}
|
}
|
||||||
assert_eq!(sh.working_directory(), PathBuf::from("/"));
|
assert_eq!(sh.working_directory(), PathBuf::from("/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn env_gets_term_and_utf8() {
|
|
||||||
let mut e = vec![("LANG".to_string(), "C".to_string())];
|
|
||||||
local_shell_env(&mut e);
|
|
||||||
assert!(e.contains(&("TERM".into(), "xterm-256color".into())));
|
|
||||||
assert!(e.iter().any(|(k, v)| k == "LC_CTYPE" && v.ends_with("UTF-8")));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,16 +158,6 @@ async fn connect_agent() -> Result<Agent> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Profile env vars plus a UTF-8 LC_CTYPE when the profile sets no locale (Go's SSHEnvVars).
|
|
||||||
fn ssh_env_vars(env: &std::collections::BTreeMap<String, String>) -> std::collections::BTreeMap<String, String> {
|
|
||||||
let mut out: std::collections::BTreeMap<String, String> =
|
|
||||||
env.iter().filter(|(k, _)| !k.trim().is_empty()).map(|(k, v)| (k.trim().to_string(), v.clone())).collect();
|
|
||||||
if !["LC_ALL", "LC_CTYPE", "LANG"].iter().any(|k| out.contains_key(*k)) {
|
|
||||||
out.insert("LC_CTYPE".into(), if cfg!(target_os = "macos") { "en_US.UTF-8" } else { "C.UTF-8" }.into());
|
|
||||||
}
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_totp_prompt(q: &str) -> bool {
|
fn is_totp_prompt(q: &str) -> bool {
|
||||||
let q = q.to_lowercase();
|
let q = q.to_lowercase();
|
||||||
[
|
[
|
||||||
@@ -496,7 +486,7 @@ impl SshClient {
|
|||||||
let handle = Arc::new(handle);
|
let handle = Arc::new(handle);
|
||||||
|
|
||||||
let ch = handle.channel_open_session().await.map_err(|e| format!("failed to create SSH session: {e}"))?;
|
let ch = handle.channel_open_session().await.map_err(|e| format!("failed to create SSH session: {e}"))?;
|
||||||
for (k, v) in ssh_env_vars(&sc.env_vars) {
|
for (k, v) in crate::terminalenv::ssh_env_vars(&sc.env_vars) {
|
||||||
let _ = ch.set_env(false, k, v).await;
|
let _ = ch.set_env(false, k, v).await;
|
||||||
}
|
}
|
||||||
let term = if sc.terminal_type.is_empty() { "xterm-256color" } else { &sc.terminal_type };
|
let term = if sc.terminal_type.is_empty() { "xterm-256color" } else { &sc.terminal_type };
|
||||||
|
|||||||
Reference in New Issue
Block a user