rmux-server 0.9.1

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
use super::super::super::{
    attach_support::{DisplayPanesClientState, DisplayPanesLabel},
    prompt_support::PromptInputEvent,
};
#[cfg(test)]
use super::super::super::{
    scripting_support::QueueExecutionContext, DetachedRequesterAuthority, RequesterOrigin,
};

pub(super) enum DisplayPanesOutcome {
    Stay,
    Close,
    Select(DisplayPanesLabel),
}

pub(super) fn update_display_panes_state(
    state: &mut DisplayPanesClientState,
    event: PromptInputEvent,
) -> DisplayPanesOutcome {
    match event {
        PromptInputEvent::Char(ch) if ch.is_ascii_alphanumeric() => {
            state.input.push(ch.to_ascii_lowercase());
            match match_display_panes_label(state) {
                DisplayPanesMatch::Exact(_label, true) => DisplayPanesOutcome::Stay,
                DisplayPanesMatch::Exact(label, false) => DisplayPanesOutcome::Select(label),
                DisplayPanesMatch::Prefix => DisplayPanesOutcome::Stay,
                DisplayPanesMatch::None => DisplayPanesOutcome::Close,
            }
        }
        PromptInputEvent::Backspace => {
            state.input.pop();
            DisplayPanesOutcome::Stay
        }
        PromptInputEvent::Enter => match match_display_panes_label(state) {
            DisplayPanesMatch::Exact(label, _) => DisplayPanesOutcome::Select(label),
            DisplayPanesMatch::Prefix | DisplayPanesMatch::None => DisplayPanesOutcome::Close,
        },
        PromptInputEvent::Escape | PromptInputEvent::Ctrl('c') => DisplayPanesOutcome::Close,
        _ => DisplayPanesOutcome::Close,
    }
}

enum DisplayPanesMatch {
    Exact(DisplayPanesLabel, bool),
    Prefix,
    None,
}

fn match_display_panes_label(state: &DisplayPanesClientState) -> DisplayPanesMatch {
    if state.input.is_empty() {
        return DisplayPanesMatch::Prefix;
    }

    let exact = state
        .labels
        .iter()
        .find(|label| label.label == state.input)
        .cloned();
    let has_longer_prefix = state.labels.iter().any(|label| {
        label.label.starts_with(&state.input) && label.label.len() > state.input.len()
    });
    if let Some(label) = exact {
        return DisplayPanesMatch::Exact(label, has_longer_prefix);
    }
    if state
        .labels
        .iter()
        .any(|label| label.label.starts_with(&state.input))
    {
        DisplayPanesMatch::Prefix
    } else {
        DisplayPanesMatch::None
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::handler::StableTargetIdentity;
    use rmux_proto::{PaneTarget, SessionName, WindowTarget};

    fn session_name(value: &str) -> SessionName {
        SessionName::new(value).expect("valid session")
    }

    #[test]
    fn accepts_letter_alias_labels() {
        let alpha = session_name("alpha");
        let target = PaneTarget::with_window(alpha.clone(), 0, 10);
        let target_identity = StableTargetIdentity::pane_for_test(target.clone());
        let mut state = DisplayPanesClientState {
            id: 1,
            origin: RequesterOrigin::new(1, DetachedRequesterAuthority::Denied),
            command_context: QueueExecutionContext::without_caller_cwd(),
            window: WindowTarget::with_window(alpha.clone(), 0),
            labels: vec![DisplayPanesLabel {
                label: "a".to_owned(),
                target,
                target_string: "=alpha:0.%10".to_owned(),
                target_identity,
            }],
            input: String::new(),
            template: None,
            clear_frame: Vec::new(),
        };

        match update_display_panes_state(&mut state, PromptInputEvent::Char('A')) {
            DisplayPanesOutcome::Select(label) => assert_eq!(label.label, "a"),
            DisplayPanesOutcome::Stay => panic!("letter alias should select"),
            DisplayPanesOutcome::Close => panic!("letter alias should not close"),
        }
    }
}