use super::{
Error, NO_CURRENT_TARGET, ObjectKind, OptionErrorKind, SENSITIVE_OUTPUT_WITHHELD,
ServerGoneKind,
};
const fn is_identity(kind: ObjectKind, target: &str) -> bool {
match kind {
ObjectKind::Session | ObjectKind::Client => true,
ObjectKind::Window => matches!(target.as_bytes().first(), Some(b'@')),
ObjectKind::Pane => matches!(target.as_bytes().first(), Some(b'%')),
}
}
impl Error {
pub(crate) fn refused(
command: &'static str,
exit_code: Option<i32>,
stderr: String,
target: Option<&std::ffi::OsStr>,
) -> Self {
const GONE: [(&str, ServerGoneKind); 4] = [
("no server running on", ServerGoneKind::NotRunning),
("error connecting to", ServerGoneKind::Unreachable),
("server exited unexpectedly", ServerGoneKind::Lost),
("server exited", ServerGoneKind::Stopped),
];
const MISSING: [(&str, ObjectKind); 7] = [
("can't find session:", ObjectKind::Session),
("can't find window:", ObjectKind::Window),
("can't find pane:", ObjectKind::Pane),
("can't find client:", ObjectKind::Client),
("no such session:", ObjectKind::Session),
("no such window:", ObjectKind::Window),
("no such pane:", ObjectKind::Pane),
];
const OPTION: [(&str, OptionErrorKind); 5] = [
("invalid option:", OptionErrorKind::Unknown),
("unknown option:", OptionErrorKind::Unknown),
("ambiguous option:", OptionErrorKind::Ambiguous),
("bad value:", OptionErrorKind::BadValue),
("value is invalid:", OptionErrorKind::BadValue),
];
for (prefix, kind) in GONE {
if stderr.trim_end().starts_with(prefix) {
return Self::ServerGone { command, kind };
}
}
for (prefix, kind) in OPTION {
if let Some(detail) = stderr.trim_end().strip_prefix(prefix) {
return Self::OptionRejected {
kind,
detail: detail.trim().to_owned(),
};
}
}
if let Some(name) = stderr.trim_end().strip_prefix("duplicate session:") {
return Self::SessionExists {
name: name.trim().to_owned(),
};
}
if let Some(target) = target.filter(|_| stderr.trim_end() == NO_CURRENT_TARGET) {
return Self::object_gone(&target.to_string_lossy());
}
for (prefix, kind) in MISSING {
let Some(echo) = stderr.trim_end().strip_prefix(prefix) else {
continue;
};
let echo = echo.trim();
if is_identity(kind, echo) {
return Self::ObjectGone {
kind,
id: echo.to_owned(),
};
}
return Self::LinkGone {
kind,
target: target.map_or_else(
|| echo.to_owned(),
|sent| sent.to_string_lossy().into_owned(),
),
};
}
Self::CommandFailed {
command,
exit_code,
stderr,
}
}
pub(crate) fn refused_withheld(command: &'static str, exit_code: Option<i32>) -> Self {
Self::CommandFailed {
command,
exit_code,
stderr: SENSITIVE_OUTPUT_WITHHELD.to_owned(),
}
}
pub(crate) fn from_refused_result(
command: &'static str,
result: &crate::CommandResult,
target: Option<&std::ffi::OsStr>,
) -> Self {
let stderr = result.stderr_lossy().into_owned();
if result.command().sensitive_argument_count() > 0 {
let classified = Self::refused(command, result.exit_code(), stderr, target);
if matches!(
&classified,
Self::ObjectGone { id, .. }
if target.is_some_and(|target| id == &target.to_string_lossy())
) || matches!(classified, Self::ServerGone { .. })
{
return classified;
}
Self::refused_withheld(command, result.exit_code())
} else {
Self::refused(command, result.exit_code(), stderr, target)
}
}
fn object_gone(target: &str) -> Self {
Self::ObjectGone {
kind: match target.as_bytes().first() {
Some(b'@') => ObjectKind::Window,
Some(b'%') => ObjectKind::Pane,
_ => ObjectKind::Session,
},
id: target.to_owned(),
}
}
}