aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
authorGolden-Pigeon <goldenpigeon2000@gmail.com>2026-05-22 02:39:02 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-22 02:43:30 +0300
commit1daf54ecbf960c42d612ed4ad2764045a606fb30 (patch)
treec6ee19c7e8032ac77f5c00bc17bc6a1bd93d2272 /src/client
parent0148c1379b76c5c0a9d05588c07dcb06ec32f93f (diff)
feat: show named session command hints
refs #199
Diffstat (limited to 'src/client')
-rw-r--r--src/client/mod.rs115
1 files changed, 114 insertions, 1 deletions
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<Mutex<()>> = OnceLock::new();
+ LOCK.get_or_init(|| Mutex::new(()))
+ }
+
+ fn restore_env_var(key: &str, value: Option<OsString>) {
+ if let Some(value) = value {
+ std::env::set_var(key, value);
+ } else {
+ std::env::remove_var(key);
+ }
+ }
+
+ struct EnvVarGuard {
+ key: &'static str,
+ previous: Option<OsString>,
+ }
+
+ 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<OsString>)>,
+ }
+
+ 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() {
@@ -1296,6 +1359,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 =
ClientError::ConnectionLost(io::Error::new(io::ErrorKind::BrokenPipe, "broken pipe"));