rmux-core 0.9.1

Core session, pane, layout, format, hook, and buffer model for the RMUX terminal multiplexer.
Documentation
use super::target_error::invalid_window_target_with_reason;
use super::{PaneSwapOptions, Session};
use crate::{Pane, PaneId, Window};
use rmux_proto::RmuxError;

pub(super) fn validate_swap_destination(
    destination_window: &Window,
    incoming_pane: &Pane,
    replaced_pane_index: u32,
) -> Result<(), RmuxError> {
    let replaced_position = destination_window
        .pane_position(replaced_pane_index)
        .expect("validated replaced pane must exist");
    for (position, existing) in destination_window.panes().iter().enumerate() {
        if position != replaced_position && existing.id() == incoming_pane.id() {
            return Err(RmuxError::Server(format!(
                "pane id {} already exists in window {}",
                incoming_pane.id().as_u32(),
                destination_window.id()
            )));
        }
    }
    Ok(())
}

pub(super) fn apply_swap_between_windows(
    source_window: &mut Window,
    source: SwapPaneEntry,
    target_window: &mut Window,
    target: SwapPaneEntry,
    options: PaneSwapOptions,
) -> Result<(), RmuxError> {
    let source_active_before = source_window.active_pane_index();
    let target_active_before = target_window.active_pane_index();
    let source_active_before_id = source_window
        .active_pane()
        .expect("source window has an active pane")
        .id();
    let target_active_before_id = target_window
        .active_pane()
        .expect("target window has an active pane")
        .id();
    let source_last_before_id = source_window
        .last_pane_index()
        .and_then(|index| source_window.pane(index).map(Pane::id));
    let target_last_before_id = target_window
        .last_pane_index()
        .and_then(|index| target_window.pane(index).map(Pane::id));
    let source_pane_id = source.pane.id();
    let target_pane_id = target.pane.id();
    let select_target_in_source = !options.detached || source_active_before == source.index;
    let select_source_in_target = !options.detached || target_active_before == target.index;
    source_window.push_zoom(options.preserve_zoom);
    target_window.push_zoom(options.preserve_zoom);
    source_window.replace_pane_for_swap(source.index, target.pane)?;
    target_window.replace_pane_for_swap(target.index, source.pane)?;

    let source_active_after_id = if select_target_in_source {
        target_pane_id
    } else {
        source_active_before_id
    };
    let target_active_after_id = if select_source_in_target {
        source_pane_id
    } else {
        target_active_before_id
    };
    let source_last_after_id = swapped_window_last_pane_id(
        source_active_before_id,
        source_last_before_id,
        source_pane_id,
        target_pane_id,
        select_target_in_source,
    );
    let target_last_after_id = swapped_window_last_pane_id(
        target_active_before_id,
        target_last_before_id,
        target_pane_id,
        source_pane_id,
        select_source_in_target,
    );

    if select_target_in_source {
        source_window.renumber_panes_by_position_stamping(
            source_active_after_id,
            source_last_after_id,
            Some(source_active_before_id),
        );
    } else {
        source_window.renumber_panes_by_position(source_active_after_id, source_last_after_id);
    }
    if select_source_in_target {
        target_window.renumber_panes_by_position_stamping(
            target_active_after_id,
            target_last_after_id,
            Some(target_active_before_id),
        );
    } else {
        target_window.renumber_panes_by_position(target_active_after_id, target_last_after_id);
    }
    source_window.recalculate_geometry();
    target_window.recalculate_geometry();
    source_window.pop_zoom();
    target_window.pop_zoom();
    Ok(())
}

fn swapped_window_last_pane_id(
    active_before_id: PaneId,
    last_before_id: Option<PaneId>,
    outgoing_pane_id: PaneId,
    _incoming_pane_id: PaneId,
    selects_incoming: bool,
) -> Option<PaneId> {
    if selects_incoming {
        if active_before_id == outgoing_pane_id {
            return last_before_id.filter(|pane_id| *pane_id != outgoing_pane_id);
        }
        return Some(active_before_id);
    }

    match last_before_id {
        Some(last_pane_id) if last_pane_id == outgoing_pane_id => None,
        other => other,
    }
}

pub(super) struct SwapPaneEntry {
    pub(super) index: u32,
    pub(super) pane: Pane,
}

pub(super) const fn adjusted_insert_position(
    source_position: usize,
    target_position: usize,
) -> usize {
    if source_position < target_position {
        target_position
    } else {
        target_position + 1
    }
}

pub(super) const fn adjusted_insert_position_before(
    source_position: usize,
    target_position: usize,
) -> usize {
    if source_position < target_position {
        target_position.saturating_sub(1)
    } else {
        target_position
    }
}

pub(super) fn resolve_break_destination_index(
    session: &Session,
    target_window_index: Option<u32>,
    allowed_occupied_index: Option<u32>,
) -> Result<u32, RmuxError> {
    match target_window_index {
        Some(target_window_index) => {
            if session.window_at(target_window_index).is_some()
                && allowed_occupied_index != Some(target_window_index)
            {
                return Err(invalid_window_target_with_reason(
                    session.name(),
                    target_window_index,
                    "window index already exists in session",
                ));
            }

            Ok(target_window_index)
        }
        None => session.lowest_available_window_index_at_or_above(0),
    }
}