use std::path::Path;
use crate::path_display::display_path;
use crate::session_id::SessionId;
use crate::session_record::SessionRecord;
use crate::trace::{Trace, trace};
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum DetectOutcome {
None,
Unique(SessionId),
Ambiguous(Vec<SessionRecord>),
}
pub(crate) fn auto_detect(live: &[SessionRecord], cwd: &Path, trace: Trace) -> DetectOutcome {
if live.is_empty() {
trace!(trace, "auto-detect: no live sessions to choose from");
return DetectOutcome::None;
}
let matches: Vec<&SessionRecord> = live
.iter()
.filter(|session| session.launch_directory == cwd)
.collect();
if trace.is_enabled() {
trace!(
trace,
"auto-detect: current directory {}; {} live {}; {} launch-directory {}",
display_path(cwd),
live.len(),
session_noun(live.len()),
matches.len(),
match_noun(matches.len()),
);
for session in live {
trace!(
trace,
"auto-detect: session {} was launched from {} ({})",
session.id,
display_path(&session.launch_directory),
match_mark(&session.launch_directory, cwd)
);
}
trace!(trace, "auto-detect: {}", outcome_note(matches.len()));
}
match matches.as_slice() {
[only] => DetectOutcome::Unique(only.session_id()),
_ => DetectOutcome::Ambiguous(live.to_vec()),
}
}
#[cfg_attr(test, mutants::skip)]
fn outcome_note(matches: usize) -> &'static str {
match matches {
0 => "no session was launched from here, so every live session is offered",
1 => "exactly one session was launched from here, so it is resumed",
_ => "several sessions were launched from here, so none can be chosen for you",
}
}
#[cfg_attr(test, mutants::skip)]
fn session_noun(count: usize) -> &'static str {
if count == 1 { "session" } else { "sessions" }
}
#[cfg_attr(test, mutants::skip)]
fn match_mark(launch_directory: &Path, cwd: &Path) -> &'static str {
if launch_directory == cwd {
"match"
} else {
"different directory"
}
}
#[cfg_attr(test, mutants::skip)]
fn match_noun(count: usize) -> &'static str {
if count == 1 { "match" } else { "matches" }
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use std::path::PathBuf;
use super::*;
fn record(id: u32, dir: &str) -> SessionRecord {
SessionRecord {
id,
supervisor_pid: 1,
supervisor_creation_time: 1,
pipe_name: format!("pipe-{id}"),
launch_directory: PathBuf::from(dir),
command: vec!["app.exe".to_string()],
started_at_unix_ms: 1,
attached: false,
}
}
#[test]
fn no_sessions_fails() {
assert_eq!(
auto_detect(&[], Path::new("/work"), Trace::default()),
DetectOutcome::None
);
}
#[test]
fn unique_launch_directory_match() {
let live = [record(1, "/a"), record(2, "/work")];
assert_eq!(
auto_detect(&live, Path::new("/work"), Trace::default()),
DetectOutcome::Unique(SessionId::from_u32(2).unwrap())
);
}
#[test]
fn verbose_unique_match_is_still_unique() {
let live = [record(1, "/work")];
assert_eq!(
auto_detect(&live, Path::new("/work"), Trace::new(true)),
DetectOutcome::Unique(SessionId::from_u32(1).unwrap())
);
}
#[test]
fn verbose_reports_sessions_from_other_directories() {
let live = [record(1, "/other"), record(2, "/work")];
assert_eq!(
auto_detect(&live, Path::new("/work"), Trace::new(true)),
DetectOutcome::Unique(SessionId::from_u32(2).unwrap())
);
}
#[test]
fn verbose_reports_an_empty_store() {
assert_eq!(
auto_detect(&[], Path::new("/work"), Trace::new(true)),
DetectOutcome::None
);
}
#[test]
fn several_matches_are_ambiguous() {
let live = [record(1, "/work"), record(2, "/work")];
let outcome = auto_detect(&live, Path::new("/work"), Trace::default());
assert!(matches!(outcome, DetectOutcome::Ambiguous(_)));
}
#[test]
fn single_session_in_other_directory_is_ambiguous() {
let live = [record(1, "/other")];
let outcome = auto_detect(&live, Path::new("/work"), Trace::default());
assert!(matches!(outcome, DetectOutcome::Ambiguous(sessions) if sessions.len() == 1));
}
}