magi-code 0.61.0

Repository-aware CLI coding agent for terminal work
Documentation
use crate::{config::McPaths, tui::terminal::TerminalGuard};
use anyhow::Result;
use crossterm::event::{Event, KeyCode, KeyEventKind, MouseButton, MouseEvent, MouseEventKind};
use ratatui::{
    layout::{Constraint, Layout, Rect},
    style::{Color, Style},
    text::Line,
    widgets::Paragraph,
};
use ratatui_comfy_tabs::{
    TabDirection, TabNav, TabNavState, TabOrientation, TabWheelDirection, VerticalPosition,
    tab_border, vertical_label,
};
use ratatui_core::widgets::StatefulWidget;

mod workspace;
use workspace::WorkspaceState;

pub(crate) fn run_tui(paths: McPaths) -> Result<()> {
    let _terminal = TerminalGuard::enter()?;
    let mut app = Tui2App::new(paths);
    let input_events = crate::tui::TerminalInputBridge::spawn();
    let mut terminal =
        ratatui::Terminal::new(ratatui::backend::CrosstermBackend::new(std::io::stdout()))?;
    loop {
        terminal.draw(|frame| app.draw(frame))?;
        crossbeam_channel::select! {
            recv(input_events.receiver) -> event => {
                let Ok(event) = event else { return Ok(()); };
                match event {
                    Event::Key(key) if matches!(key.kind, KeyEventKind::Press | KeyEventKind::Repeat) => {
                        if app.tab_state.selected == 0 && app.workspace.handle_key(key.code) { continue; }
                        match key.code {
                            KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
                            KeyCode::Tab => app.tab_state.select_direction_wrapping(TabDirection::Next, 2),
                            KeyCode::BackTab => app.tab_state.select_direction_wrapping(TabDirection::Previous, 2),
                            _ => {}
                        }
                    }
                    Event::Mouse(mouse) => app.handle_mouse(mouse),
                    _ => {}
                }
            }
        }
    }
}

struct Tui2App {
    tab_state: TabNavState,
    tab_labels: Vec<String>,
    tab_hit_area: Rect,
    workspace: WorkspaceState,
}

impl Tui2App {
    fn new(paths: McPaths) -> Self {
        Self {
            tab_state: TabNavState::default(),
            tab_labels: vec![vertical_label("Workspaces"), vertical_label("Nexus")],
            tab_hit_area: Rect::default(),
            workspace: WorkspaceState::new(paths),
        }
    }

    fn handle_mouse(&mut self, mouse: MouseEvent) {
        let tab_refs: Vec<&str> = self.tab_labels.iter().map(String::as_str).collect();
        let nav = TabNav::new(&tab_refs, self.tab_state.selected)
            .orientation(TabOrientation::Vertical)
            .vertical_position(VerticalPosition::Left)
            .mouse_click(true)
            .mouse_wheel(true);
        match mouse.kind {
            MouseEventKind::Down(MouseButton::Left) => {
                self.tab_state
                    .handle_mouse_click(&nav, self.tab_hit_area, mouse.column, mouse.row);
            }
            MouseEventKind::ScrollDown => {
                self.tab_state.handle_mouse_wheel(
                    &nav,
                    self.tab_hit_area,
                    mouse.column,
                    mouse.row,
                    TabWheelDirection::Down,
                );
            }
            MouseEventKind::ScrollUp => {
                self.tab_state.handle_mouse_wheel(
                    &nav,
                    self.tab_hit_area,
                    mouse.column,
                    mouse.row,
                    TabWheelDirection::Up,
                );
            }
            _ => {}
        }
    }

    fn draw(&mut self, frame: &mut ratatui::Frame<'_>) {
        let area = frame.area();
        let app_bg = Color::Rgb(15, 15, 25);
        let bar_bg = Color::Rgb(25, 25, 45);
        let tab_border_color = Color::Rgb(50, 50, 80);

        frame.render_widget(Paragraph::new("").style(Style::default().bg(app_bg)), area);

        let [body_area, footer_area] =
            Layout::vertical([Constraint::Fill(1), Constraint::Length(1)]).areas(area);

        let tab_refs: Vec<&str> = self.tab_labels.iter().map(String::as_str).collect();
        let nav = TabNav::new(&tab_refs, self.tab_state.selected)
            .orientation(TabOrientation::Vertical)
            .vertical_position(VerticalPosition::Left)
            .style(Style::default().fg(Color::DarkGray).bg(app_bg))
            .highlight_style(Style::default().fg(Color::LightBlue).bg(app_bg))
            .border_style(Style::default().fg(tab_border_color).bg(app_bg))
            .border_set(tab_border::Rnd)
            .mouse_click(true)
            .mouse_wheel(true);
        let rail_width = nav.vertical_rail_width();
        let [tabs_area, content_area] =
            Layout::horizontal([Constraint::Length(rail_width), Constraint::Fill(1)])
                .areas(body_area);

        self.tab_hit_area = tabs_area;
        self.tab_state.ensure_selected_visible(&nav, tabs_area);
        StatefulWidget::render(nav, tabs_area, frame.buffer_mut(), &mut self.tab_state);

        match self.tab_state.selected {
            0 => self.workspace.render(content_area, frame.buffer_mut()),
            _ => {
                let content = Paragraph::new("Nexus — coming soon")
                    .style(Style::default().fg(Color::Gray).bg(app_bg));
                frame.render_widget(content, content_area);
            }
        }

        let footer = Paragraph::new(Line::from(
            " q/Esc exit | Tab next tab | Shift+Tab previous tab | click tabs",
        ))
        .style(Style::default().fg(Color::Gray).bg(bar_bg));
        frame.render_widget(footer, footer_area);
    }
}