use super::row::{FIELD_SEPARATOR, FormatCodecError, FormatCodecErrorKind};
use super::{
CLIENT_INFO_SUPPLEMENTS, CLIENT_NAME, FormatDescriptor, InfoPlacement, ListProfile, PANE_ID,
PANE_INFO_SUPPLEMENTS, SESSION_ID, SESSION_INFO_SUPPLEMENTS, WINDOW_ID,
WINDOW_INFO_SUPPLEMENTS,
};
use crate::version::{ReleaseSuffix, ReleaseVersion, TmuxVersion};
impl ListProfile {
const fn baseline(self) -> &'static FormatDescriptor {
match self {
Self::Sessions => &SESSION_ID,
Self::Windows => &WINDOW_ID,
Self::Panes => &PANE_ID,
Self::Clients => &CLIENT_NAME,
}
}
const fn supplements(self) -> &'static [&'static FormatDescriptor] {
match self {
Self::Sessions => SESSION_INFO_SUPPLEMENTS,
Self::Windows => WINDOW_INFO_SUPPLEMENTS,
Self::Panes => PANE_INFO_SUPPLEMENTS,
Self::Clients => CLIENT_INFO_SUPPLEMENTS,
}
}
}
#[allow(
dead_code,
reason = "modelled and tested; only a projection of it is hydrated today"
)]
pub(super) enum PlanVersion {
Detected(TmuxVersion),
#[cfg(test)]
MinimumSupportedFixture,
}
impl PlanVersion {
fn dialect(&self) -> TransportDialect {
match self {
Self::Detected(version) => TransportDialect::for_version(version),
#[cfg(test)]
Self::MinimumSupportedFixture => TransportDialect::RawQ,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum TransportDialect {
RawQ,
Vis,
}
impl TransportDialect {
const VIS_FIRST: ReleaseVersion = ReleaseVersion::new(3, 4, ReleaseSuffix::FINAL);
const VIS_RESTORED: ReleaseVersion = ReleaseVersion::new(3, 6, ReleaseSuffix::FINAL);
pub(crate) fn for_version(version: &TmuxVersion) -> Self {
match version.behavior_release() {
Some(release) if release >= Self::VIS_FIRST && release < Self::VIS_RESTORED => {
Self::Vis
}
_ => Self::RawQ,
}
}
}
#[allow(
dead_code,
reason = "modelled and tested; only a projection of it is hydrated today"
)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum PlanPurpose {
Intrinsic(InfoPlacement),
Projection,
}
#[allow(
dead_code,
reason = "modelled and tested; only a projection of it is hydrated today"
)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum PlanFieldState {
Selected { slot: usize },
Unsupported,
Unproven,
}
#[allow(
dead_code,
reason = "modelled and tested; only a projection of it is hydrated today"
)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct PlannedField {
pub(crate) descriptor: &'static FormatDescriptor,
pub(crate) state: PlanFieldState,
}
#[allow(
dead_code,
reason = "modelled and tested; only a projection of it is hydrated today"
)]
pub(crate) struct FormatPlan {
pub(super) profile: ListProfile,
pub(super) version: PlanVersion,
pub(super) baseline: &'static FormatDescriptor,
pub(super) purpose: PlanPurpose,
pub(super) planned: Box<[PlannedField]>,
pub(super) descriptors: Box<[&'static FormatDescriptor]>,
pub(super) template: Box<str>,
pub(super) dialect: TransportDialect,
}
#[allow(
dead_code,
reason = "modelled and tested; only a projection of it is hydrated today"
)]
impl FormatPlan {
pub(crate) fn for_profile(profile: ListProfile, version: &TmuxVersion) -> Self {
select_for_profile(profile, version, profile.supplements())
}
pub(crate) fn for_descriptors(
profile: ListProfile,
version: &TmuxVersion,
requested: &[&'static FormatDescriptor],
) -> Result<Self, FormatCodecError> {
let baseline = profile.baseline();
let mut descriptors = vec![baseline];
let mut planned = vec![PlannedField {
descriptor: baseline,
state: PlanFieldState::Selected { slot: 0 },
}];
let mut seen = std::collections::HashSet::with_capacity(requested.len());
for descriptor in requested.iter().copied() {
if !descriptor.profiles().contains(profile) {
return Err(FormatCodecError::plan(
FormatCodecErrorKind::ScopeInapplicable,
descriptor,
profile,
));
}
if std::ptr::eq(descriptor, baseline) {
continue;
}
if !seen.insert(std::ptr::from_ref(descriptor)) {
return Err(FormatCodecError::plan(
FormatCodecErrorKind::DuplicateDescriptor,
descriptor,
profile,
));
}
let state = classify_field(version, descriptor, descriptors.len());
if matches!(state, PlanFieldState::Selected { .. }) {
descriptors.push(descriptor);
}
planned.push(PlannedField { descriptor, state });
}
Ok(Self::build(
profile,
PlanVersion::Detected(version.clone()),
baseline,
PlanPurpose::Projection,
planned,
descriptors,
))
}
#[cfg(test)]
pub(crate) fn for_codec_test(
descriptors: Vec<&'static FormatDescriptor>,
) -> Result<Self, FormatCodecError> {
Self::for_codec_test_with(descriptors, PlanVersion::MinimumSupportedFixture)
}
#[cfg(test)]
pub(crate) fn for_codec_test_at(
descriptors: Vec<&'static FormatDescriptor>,
version: &TmuxVersion,
) -> Result<Self, FormatCodecError> {
Self::for_codec_test_with(descriptors, PlanVersion::Detected(version.clone()))
}
#[cfg(test)]
fn for_codec_test_with(
descriptors: Vec<&'static FormatDescriptor>,
version: PlanVersion,
) -> Result<Self, FormatCodecError> {
let Some(baseline) = descriptors.first().copied() else {
return Err(FormatCodecError::empty_plan());
};
Ok(Self::build(
ListProfile::Sessions,
version,
baseline,
PlanPurpose::Projection,
descriptors
.iter()
.copied()
.enumerate()
.map(|(slot, descriptor)| PlannedField {
descriptor,
state: PlanFieldState::Selected { slot },
})
.collect(),
descriptors,
))
}
#[cfg(test)]
pub(crate) fn descriptors_for_test(&self) -> &[&'static FormatDescriptor] {
&self.descriptors
}
pub(crate) const fn profile(&self) -> ListProfile {
self.profile
}
pub(crate) const fn purpose(&self) -> PlanPurpose {
self.purpose
}
pub(crate) fn planned(&self) -> &[PlannedField] {
&self.planned
}
pub(crate) fn template(&self) -> &str {
&self.template
}
fn build(
profile: ListProfile,
version: PlanVersion,
baseline: &'static FormatDescriptor,
purpose: PlanPurpose,
planned: Vec<PlannedField>,
descriptors: Vec<&'static FormatDescriptor>,
) -> Self {
let descriptors = descriptors.into_boxed_slice();
let mut template = String::new();
for descriptor in &descriptors {
template.push_str("#{q:");
template.push_str(descriptor.name());
template.push('}');
template.push(FIELD_SEPARATOR as char);
}
let dialect = version.dialect();
Self {
profile,
version,
baseline,
purpose,
planned: planned.into_boxed_slice(),
descriptors,
template: template.into_boxed_str(),
dialect,
}
}
}
#[allow(
dead_code,
reason = "modelled and tested; only a projection of it is hydrated today"
)]
fn select_for_profile(
profile: ListProfile,
version: &TmuxVersion,
supplements: &'static [&'static FormatDescriptor],
) -> FormatPlan {
let baseline = profile.baseline();
let mut descriptors = Vec::with_capacity(supplements.len() + 1);
descriptors.push(baseline);
let mut planned = Vec::with_capacity(supplements.len() + 1);
planned.push(PlannedField {
descriptor: baseline,
state: PlanFieldState::Selected { slot: 0 },
});
for descriptor in supplements.iter().copied() {
if std::ptr::eq(descriptor, baseline) {
continue;
}
let state = classify_field(version, descriptor, descriptors.len());
if matches!(state, PlanFieldState::Selected { .. }) {
descriptors.push(descriptor);
}
planned.push(PlannedField { descriptor, state });
}
FormatPlan::build(
profile,
PlanVersion::Detected(version.clone()),
baseline,
PlanPurpose::Intrinsic(baseline.placement()),
planned,
descriptors,
)
}
#[allow(
dead_code,
reason = "modelled and tested; only a projection of it is hydrated today"
)]
fn classify_field(
version: &TmuxVersion,
descriptor: &'static FormatDescriptor,
selected_slot: usize,
) -> PlanFieldState {
match version.release() {
Some(release) if *release >= descriptor.minimum_release() => PlanFieldState::Selected {
slot: selected_slot,
},
Some(_) => PlanFieldState::Unsupported,
None if descriptor.minimum_release() <= TmuxVersion::MIN_SUPPORTED => {
PlanFieldState::Selected {
slot: selected_slot,
}
}
None => PlanFieldState::Unproven,
}
}
#[cfg(test)]
pub(super) fn for_profile_selection_test(
profile: ListProfile,
version: &TmuxVersion,
supplements: &'static [&'static FormatDescriptor],
) -> FormatPlan {
select_for_profile(profile, version, supplements)
}