rmux-server 0.1.1

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
use rmux_proto::{
    LinkWindowRequest, LinkWindowResponse, RmuxError, UnlinkWindowResponse, WindowTarget,
};

use super::{link_window_destination_index, session_not_found, window_pane_ids, HandlerState};

impl HandlerState {
    pub(crate) fn link_window(
        &mut self,
        request: LinkWindowRequest,
    ) -> Result<LinkWindowResponse, RmuxError> {
        let source_session_name = request.source.session_name().clone();
        let target_session_name = request.target.session_name().clone();
        let target_window_index = {
            let session = self
                .sessions
                .session(&target_session_name)
                .ok_or_else(|| session_not_found(&target_session_name))?;
            link_window_destination_index(
                session,
                request.target.window_index(),
                request.after,
                request.before,
            )?
        };

        if source_session_name == target_session_name
            && request.source.window_index() == target_window_index
        {
            return Err(RmuxError::Server(format!(
                "same index: {target_window_index}"
            )));
        }

        let source_window = self
            .sessions
            .session(&source_session_name)
            .and_then(|session| session.window_at(request.source.window_index()))
            .cloned()
            .ok_or_else(|| {
                RmuxError::invalid_target(
                    request.source.to_string(),
                    "window index does not exist in session",
                )
            })?;
        let source_pane_ids = source_window
            .panes()
            .iter()
            .map(|pane| pane.id())
            .collect::<Vec<_>>();
        let source_runtime_session = self
            .runtime_session_name_for_window(&source_session_name, request.source.window_index());
        self.terminals
            .ensure_panes_exist(&source_runtime_session, &source_pane_ids)?;

        let previous_target_session = self
            .sessions
            .session(&target_session_name)
            .cloned()
            .ok_or_else(|| session_not_found(&target_session_name))?;
        let replaced_target = if request.after || request.before {
            None
        } else {
            previous_target_session
                .window_at(target_window_index)
                .map(|_window| {
                    (
                        window_pane_ids(
                            &previous_target_session,
                            &target_session_name,
                            target_window_index,
                        )
                        .expect("window ids were already validated"),
                        self.window_link_count(&target_session_name, target_window_index),
                        self.runtime_session_name_for_window(
                            &target_session_name,
                            target_window_index,
                        ),
                    )
                })
        };

        let adjusted_source_window_index = if source_session_name == target_session_name
            && (request.after || request.before)
            && request.source.window_index() >= target_window_index
        {
            request.source.window_index().saturating_add(1)
        } else {
            request.source.window_index()
        };

        let inserted = {
            let session = self
                .sessions
                .session_mut(&target_session_name)
                .ok_or_else(|| session_not_found(&target_session_name))?;
            if request.after || request.before {
                session.make_room_for_window(target_window_index)?;
            }
            session.link_window(
                target_window_index,
                source_window,
                request.kill_destination,
                !request.detached,
            )?
        };

        if let (Some((pane_ids, link_count, runtime_session_name)), Some(_)) =
            (replaced_target, inserted)
        {
            let _ = self.options.remove_window(&WindowTarget::with_window(
                target_session_name.clone(),
                target_window_index,
            ));
            let _ = self.hooks.remove_window(&WindowTarget::with_window(
                target_session_name.clone(),
                target_window_index,
            ));
            self.clear_auto_named_window(&target_session_name, target_window_index);
            if link_count > 1 {
                let _ = self.detach_window_link_slot(&target_session_name, target_window_index);
            } else {
                for pane_id in &pane_ids {
                    if let Some(pipe) = self.remove_pane_pipe(&runtime_session_name, *pane_id) {
                        pipe.stop();
                    }
                }
                let _ = self
                    .terminals
                    .remove_pane_batch(&runtime_session_name, &pane_ids)?;
                let _ = self.remove_pane_outputs(&runtime_session_name, &pane_ids);
                self.remove_pane_lifecycles(&pane_ids);
            }
        }

        self.attach_window_link_slot(
            &source_session_name,
            adjusted_source_window_index,
            &target_session_name,
            target_window_index,
        );
        self.synchronize_linked_window_from_slot(
            &source_session_name,
            adjusted_source_window_index,
        )?;
        self.synchronize_session_group_from(&target_session_name)?;
        if source_session_name != target_session_name {
            self.synchronize_session_group_from(&source_session_name)?;
        }
        self.sync_pane_lifecycle_dimensions_for_session(&target_session_name);
        if source_session_name != target_session_name {
            self.sync_pane_lifecycle_dimensions_for_session(&source_session_name);
        }

        Ok(LinkWindowResponse {
            target: WindowTarget::with_window(target_session_name, target_window_index),
        })
    }

    pub(crate) fn unlink_window(
        &mut self,
        target: WindowTarget,
        kill_if_last: bool,
    ) -> Result<UnlinkWindowResponse, RmuxError> {
        let session_name = target.session_name().clone();
        let window_index = target.window_index();
        let link_count = self.window_link_count(&session_name, window_index);
        if link_count == 1 {
            if !kill_if_last {
                return Err(RmuxError::Message(
                    "window only linked to one session".to_owned(),
                ));
            }
            return Ok(UnlinkWindowResponse {
                target: self.kill_window(target, false)?.response.target,
            });
        }

        let active_window = {
            let session = self
                .sessions
                .session_mut(&session_name)
                .ok_or_else(|| session_not_found(&session_name))?;
            let target_was_active = session.active_window_index() == window_index;
            let previous_last = session.last_window_index();
            let _removed_window = session.remove_window(window_index)?;
            if target_was_active && previous_last == Some(session.active_window_index()) {
                session.restore_last_window_after_active_unlink();
            }
            session.active_window_index()
        };

        let _ = self.options.remove_window(&target);
        let _ = self.hooks.remove_window(&target);
        self.clear_auto_named_window(&session_name, window_index);
        let _ = self.detach_window_link_slot(&session_name, window_index);
        self.synchronize_session_group_from(&session_name)?;

        Ok(UnlinkWindowResponse {
            target: WindowTarget::with_window(session_name, active_window),
        })
    }
}