use crate::catchup::resolve::window_id_of;
pub const MANAGED_SESSION_ID_ENV: &str = "TM_MANAGED_SESSION_ID";
pub const TMUX_WINDOW_ID_PREFIX: &str = "tmux-window-";
pub fn derive_session_id(
managed_session_id: Option<&str>,
tmux_window: Option<&str>,
) -> Option<String> {
if let Some(id) = managed_session_id.map(str::trim).filter(|s| !s.is_empty()) {
return Some(id.to_string());
}
tmux_window.and_then(tmux_window_session_id)
}
fn tmux_window_session_id(tmux_window: &str) -> Option<String> {
let body = window_id_of(tmux_window)?.trim_start_matches('@');
if body.is_empty() || !body.chars().all(|c| c.is_ascii_alphanumeric()) {
return None;
}
Some(format!("{TMUX_WINDOW_ID_PREFIX}{body}"))
}
pub fn managed_session_id_from_env() -> Option<String> {
std::env::var(MANAGED_SESSION_ID_ENV)
.ok()
.map(|v| v.trim().to_string())
.filter(|v| !v.is_empty())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::catchup::pause::{PauseSnapshotInput, write_pause_snapshot};
use crate::catchup::resolve::{ResolutionPath, resolve_snapshot_for_caller};
use crate::catchup::session_log::session_dir_name;
use tempfile::TempDir;
fn pause(dir: &std::path::Path, session_id: &str, tmux_window: Option<&str>) {
let input = PauseSnapshotInput {
session_id,
summary: "Paused.",
completed: &[],
in_progress: &[],
next_steps: &[],
tmux_window,
};
write_pause_snapshot(dir, &input).unwrap();
}
#[test]
fn managed_id_wins_over_the_window() {
assert_eq!(
derive_session_id(Some("mgr-1"), Some("proj:0:@230")).as_deref(),
Some("mgr-1")
);
assert_eq!(
derive_session_id(Some(" "), Some("proj:0:@230")).as_deref(),
Some("tmux-window-230")
);
}
#[test]
fn derives_a_directory_safe_id_from_a_tmux_window() {
let id = derive_session_id(None, Some("tm-dogfood:0:@230")).unwrap();
assert_eq!(id, "tmux-window-230");
assert_eq!(
session_dir_name(&id),
Some(id.as_str()),
"a derived id must be usable as a snapshot directory name"
);
}
#[test]
fn a_malformed_window_derives_nothing() {
for bad in ["", "proj", "a:b:c:d", "proj:0:@", "proj:0:@2 3"] {
assert_eq!(derive_session_id(None, Some(bad)), None, "{bad:?}");
}
}
#[test]
fn an_unidentified_caller_derives_nothing() {
assert_eq!(derive_session_id(None, None), None);
assert_eq!(derive_session_id(Some(""), None), None);
}
#[test]
fn managed_session_id_from_env_reads_the_variable() {
let expected = std::env::var(MANAGED_SESSION_ID_ENV)
.ok()
.map(|v| v.trim().to_string())
.filter(|v| !v.is_empty());
assert_eq!(managed_session_id_from_env(), expected);
}
#[test]
fn derived_pause_resolves_for_the_same_managed_caller() {
let tmp = TempDir::new().unwrap();
let managed = Some("7bd5c27a-475b-41df-9e9f-a6f630801717");
let write_id = derive_session_id(managed, None).unwrap();
pause(tmp.path(), &write_id, None);
let read_id = derive_session_id(managed, None).unwrap();
let resolved = resolve_snapshot_for_caller(tmp.path(), Some(&read_id), None)
.expect("a managed caller must resolve the pause it just wrote");
assert_eq!(resolved.via, ResolutionPath::SessionId);
assert!(resolved.path.exists());
}
#[test]
fn derived_pause_resolves_for_the_same_tmux_caller() {
let tmp = TempDir::new().unwrap();
let write_id = derive_session_id(None, Some("tm-dogfood:0:@230")).unwrap();
pause(tmp.path(), &write_id, None);
let read_id = derive_session_id(None, Some("renamed:7:@230")).unwrap();
let resolved = resolve_snapshot_for_caller(tmp.path(), Some(&read_id), None)
.expect("the same tmux window must resolve its own pause");
assert_eq!(resolved.via, ResolutionPath::SessionId);
let other = derive_session_id(None, Some("tm-dogfood:0:@999")).unwrap();
assert!(
resolve_snapshot_for_caller(tmp.path(), Some(&other), None).is_none(),
"another window must never inherit this snapshot (#5272)"
);
}
#[test]
fn an_explicit_session_id_still_round_trips() {
let tmp = TempDir::new().unwrap();
pause(tmp.path(), "explicit-id", None);
let resolved = resolve_snapshot_for_caller(tmp.path(), Some("explicit-id"), None)
.expect("an explicit session id still resolves its own pause");
assert_eq!(resolved.via, ResolutionPath::SessionId);
let derived = derive_session_id(None, Some("tm-dogfood:0:@230")).unwrap();
assert!(
resolve_snapshot_for_caller(tmp.path(), Some(&derived), None).is_none(),
"a derived id must not pick up an explicitly attributed snapshot"
);
}
}