nimue-term 0.1.6

Terminal emulator with multiplexer capabilities designed for maximum productivity, git worktrees and agentic engineering.
use freya::{
    prelude::*,
    radio::use_radio,
};
use keyboard_types::Modifiers;

use crate::{
    components::TerminalPane,
    state::{
        AppChannel,
        AppState,
        TerminalState,
    },
};

#[derive(PartialEq)]
pub struct TerminalGrid {
    pub instance_id: usize,
    pub terminals: Vec<TerminalState>,
    pub on_terminal_reopen: Callback<usize, ()>,
}

fn create_grid_terminal_panel(
    terminals: &[TerminalState],
    terminal_index: usize,
    on_terminal_reopen: Callback<usize, ()>,
    focused_terminal_id: Option<AccessibilityId>,
    instance_id: usize,
) -> ResizablePanel {
    if let Some(terminal_state) = terminals.get(terminal_index) {
        let on_reopen = Callback::new(move |_| {
            on_terminal_reopen.call(terminal_index);
        });
        let is_focused = focused_terminal_id
            .map(|id| id == terminal_state.focus_id)
            .unwrap_or(false);
        let focus_id = terminal_state.focus_id;

        ResizablePanel::new(50.)
            .key(&terminal_state.terminal_id)
            .order(terminal_index)
            .min_size(5.)
            .child(
                terminal_state
                    .handle
                    .clone()
                    .map(|handle| {
                        TerminalPane {
                            handle,
                            on_reopen,
                            is_focused,
                            focus_id,
                            instance_id,
                            terminal_index,
                        }
                        .into_element()
                    })
                    .unwrap_or_else(|| "Failed to create terminal".into_element()),
            )
    } else {
        ResizablePanel::new(50.)
            .key(&terminal_index)
            .order(terminal_index)
            .min_size(5.)
            .child("No terminal")
    }
}

impl Component for TerminalGrid {
    fn render(&self) -> impl IntoElement {
        let instance_id = self.instance_id;
        let mut radio = use_radio::<AppState, AppChannel>(AppChannel::Instance(instance_id));

        let focused_terminal_id = radio
            .read()
            .instances
            .iter()
            .find(|i| i.id == instance_id)
            .and_then(|i| i.focused_terminal_id);

        rect()
            .expanded()
            .on_key_down(move |e: Event<KeyboardEventData>| {
                if e.modifiers.contains(Modifiers::ALT) {
                    match &e.key {
                        Key::Named(NamedKey::ArrowRight) => {
                            radio.write().focus_terminal_right(instance_id);
                        }
                        Key::Named(NamedKey::ArrowLeft) => {
                            radio.write().focus_terminal_left(instance_id);
                        }
                        Key::Named(NamedKey::ArrowDown) => {
                            radio.write().focus_terminal_down(instance_id);
                        }
                        Key::Named(NamedKey::ArrowUp) => {
                            radio.write().focus_terminal_up(instance_id);
                        }
                        _ => {}
                    }
                }
            })
            .child(
                ResizableContainer::new()
                    .direction(Direction::Horizontal)
                    .panel(
                        ResizablePanel::new(50.)
                            .key(&"left")
                            .order(0usize)
                            .min_size(5.)
                            .child(
                                ResizableContainer::new()
                                    .direction(Direction::Vertical)
                                    .panel(create_grid_terminal_panel(
                                        &self.terminals,
                                        0,
                                        self.on_terminal_reopen.clone(),
                                        focused_terminal_id,
                                        instance_id,
                                    ))
                                    .panel(create_grid_terminal_panel(
                                        &self.terminals,
                                        2,
                                        self.on_terminal_reopen.clone(),
                                        focused_terminal_id,
                                        instance_id,
                                    ))
                                    .into_element(),
                            ),
                    )
                    .panel(
                        ResizablePanel::new(50.)
                            .key(&"right")
                            .order(1usize)
                            .min_size(5.)
                            .child(
                                ResizableContainer::new()
                                    .direction(Direction::Vertical)
                                    .panel(create_grid_terminal_panel(
                                        &self.terminals,
                                        1,
                                        self.on_terminal_reopen.clone(),
                                        focused_terminal_id,
                                        instance_id,
                                    ))
                                    .panel(create_grid_terminal_panel(
                                        &self.terminals,
                                        3,
                                        self.on_terminal_reopen.clone(),
                                        focused_terminal_id,
                                        instance_id,
                                    ))
                                    .into_element(),
                            ),
                    ),
            )
    }
}