#![allow(
dead_code,
reason = "schemars reads these types through field attributes"
)]
use libtmux::{ResizeDirection, SplitDirection};
use schemars::JsonSchema;
#[derive(JsonSchema)]
#[schemars(rename_all = "snake_case")]
pub(crate) enum SplitDirectionSchema {
Above,
Below,
Left,
Right,
}
#[derive(JsonSchema)]
#[schemars(rename_all = "snake_case")]
pub(crate) enum ResizeDirectionSchema {
Up,
Down,
Left,
Right,
}
#[derive(JsonSchema)]
#[schemars(rename_all = "snake_case")]
pub(crate) enum SelectPaneDirectionSchema {
Up,
Down,
Left,
Right,
Last,
Next,
Previous,
}
#[derive(JsonSchema)]
#[schemars(rename_all = "snake_case")]
pub(crate) enum SelectWindowDirectionSchema {
Next,
Previous,
Last,
}
#[derive(JsonSchema)]
#[schemars(rename_all = "kebab-case")]
pub(crate) enum OptionScopeSchema {
Server,
GlobalSession,
GlobalWindow,
Session,
Window,
Pane,
}
#[derive(JsonSchema)]
#[schemars(rename_all = "snake_case")]
pub(crate) enum ChannelWaitOutcomeSchema {
Signalled,
Deadline,
}
pub(crate) const fn split_direction_word(direction: SplitDirection) -> &'static str {
match direction {
SplitDirection::Above => "above",
SplitDirection::Below => "below",
SplitDirection::Left => "left",
SplitDirection::Right => "right",
}
}
pub(crate) const fn resize_direction_word(direction: ResizeDirection) -> &'static str {
match direction {
ResizeDirection::Up => "up",
ResizeDirection::Down => "down",
ResizeDirection::Left => "left",
ResizeDirection::Right => "right",
}
}
pub(crate) const SPLIT_DIRECTIONS: [SplitDirection; 4] = [
SplitDirection::Above,
SplitDirection::Below,
SplitDirection::Left,
SplitDirection::Right,
];
pub(crate) const RESIZE_DIRECTIONS: [ResizeDirection; 4] = [
ResizeDirection::Up,
ResizeDirection::Down,
ResizeDirection::Left,
ResizeDirection::Right,
];
pub(crate) fn split_direction(word: &str) -> Option<SplitDirection> {
SPLIT_DIRECTIONS
.into_iter()
.find(|direction| split_direction_word(*direction) == word)
}
pub(crate) fn resize_direction(word: &str) -> Option<ResizeDirection> {
RESIZE_DIRECTIONS
.into_iter()
.find(|direction| resize_direction_word(*direction) == word)
}
pub(crate) fn split_direction_words() -> Vec<&'static str> {
SPLIT_DIRECTIONS
.into_iter()
.map(split_direction_word)
.collect()
}
pub(crate) fn resize_direction_words() -> Vec<&'static str> {
RESIZE_DIRECTIONS
.into_iter()
.map(resize_direction_word)
.collect()
}
pub(crate) fn words_or(words: &[&str]) -> String {
match words {
[] => String::new(),
[only] => (*only).to_owned(),
[first, second] => format!("{first} or {second}"),
[rest @ .., last] => format!("{}, or {last}", rest.join(", ")),
}
}
#[cfg(test)]
mod tests {
use super::{
ResizeDirectionSchema, SplitDirectionSchema, resize_direction, resize_direction_words,
split_direction, split_direction_words,
};
fn advertised<T: schemars::JsonSchema>() -> Vec<String> {
let schema = serde_json::to_value(schemars::schema_for!(T)).expect("a schema");
schema["enum"]
.as_array()
.expect("a closed vocabulary is a JSON Schema enum")
.iter()
.map(|value| {
value
.as_str()
.expect("a vocabulary word is a string")
.to_owned()
})
.collect()
}
#[test]
fn mcp_vocabulary_matches_the_core_enums() {
assert_eq!(
advertised::<SplitDirectionSchema>(),
split_direction_words()
);
assert_eq!(
advertised::<ResizeDirectionSchema>(),
resize_direction_words()
);
}
#[test]
fn every_advertised_direction_resolves() {
for word in split_direction_words() {
assert!(split_direction(word).is_some(), "split {word} resolves");
}
for word in resize_direction_words() {
assert!(resize_direction(word).is_some(), "resize {word} resolves");
}
assert!(split_direction("sideways").is_none());
assert!(resize_direction("above").is_none(), "not a resize word");
}
#[test]
fn a_vocabulary_reads_as_prose() {
use super::words_or;
assert_eq!(
words_or(&split_direction_words()),
"above, below, left, or right"
);
assert_eq!(
words_or(&resize_direction_words()),
"up, down, left, or right"
);
assert_eq!(words_or(&["next", "previous"]), "next or previous");
assert_eq!(words_or(&["only"]), "only");
assert_eq!(words_or(&[]), "");
}
}