rmux-server 0.1.1

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
use rmux_core::LifecycleEvent;
use rmux_proto::{
    ErrorResponse, NextLayoutResponse, OptionName, PaneTarget, PreviousLayoutResponse,
    ResizePaneAdjustment, ResizePaneResponse, Response, SelectLayoutResponse, WindowTarget,
};

use super::super::RequestHandler;
use crate::pane_terminals::HandlerState;

impl RequestHandler {
    pub(in crate::handler) async fn handle_select_layout(
        &self,
        request: rmux_proto::SelectLayoutRequest,
    ) -> Response {
        let layout = request.layout;
        let session_name = match &request.target {
            rmux_proto::SelectLayoutTarget::Session(session_name) => session_name.clone(),
            rmux_proto::SelectLayoutTarget::Window(target) => target.session_name().clone(),
        };
        let response = {
            let mut state = self.state.lock().await;
            let option_size = layout_main_pane_size_for_select_target(&state, &request.target);
            match state.mutate_session_and_resize_terminals(&session_name, |session| {
                let window_index = layout_window_index(session, &request.target);
                session.save_old_layout_in_window(window_index)?;
                session.select_layout_in_window_with_main_pane_size(
                    window_index,
                    layout,
                    option_size.width,
                    option_size.height,
                )?;
                Ok(SelectLayoutResponse { layout })
            }) {
                Ok(response) => Response::SelectLayout(response),
                Err(error) => Response::Error(ErrorResponse { error }),
            }
        };

        if matches!(response, Response::SelectLayout(_)) {
            let target = match &request.target {
                rmux_proto::SelectLayoutTarget::Session(session_name) => {
                    let state = self.state.lock().await;
                    state.sessions.session(session_name).map(|session| {
                        WindowTarget::with_window(
                            session_name.clone(),
                            session.active_window_index(),
                        )
                    })
                }
                rmux_proto::SelectLayoutTarget::Window(target) => Some(target.clone()),
            };
            if let Some(target) = target {
                self.emit(LifecycleEvent::WindowLayoutChanged { target })
                    .await;
            }
            self.refresh_attached_session(&session_name).await;
        }

        response
    }

    pub(in crate::handler) async fn handle_select_custom_layout(
        &self,
        request: rmux_proto::SelectCustomLayoutRequest,
    ) -> Response {
        let session_name = match &request.target {
            rmux_proto::SelectLayoutTarget::Session(session_name) => session_name.clone(),
            rmux_proto::SelectLayoutTarget::Window(target) => target.session_name().clone(),
        };
        let response = {
            let mut state = self.state.lock().await;
            match state.mutate_session_and_resize_terminals(&session_name, |session| {
                let window_index = layout_window_index(session, &request.target);
                session.save_old_layout_in_window(window_index)?;
                session.select_custom_layout_in_window(window_index, &request.layout)?;
                let layout = session
                    .window_at(window_index)
                    .expect("selected layout window exists")
                    .layout();
                Ok(SelectLayoutResponse { layout })
            }) {
                Ok(response) => Response::SelectLayout(response),
                Err(error) => Response::Error(ErrorResponse { error }),
            }
        };

        if matches!(response, Response::SelectLayout(_)) {
            if let Some(target) =
                layout_change_target_from_select_target(self, &request.target).await
            {
                self.emit(LifecycleEvent::WindowLayoutChanged { target })
                    .await;
            }
            self.refresh_attached_session(&session_name).await;
        }

        response
    }

    pub(in crate::handler) async fn handle_select_old_layout(
        &self,
        request: rmux_proto::SelectOldLayoutRequest,
    ) -> Response {
        let session_name = match &request.target {
            rmux_proto::SelectLayoutTarget::Session(session_name) => session_name.clone(),
            rmux_proto::SelectLayoutTarget::Window(target) => target.session_name().clone(),
        };
        let response = {
            let mut state = self.state.lock().await;
            match state.mutate_session_and_resize_terminals(&session_name, |session| {
                let window_index = layout_window_index(session, &request.target);
                let _ = session.reapply_old_layout_in_window(window_index)?;
                let layout = session
                    .window_at(window_index)
                    .expect("selected layout window exists")
                    .layout();
                Ok(SelectLayoutResponse { layout })
            }) {
                Ok(response) => Response::SelectLayout(response),
                Err(error) => Response::Error(ErrorResponse { error }),
            }
        };

        if matches!(response, Response::SelectLayout(_)) {
            if let Some(target) =
                layout_change_target_from_select_target(self, &request.target).await
            {
                self.emit(LifecycleEvent::WindowLayoutChanged { target })
                    .await;
            }
            self.refresh_attached_session(&session_name).await;
        }

        response
    }

    pub(in crate::handler) async fn handle_spread_layout(
        &self,
        request: rmux_proto::SpreadLayoutRequest,
    ) -> Response {
        let session_name = match &request.target {
            rmux_proto::SelectLayoutTarget::Session(session_name) => session_name.clone(),
            rmux_proto::SelectLayoutTarget::Window(target) => target.session_name().clone(),
        };
        let response = {
            let mut state = self.state.lock().await;
            match state.mutate_session_and_resize_terminals(&session_name, |session| {
                let window_index = layout_window_index(session, &request.target);
                session.save_old_layout_in_window(window_index)?;
                let _ = session.spread_layout_in_window(window_index)?;
                let layout = session
                    .window_at(window_index)
                    .expect("selected layout window exists")
                    .layout();
                Ok(SelectLayoutResponse { layout })
            }) {
                Ok(response) => Response::SelectLayout(response),
                Err(error) => Response::Error(ErrorResponse { error }),
            }
        };

        if matches!(response, Response::SelectLayout(_)) {
            if let Some(target) =
                layout_change_target_from_select_target(self, &request.target).await
            {
                self.emit(LifecycleEvent::WindowLayoutChanged { target })
                    .await;
            }
            self.refresh_attached_session(&session_name).await;
        }

        response
    }

    pub(in crate::handler) async fn handle_next_layout(
        &self,
        request: rmux_proto::NextLayoutRequest,
    ) -> Response {
        let session_name = request.target.session_name().clone();
        let response = {
            let mut state = self.state.lock().await;
            let option_size = layout_main_pane_size_for_window_target(&state, &request.target);
            match state.mutate_session_and_resize_terminals(&session_name, |session| {
                session.save_old_layout_in_window(request.target.window_index())?;
                let layout = session.next_layout_in_window_with_main_pane_size(
                    request.target.window_index(),
                    option_size.width,
                    option_size.height,
                )?;
                Ok(NextLayoutResponse { layout })
            }) {
                Ok(response) => Response::NextLayout(response),
                Err(error) => Response::Error(ErrorResponse { error }),
            }
        };

        if matches!(response, Response::NextLayout(_)) {
            self.emit(LifecycleEvent::WindowLayoutChanged {
                target: request.target,
            })
            .await;
            self.refresh_attached_session(&session_name).await;
        }

        response
    }

    pub(in crate::handler) async fn handle_previous_layout(
        &self,
        request: rmux_proto::PreviousLayoutRequest,
    ) -> Response {
        let session_name = request.target.session_name().clone();
        let response = {
            let mut state = self.state.lock().await;
            let option_size = layout_main_pane_size_for_window_target(&state, &request.target);
            match state.mutate_session_and_resize_terminals(&session_name, |session| {
                session.save_old_layout_in_window(request.target.window_index())?;
                let layout = session.previous_layout_in_window_with_main_pane_size(
                    request.target.window_index(),
                    option_size.width,
                    option_size.height,
                )?;
                Ok(PreviousLayoutResponse { layout })
            }) {
                Ok(response) => Response::PreviousLayout(response),
                Err(error) => Response::Error(ErrorResponse { error }),
            }
        };

        if matches!(response, Response::PreviousLayout(_)) {
            self.emit(LifecycleEvent::WindowLayoutChanged {
                target: request.target,
            })
            .await;
            self.refresh_attached_session(&session_name).await;
        }

        response
    }

    pub(in crate::handler) async fn handle_resize_pane(
        &self,
        request: rmux_proto::ResizePaneRequest,
    ) -> Response {
        let session_name = request.target.session_name().clone();
        let window_index = request.target.window_index();
        let pane_index = request.target.pane_index();
        let adjustment = request.adjustment;
        let response_target =
            PaneTarget::with_window(session_name.clone(), window_index, pane_index);
        let response = {
            let mut state = self.state.lock().await;
            match state.mutate_session_and_resize_terminals(&session_name, |session| {
                session.resize_pane_in_window(window_index, pane_index, adjustment)?;

                Ok(ResizePaneResponse {
                    target: response_target,
                    adjustment,
                })
            }) {
                Ok(response) => Response::ResizePane(response),
                Err(error) => Response::Error(ErrorResponse { error }),
            }
        };

        if matches!(response, Response::ResizePane(_))
            && !matches!(adjustment, ResizePaneAdjustment::NoOp)
        {
            self.emit(LifecycleEvent::WindowLayoutChanged {
                target: WindowTarget::with_window(session_name.clone(), window_index),
            })
            .await;
            self.refresh_attached_session(&session_name).await;
        }

        response
    }
}

fn layout_window_index(
    session: &rmux_core::Session,
    target: &rmux_proto::SelectLayoutTarget,
) -> u32 {
    match target {
        rmux_proto::SelectLayoutTarget::Session(_) => session.active_window_index(),
        rmux_proto::SelectLayoutTarget::Window(target) => target.window_index(),
    }
}

#[derive(Clone, Copy, Default)]
struct LayoutMainPaneSize {
    width: Option<u16>,
    height: Option<u16>,
}

fn layout_main_pane_size_for_select_target(
    state: &HandlerState,
    target: &rmux_proto::SelectLayoutTarget,
) -> LayoutMainPaneSize {
    match target {
        rmux_proto::SelectLayoutTarget::Session(session_name) => state
            .sessions
            .session(session_name)
            .map_or_else(LayoutMainPaneSize::default, |session| {
                layout_main_pane_size_for_window(state, session_name, session.active_window_index())
            }),
        rmux_proto::SelectLayoutTarget::Window(target) => {
            layout_main_pane_size_for_window_target(state, target)
        }
    }
}

fn layout_main_pane_size_for_window_target(
    state: &HandlerState,
    target: &WindowTarget,
) -> LayoutMainPaneSize {
    layout_main_pane_size_for_window(state, target.session_name(), target.window_index())
}

fn layout_main_pane_size_for_window(
    state: &HandlerState,
    session_name: &rmux_proto::SessionName,
    window_index: u32,
) -> LayoutMainPaneSize {
    LayoutMainPaneSize {
        width: option_dimension(state.options.resolve_for_window(
            session_name,
            window_index,
            OptionName::MainPaneWidth,
        )),
        height: option_dimension(state.options.resolve_for_window(
            session_name,
            window_index,
            OptionName::MainPaneHeight,
        )),
    }
}

fn option_dimension(value: Option<&str>) -> Option<u16> {
    value.and_then(|value| value.parse::<u16>().ok())
}

async fn layout_change_target_from_select_target(
    handler: &RequestHandler,
    target: &rmux_proto::SelectLayoutTarget,
) -> Option<WindowTarget> {
    match target {
        rmux_proto::SelectLayoutTarget::Session(session_name) => {
            let state = handler.state.lock().await;
            state.sessions.session(session_name).map(|session| {
                WindowTarget::with_window(session_name.clone(), session.active_window_index())
            })
        }
        rmux_proto::SelectLayoutTarget::Window(target) => Some(target.clone()),
    }
}