From 1daf54ecbf960c42d612ed4ad2764045a606fb30 Mon Sep 17 00:00:00 2001 From: Golden-Pigeon Date: Fri, 22 May 2026 02:39:02 +0300 Subject: feat: show named session command hints refs #199 --- src/client/mod.rs | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 1 deletion(-) (limited to 'src/client') diff --git a/src/client/mod.rs b/src/client/mod.rs index 11273061..1ac05201 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -154,7 +154,11 @@ impl std::fmt::Display for ClientError { write!(f, "\nRun `{reattach_command}` to reattach")?; } else { write!(f, "detached from server")?; - write!(f, "\nRun `herdr` to reattach")?; + write!( + f, + "\nRun `{}` to reattach", + crate::session::local_attach_command() + )?; } } _ => { @@ -1125,6 +1129,65 @@ fn init_logging() { #[cfg(test)] mod tests { use super::*; + use std::ffi::OsString; + use std::sync::{Mutex, OnceLock}; + + fn env_lock() -> &'static Mutex<()> { + static LOCK: OnceLock> = OnceLock::new(); + LOCK.get_or_init(|| Mutex::new(())) + } + + fn restore_env_var(key: &str, value: Option) { + if let Some(value) = value { + std::env::set_var(key, value); + } else { + std::env::remove_var(key); + } + } + + struct EnvVarGuard { + key: &'static str, + previous: Option, + } + + impl EnvVarGuard { + fn set(key: &'static str, value: &str) -> Self { + let previous = std::env::var_os(key); + std::env::set_var(key, value); + Self { key, previous } + } + } + + impl Drop for EnvVarGuard { + fn drop(&mut self) { + restore_env_var(self.key, self.previous.clone()); + } + } + + struct EnvVarsRemovedGuard { + previous: Vec<(&'static str, Option)>, + } + + impl EnvVarsRemovedGuard { + fn new(keys: &[&'static str]) -> Self { + let previous: Vec<_> = keys + .iter() + .map(|key| (*key, std::env::var_os(key))) + .collect(); + for key in keys { + std::env::remove_var(key); + } + Self { previous } + } + } + + impl Drop for EnvVarsRemovedGuard { + fn drop(&mut self) { + for (key, value) in self.previous.clone() { + restore_env_var(key, value); + } + } + } #[test] fn clipboard_image_paste_bridge_triggers_on_ctrl_v_and_empty_paste() { @@ -1295,6 +1358,56 @@ mod tests { ); } + #[test] + fn client_error_display_detached_default_session_reattach_hint() { + let _guard = env_lock().lock().unwrap(); + let _env = EnvVarsRemovedGuard::new(&[ + crate::remote::REATTACH_COMMAND_ENV_VAR, + crate::session::SESSION_ENV_VAR, + ]); + let err = ClientError::ServerShutdown { + reason: Some("detached".into()), + }; + let msg = err.to_string(); + assert!( + msg.contains("Run `herdr` to reattach"), + "should suggest default reattach command: {msg}" + ); + } + + #[test] + fn client_error_display_detached_named_session_reattach_hint() { + let _guard = env_lock().lock().unwrap(); + let _remote_env = EnvVarsRemovedGuard::new(&[crate::remote::REATTACH_COMMAND_ENV_VAR]); + let _session_env = EnvVarGuard::set(crate::session::SESSION_ENV_VAR, "work"); + let err = ClientError::ServerShutdown { + reason: Some("detached".into()), + }; + let msg = err.to_string(); + assert!( + msg.contains("Run `herdr session attach work` to reattach"), + "should suggest named session reattach command: {msg}" + ); + } + + #[test] + fn client_error_display_detached_remote_reattach_hint_takes_precedence() { + let _guard = env_lock().lock().unwrap(); + let _remote_env = EnvVarGuard::set( + crate::remote::REATTACH_COMMAND_ENV_VAR, + "herdr --remote host --session work", + ); + let _session_env = EnvVarGuard::set(crate::session::SESSION_ENV_VAR, "work"); + let err = ClientError::ServerShutdown { + reason: Some("detached".into()), + }; + let msg = err.to_string(); + assert!( + msg.contains("Run `herdr --remote host --session work` to reattach"), + "should prefer remote reattach command: {msg}" + ); + } + #[test] fn client_error_display_connection_lost() { let err = -- cgit v1.2.3