1
//! Inherent draw + intent handling for `ConsoleState`.
2
//!
3
//! The draw logic (scrollback + prompt + hint) lives here; the pure state
4
//! stays in `tabs::nms`. The console consumes no tab-layer intents (input is
5
//! routed via `handle_console` while focused), so `handle` always ignores.
6

            
7
use crate::event::Intent;
8
use crate::pane::PaneId;
9
use crate::tabs::nms::ConsoleState;
10
use crate::view::{DrawCtx, Handled};
11
use ratatui::Frame;
12
use ratatui::layout::{Constraint, Direction, Layout, Rect};
13
use ratatui::style::{Color, Modifier, Style};
14
use ratatui::text::{Line, Span};
15
use ratatui::widgets::{Block, Borders, Paragraph, Wrap};
16

            
17
11
fn draw_scrollback(state: &ConsoleState, frame: &mut Frame, area: Rect) {
18
11
    let visible = usize::from(area.height);
19
11
    let lines: Vec<Line> = state
20
11
        .visible_scrollback(visible)
21
11
        .iter()
22
32
        .map(|l| Line::from(l.as_str()))
23
11
        .collect();
24
11
    frame.render_widget(Paragraph::new(lines).wrap(Wrap { trim: false }), area);
25
11
}
26

            
27
11
fn draw_prompt(state: &ConsoleState, frame: &mut Frame, area: Rect, focused: bool) {
28
11
    let marker = if state.pending.is_empty() {
29
10
        "nms> "
30
    } else {
31
1
        "...> "
32
    };
33
11
    let content = format!("{marker}{}", state.input.buffer());
34
11
    let style = if focused {
35
2
        Style::default()
36
2
            .fg(Color::White)
37
2
            .add_modifier(Modifier::BOLD)
38
    } else {
39
9
        Style::default().fg(Color::Gray)
40
    };
41
11
    frame.render_widget(Paragraph::new(Span::styled(content, style)), area);
42
11
}
43

            
44
11
fn draw_hint(frame: &mut Frame, area: Rect, active: bool, pane: PaneId) {
45
11
    let hint = if active {
46
3
        match pane {
47
1
            PaneId::Scrollback => "Tab prompt  Up/Down scroll  PgUp/PgDn page  Esc blur",
48
2
            PaneId::Prompt => "Enter run  Esc blur  C-c interrupt  Up/Down history",
49
        }
50
    } else {
51
8
        "i/Enter focus  1-6 tabs  Tab next  q quit"
52
    };
53
11
    let line = Span::styled(hint, Style::default().fg(Color::DarkGray));
54
11
    frame.render_widget(Paragraph::new(line), area);
55
11
}
56

            
57
impl ConsoleState {
58
11
    pub fn draw(&self, frame: &mut Frame, area: Rect, ctx: &DrawCtx) {
59
11
        let title = if ctx.is_active && self.panes.is_focused(PaneId::Scrollback) {
60
1
            "Console — Scrollback ↕"
61
        } else {
62
10
            "Console"
63
        };
64
11
        let block = Block::default().borders(Borders::ALL).title(title);
65
11
        let inner = block.inner(area);
66
11
        frame.render_widget(block, area);
67

            
68
11
        let chunks = Layout::default()
69
11
            .direction(Direction::Vertical)
70
11
            .constraints([
71
11
                Constraint::Min(1),
72
11
                Constraint::Length(1),
73
11
                Constraint::Length(1),
74
11
            ])
75
11
            .split(inner);
76

            
77
11
        draw_scrollback(self, frame, chunks[0]);
78
11
        draw_prompt(
79
11
            self,
80
11
            frame,
81
11
            chunks[1],
82
11
            ctx.is_active && self.panes.is_focused(PaneId::Prompt),
83
        );
84
11
        draw_hint(frame, chunks[2], ctx.is_active, self.panes.focused());
85
11
    }
86

            
87
5
    pub fn handle(&mut self, _intent: Intent, _ctx: &DrawCtx) -> Handled {
88
5
        Handled::Ignored
89
5
    }
90
}