stynx-code-tui 3.6.2

Terminal user interface with ratatui for interactive sessions
Documentation
use ratatui::{
    buffer::Buffer,
    layout::Rect,
    style::{Modifier, Style},
    text::{Line, Span},
    widgets::{Paragraph, Widget, Wrap},
};

use crate::state::app_state::SidebarState;
use crate::theme;

pub struct Sidebar<'a> {
    state: &'a SidebarState,
}

impl<'a> Sidebar<'a> {
    pub fn new(state: &'a SidebarState) -> Self { Self { state } }
}

impl<'a> Widget for Sidebar<'a> {
    fn render(self, area: Rect, buf: &mut Buffer) {
        for y in area.y..area.y + area.height {
            for x in area.x..area.x + area.width {
                buf[(x, y)].set_style(Style::default().bg(theme::BACKGROUND_PANEL()));
            }
        }

        let inner = Rect {
            x: area.x + 2,
            y: area.y + 1,
            width: area.width.saturating_sub(4),
            height: area.height.saturating_sub(2),
        };

        let mut lines: Vec<Line<'static>> = Vec::new();

        lines.push(Line::from(Span::styled(
            self.state.title.clone(),
            Style::default()
                .fg(theme::TEXT())
                .bg(theme::BACKGROUND_PANEL())
                .add_modifier(Modifier::BOLD),
        )));
        if !self.state.session_id.is_empty() {
            lines.push(Line::from(Span::styled(
                self.state.session_id.clone(),
                Style::default().fg(theme::TEXT_MUTED()).bg(theme::BACKGROUND_PANEL()),
            )));
        }
        lines.push(Line::from(""));

        if !self.state.sessions.is_empty() {
            lines.push(Line::from(Span::styled(
                "Recent",
                Style::default()
                    .fg(theme::TEXT_MUTED())
                    .bg(theme::BACKGROUND_PANEL())
                    .add_modifier(Modifier::DIM),
            )));
            for s in self.state.sessions.iter().take(20) {
                let prefix = if s.pinned { "" } else { "  " };
                let title = if s.title.len() > 32 { format!("{}", &s.title[..31]) } else { s.title.clone() };
                lines.push(Line::from(vec![
                    Span::styled(prefix, Style::default().fg(theme::WARNING()).bg(theme::BACKGROUND_PANEL())),
                    Span::styled(title, Style::default().fg(theme::TEXT()).bg(theme::BACKGROUND_PANEL())),
                ]));
            }
        }

        let body_height = inner.height.saturating_sub(2);
        let body_area = Rect { height: body_height, ..inner };
        Paragraph::new(lines)
            .wrap(Wrap { trim: false })
            .style(Style::default().bg(theme::BACKGROUND_PANEL()))
            .render(body_area, buf);

        let footer_y = inner.y + inner.height.saturating_sub(1);
        let footer_area = Rect { x: inner.x, y: footer_y, width: inner.width, height: 1 };
        let footer = Line::from(vec![
            Span::styled("", Style::default().fg(theme::SUCCESS()).bg(theme::BACKGROUND_PANEL())),
            Span::styled("stynx", Style::default().fg(theme::TEXT_MUTED()).bg(theme::BACKGROUND_PANEL())),
            Span::styled(
                "-code",
                Style::default()
                    .fg(theme::TEXT())
                    .bg(theme::BACKGROUND_PANEL())
                    .add_modifier(Modifier::BOLD),
            ),
            Span::styled(
                format!(" v{}", self.state.version),
                Style::default().fg(theme::TEXT_MUTED()).bg(theme::BACKGROUND_PANEL()),
            ),
        ]);
        Paragraph::new(footer)
            .style(Style::default().bg(theme::BACKGROUND_PANEL()))
            .render(footer_area, buf);
    }
}