#[derive(Default)]
pub(crate) struct TabProfileState {
pub(crate) source_profile_id: Option<crate::profile::ProfileId>,
pub(crate) auto_applied_profile_id: Option<crate::profile::ProfileId>,
pub(crate) auto_applied_dir_profile_id: Option<crate::profile::ProfileId>,
pub(crate) auto_reason: Option<String>,
pub(crate) pinned: bool,
pub(crate) profile_icon: Option<String>,
pub(crate) pre_profile_title: Option<String>,
pub(crate) pre_profile_icon: Option<Option<String>>,
pub(crate) badge_override: Option<String>,
pub(crate) pre_badge_override: Option<Option<String>>,
pub(crate) declined_profile_id: Option<crate::profile::ProfileId>,
pub(crate) pending_command_id: Option<u64>,
pub(crate) ssh_auto_switched: bool,
pub(crate) evaluated_generation: u64,
}
impl TabProfileState {
pub(crate) fn effective_profile_id(&self) -> Option<crate::profile::ProfileId> {
self.auto_applied_profile_id
.or(self.auto_applied_dir_profile_id)
.or(self.source_profile_id)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum AutoRule {
Host,
Directory,
}
#[derive(Debug, Clone, Default)]
pub(crate) struct AutoApply {
pub(crate) profile_id: crate::profile::ProfileId,
pub(crate) title: String,
pub(crate) icon: Option<String>,
pub(crate) badge_text: Option<String>,
pub(crate) reason: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum TitleChange {
Keep,
Set(String),
}
impl TabProfileState {
pub(crate) fn apply_auto(
&mut self,
rule: AutoRule,
apply: AutoApply,
current_title: &str,
) -> TitleChange {
if self.pre_profile_title.is_none() {
self.pre_profile_title = Some(current_title.to_string());
}
if self.pre_profile_icon.is_none() {
self.pre_profile_icon = Some(self.profile_icon.clone());
}
if self.pre_badge_override.is_none() {
self.pre_badge_override = Some(self.badge_override.clone());
}
match rule {
AutoRule::Host => self.auto_applied_profile_id = Some(apply.profile_id),
AutoRule::Directory => self.auto_applied_dir_profile_id = Some(apply.profile_id),
}
self.auto_reason = Some(apply.reason);
self.profile_icon = apply.icon;
if apply.badge_text.is_some() {
self.badge_override = apply.badge_text;
}
TitleChange::Set(apply.title)
}
pub(crate) fn revert_auto(&mut self) -> TitleChange {
self.auto_applied_profile_id = None;
self.auto_applied_dir_profile_id = None;
self.auto_reason = None;
self.ssh_auto_switched = false;
if let Some(icon) = self.pre_profile_icon.take() {
self.profile_icon = icon;
}
if let Some(badge) = self.pre_badge_override.take() {
self.badge_override = badge;
}
match self.pre_profile_title.take() {
Some(title) => TitleChange::Set(title),
None => TitleChange::Keep,
}
}
}
pub(crate) fn profile_tooltip(
state: &TabProfileState,
name_of: impl Fn(crate::profile::ProfileId) -> Option<String>,
) -> Option<String> {
let auto = state
.auto_applied_profile_id
.or(state.auto_applied_dir_profile_id)
.and_then(&name_of);
let source = state.source_profile_id.and_then(&name_of);
let pin = if state.pinned { " ยท pinned" } else { "" };
match (auto, source) {
(Some(auto), _) => {
let reason = state.auto_reason.as_deref().unwrap_or("rule");
Some(format!("Profile: {auto} (auto: {reason}){pin}"))
}
(None, Some(source)) => Some(format!("Profile: {source}{pin}")),
(None, None) if state.pinned => Some("Profile: Default ยท pinned".to_string()),
(None, None) => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn names(id: crate::profile::ProfileId) -> Option<String> {
if id == uuid::Uuid::from_u128(1) {
Some("Work".to_string())
} else if id == uuid::Uuid::from_u128(2) {
Some("Prod SSH".to_string())
} else {
None
}
}
#[test]
fn the_tooltip_names_the_source_profile() {
let state = TabProfileState {
source_profile_id: Some(uuid::Uuid::from_u128(1)),
..Default::default()
};
assert_eq!(
profile_tooltip(&state, names).as_deref(),
Some("Profile: Work")
);
}
#[test]
fn the_tooltip_names_an_auto_applied_profile_and_its_rule() {
let state = TabProfileState {
source_profile_id: Some(uuid::Uuid::from_u128(1)),
auto_applied_profile_id: Some(uuid::Uuid::from_u128(2)),
auto_reason: Some("host prod.example.com".to_string()),
..Default::default()
};
assert_eq!(
profile_tooltip(&state, names).as_deref(),
Some("Profile: Prod SSH (auto: host prod.example.com)")
);
assert_eq!(state.effective_profile_id(), Some(uuid::Uuid::from_u128(2)));
}
#[test]
fn a_plain_tab_has_no_profile_line_and_a_deleted_profile_drops_out() {
assert_eq!(profile_tooltip(&TabProfileState::default(), names), None);
let state = TabProfileState {
source_profile_id: Some(uuid::Uuid::from_u128(99)),
..Default::default()
};
assert_eq!(profile_tooltip(&state, names), None);
}
#[test]
fn apply_then_revert_restores_title_icon_and_badge() {
let mut state = TabProfileState {
profile_icon: Some("๐".to_string()),
badge_override: Some("mine".to_string()),
..Default::default()
};
let change = state.apply_auto(
AutoRule::Host,
AutoApply {
profile_id: uuid::Uuid::from_u128(2),
title: "Prod".to_string(),
icon: Some("๐ฅ".to_string()),
badge_text: Some("PROD".to_string()),
reason: "host prod".to_string(),
},
"zsh",
);
assert_eq!(change, TitleChange::Set("Prod".to_string()));
assert_eq!(state.badge_override.as_deref(), Some("PROD"));
state.apply_auto(
AutoRule::Directory,
AutoApply {
profile_id: uuid::Uuid::from_u128(1),
title: "Work".to_string(),
..Default::default()
},
"Prod",
);
assert_eq!(state.revert_auto(), TitleChange::Set("zsh".to_string()));
assert_eq!(state.profile_icon.as_deref(), Some("๐"));
assert_eq!(state.badge_override.as_deref(), Some("mine"));
assert_eq!(state.effective_profile_id(), None);
assert_eq!(state.revert_auto(), TitleChange::Keep, "nothing left");
}
#[test]
fn a_pinned_tab_says_so() {
let state = TabProfileState {
source_profile_id: Some(uuid::Uuid::from_u128(1)),
pinned: true,
..Default::default()
};
assert_eq!(
profile_tooltip(&state, names).as_deref(),
Some("Profile: Work ยท pinned")
);
}
}