rmux-server 0.9.0

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
use rmux_core::{HookBindingView, HookGlobalRoot, HookStore};
use rmux_proto::{HookName, OptionScopeSelector, ScopeSelector, ShowOptionsRequest, WindowTarget};

use super::super::HandlerState;

#[derive(Debug, Clone, Copy)]
struct HookFilter {
    hook: HookName,
    index: Option<u32>,
}

pub(super) fn render_named_hook(
    state: &HandlerState,
    request: &ShowOptionsRequest,
) -> Option<Vec<String>> {
    let filter = parse_hook_filter(request.name.as_deref()?)?;
    Some(render_hooks(state, request, Some(filter)))
}

pub(super) fn render_included_hooks(
    state: &HandlerState,
    request: &ShowOptionsRequest,
) -> Vec<String> {
    render_hooks(state, request, None)
}

fn render_hooks(
    state: &HandlerState,
    request: &ShowOptionsRequest,
    filter: Option<HookFilter>,
) -> Vec<String> {
    match &request.scope {
        OptionScopeSelector::ServerGlobal => Vec::new(),
        OptionScopeSelector::SessionGlobal => render_global(
            &state.hooks,
            HookGlobalRoot::Session,
            filter,
            request.value_only,
        ),
        OptionScopeSelector::WindowGlobal => render_global(
            &state.hooks,
            HookGlobalRoot::Window,
            filter,
            request.value_only,
        ),
        OptionScopeSelector::Session(session_name) => render_local(
            &state.hooks,
            HookGlobalRoot::Session,
            state
                .hooks
                .session_bindings_view(session_name, hook(filter)),
            Vec::new(),
            filter,
            request,
        ),
        OptionScopeSelector::Window(target) => {
            let Ok(explicit) = super::super::hook_bindings_view(
                state,
                &ScopeSelector::Window(target.clone()),
                hook(filter),
            ) else {
                return Vec::new();
            };
            render_local(
                &state.hooks,
                HookGlobalRoot::Window,
                explicit,
                Vec::new(),
                filter,
                request,
            )
        }
        OptionScopeSelector::Pane(target) => {
            let window =
                WindowTarget::with_window(target.session_name().clone(), target.window_index());
            let Ok(explicit) = super::super::hook_bindings_view(
                state,
                &ScopeSelector::Pane(target.clone()),
                hook(filter),
            ) else {
                return Vec::new();
            };
            let Ok(intermediate) = super::super::hook_bindings_view(
                state,
                &ScopeSelector::Window(window),
                hook(filter),
            ) else {
                return Vec::new();
            };
            render_local(
                &state.hooks,
                HookGlobalRoot::Window,
                explicit,
                intermediate,
                filter,
                request,
            )
        }
    }
}

fn render_global(
    store: &HookStore,
    root: HookGlobalRoot,
    filter: Option<HookFilter>,
    value_only: bool,
) -> Vec<String> {
    let explicit = store.global_bindings_view(root, hook(filter));
    HookStore::shipped_global_hooks(root, hook(filter))
        .into_iter()
        .flat_map(|hook_name| {
            let bindings = matching_bindings(&explicit, hook_name, filter.and_then(|f| f.index));
            if bindings.is_empty() {
                render_empty_hook(hook_name, filter.and_then(|f| f.index), false, value_only)
            } else {
                render_bindings(bindings, false, value_only)
            }
        })
        .collect()
}

fn render_local(
    store: &HookStore,
    root: HookGlobalRoot,
    explicit: Vec<HookBindingView>,
    intermediate: Vec<HookBindingView>,
    filter: Option<HookFilter>,
    request: &ShowOptionsRequest,
) -> Vec<String> {
    if !request.include_inherited {
        return render_bindings(
            filter_bindings(&explicit, filter),
            false,
            request.value_only,
        );
    }

    let global = store.global_bindings_view(root, hook(filter));
    HookStore::shipped_global_hooks(root, hook(filter))
        .into_iter()
        .flat_map(|hook_name| {
            let index = filter.and_then(|f| f.index);
            let local = matching_bindings(&explicit, hook_name, index);
            if !local.is_empty() {
                return render_bindings(local, false, request.value_only);
            }
            let parent = matching_bindings(&intermediate, hook_name, index);
            if !parent.is_empty() {
                return render_bindings(parent, true, request.value_only);
            }
            let inherited = matching_bindings(&global, hook_name, index);
            if inherited.is_empty() {
                render_empty_hook(hook_name, index, true, request.value_only)
            } else {
                render_bindings(inherited, true, request.value_only)
            }
        })
        .collect()
}

fn render_bindings(
    bindings: Vec<&HookBindingView>,
    inherited: bool,
    value_only: bool,
) -> Vec<String> {
    bindings
        .into_iter()
        .map(|binding| {
            if value_only {
                binding.command().to_owned()
            } else {
                let marker = if inherited { "*" } else { "" };
                format!(
                    "{}[{}]{marker} {}",
                    binding.hook(),
                    binding.index(),
                    binding.command()
                )
            }
        })
        .collect()
}

fn render_empty_hook(
    hook: HookName,
    index: Option<u32>,
    inherited: bool,
    value_only: bool,
) -> Vec<String> {
    if value_only {
        return Vec::new();
    }
    let marker = if inherited { "*" } else { "" };
    vec![match index {
        Some(index) => format!("{hook}[{index}]{marker} "),
        None => format!("{hook}{marker}"),
    }]
}

fn matching_bindings(
    bindings: &[HookBindingView],
    hook: HookName,
    index: Option<u32>,
) -> Vec<&HookBindingView> {
    bindings
        .iter()
        .filter(|binding| binding.hook() == hook)
        .filter(|binding| index.map(|index| binding.index() == index).unwrap_or(true))
        .collect()
}

fn filter_bindings(
    bindings: &[HookBindingView],
    filter: Option<HookFilter>,
) -> Vec<&HookBindingView> {
    bindings
        .iter()
        .filter(|binding| {
            filter
                .map(|filter| {
                    binding.hook() == filter.hook
                        && filter
                            .index
                            .map(|index| binding.index() == index)
                            .unwrap_or(true)
                })
                .unwrap_or(true)
        })
        .collect()
}

fn hook(filter: Option<HookFilter>) -> Option<HookName> {
    filter.map(|filter| filter.hook)
}

fn parse_hook_filter(value: &str) -> Option<HookFilter> {
    let (name, index) = match value.rsplit_once('[') {
        Some((name, index)) => (name, Some(index.strip_suffix(']')?.parse::<u32>().ok()?)),
        None => (value, None),
    };
    Some(HookFilter {
        hook: HookName::from_str(name)?,
        index,
    })
}