steer_tui/tui/widgets/input_panel/
textarea.rs1use ratatui::layout::Rect;
4use ratatui::prelude::{Buffer, StatefulWidget, Widget};
5use ratatui::widgets::{Block, Scrollbar, ScrollbarOrientation, ScrollbarState};
6use tui_textarea::TextArea;
7
8use crate::tui::InputMode;
9use crate::tui::theme::{Component, Theme};
10
11#[derive(Debug)]
13pub struct TextAreaWidget<'a> {
14 textarea: &'a mut TextArea<'static>,
15 theme: &'a Theme,
16 block: Option<Block<'a>>,
17 mode: Option<InputMode>,
18}
19
20impl<'a> TextAreaWidget<'a> {
21 pub fn new(textarea: &'a mut TextArea<'static>, theme: &'a Theme) -> Self {
23 Self {
24 textarea,
25 theme,
26 block: None,
27 mode: None,
28 }
29 }
30
31 pub fn with_block(mut self, block: Block<'a>) -> Self {
33 self.block = Some(block);
34 self
35 }
36
37 pub fn with_mode(mut self, mode: InputMode) -> Self {
39 self.mode = Some(mode);
40 self
41 }
42}
43
44impl<'a> Widget for TextAreaWidget<'a> {
45 fn render(self, area: Rect, buf: &mut Buffer) {
46 let (inner_area, theme, _mode) = if let Some(block) = self.block {
48 let styled_block = if let Some(mode) = self.mode {
49 apply_mode_styling(block, mode, self.theme)
50 } else {
51 block
52 };
53 let inner = styled_block.inner(area);
54 styled_block.render(area, buf);
55 (inner, self.theme, self.mode)
56 } else {
57 (area, self.theme, self.mode)
58 };
59
60 let textarea_height = inner_area.height;
62 let content_lines = self.textarea.lines().len();
63 let needs_scrollbar = content_lines > textarea_height as usize;
64 let cursor_row = self.textarea.cursor().0;
65
66 self.textarea.set_block(Block::default());
68 self.textarea.render(inner_area, buf);
69
70 if needs_scrollbar {
72 let mut scrollbar_state = ScrollbarState::new(content_lines)
73 .position(cursor_row)
74 .viewport_content_length(textarea_height as usize);
75
76 let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
77 .begin_symbol(Some("▲"))
78 .end_symbol(Some("▼"))
79 .thumb_style(theme.style(Component::DimText));
80
81 let scrollbar_area = Rect {
82 x: area.x + area.width - 1,
83 y: area.y + 1,
84 width: 1,
85 height: area.height - 2,
86 };
87
88 scrollbar.render(scrollbar_area, buf, &mut scrollbar_state);
89 }
90 }
91}
92
93fn apply_mode_styling<'a>(mut block: Block<'a>, mode: InputMode, theme: &Theme) -> Block<'a> {
95 match mode {
96 InputMode::Simple | InputMode::VimInsert => {
97 let active = theme.style(Component::InputPanelBorderActive);
99 block = block.style(active).border_style(active);
100 }
101 InputMode::VimNormal => {
102 let text_style = theme.style(Component::InputPanelBorderActive);
104 let border_dim = theme.style(Component::InputPanelBorder);
105 block = block.style(text_style).border_style(border_dim);
106 }
107 InputMode::BashCommand => {
108 let style = theme.style(Component::InputPanelBorderCommand);
109 block = block.style(style).border_style(style);
110 }
111 InputMode::ConfirmExit => {
112 let style = theme.style(Component::InputPanelBorderError);
113 block = block.style(style).border_style(style);
114 }
115 InputMode::EditMessageSelection => {
116 let style = theme.style(Component::InputPanelBorderCommand);
117 block = block.style(style).border_style(style);
118 }
119 InputMode::FuzzyFinder => {
120 let style = theme.style(Component::InputPanelBorderActive);
121 block = block.style(style).border_style(style);
122 }
123 _ => {}
124 }
125 block
126}