kimun_notes/components/settings/
editor_section.rs1use ratatui::Frame;
2use ratatui::layout::{Constraint, Direction, Layout, Rect};
3use ratatui::widgets::{Block, Borders, Paragraph};
4
5use crate::components::Component;
6use crate::components::event_state::EventState;
7use crate::components::events::{AppTx, InputEvent};
8use crate::settings::themes::Theme;
9
10const MIN_AUTOSAVE_SECS: u64 = 5;
11const MAX_AUTOSAVE_SECS: u64 = 300;
12const STEP: u64 = 5;
13
14pub struct EditorSection {
15 pub autosave_interval_secs: u64,
16}
17
18impl EditorSection {
19 pub fn new(autosave_interval_secs: u64) -> Self {
20 Self {
21 autosave_interval_secs,
22 }
23 }
24}
25
26impl Component for EditorSection {
27 fn handle_input(&mut self, event: &InputEvent, _tx: &AppTx) -> EventState {
28 let InputEvent::Key(key) = event else {
29 return EventState::NotConsumed;
30 };
31 match key.code {
32 ratatui::crossterm::event::KeyCode::Left
33 | ratatui::crossterm::event::KeyCode::Char('h') => {
34 self.autosave_interval_secs = self
35 .autosave_interval_secs
36 .saturating_sub(STEP)
37 .max(MIN_AUTOSAVE_SECS);
38 EventState::Consumed
39 }
40 ratatui::crossterm::event::KeyCode::Right
41 | ratatui::crossterm::event::KeyCode::Char('l') => {
42 self.autosave_interval_secs =
43 (self.autosave_interval_secs + STEP).min(MAX_AUTOSAVE_SECS);
44 EventState::Consumed
45 }
46 _ => EventState::NotConsumed,
47 }
48 }
49
50 fn render(&mut self, f: &mut Frame, rect: Rect, theme: &Theme, focused: bool) {
51 let border_style = theme.border_style(focused);
52 let block = Block::default()
53 .title("Editor")
54 .borders(Borders::ALL)
55 .border_style(border_style)
56 .style(theme.base_style());
57 let inner = block.inner(rect);
58 f.render_widget(block, rect);
59
60 let rows = Layout::default()
61 .direction(Direction::Vertical)
62 .constraints([
63 Constraint::Length(1),
64 Constraint::Length(1),
65 Constraint::Min(0),
66 ])
67 .split(inner);
68
69 let label = Paragraph::new("Autosave Interval").style(theme.base_style());
70 f.render_widget(label, rows[0]);
71
72 let value = format!(" ◀ {}s ▶ (←/→ to change)", self.autosave_interval_secs);
73 let value_style = if focused {
74 ratatui::style::Style::default()
75 .fg(theme.accent.to_ratatui())
76 .bg(theme.bg.to_ratatui())
77 } else {
78 ratatui::style::Style::default()
79 .fg(theme.fg.to_ratatui())
80 .bg(theme.bg.to_ratatui())
81 };
82 f.render_widget(Paragraph::new(value).style(value_style), rows[1]);
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers};
90
91 fn key(code: KeyCode) -> InputEvent {
92 InputEvent::Key(KeyEvent {
93 code,
94 modifiers: KeyModifiers::NONE,
95 kind: KeyEventKind::Press,
96 state: KeyEventState::NONE,
97 })
98 }
99
100 #[test]
101 fn right_increases_interval_by_step() {
102 let (tx, _rx) = tokio::sync::mpsc::unbounded_channel();
103 let mut section = EditorSection::new(10);
104 section.handle_input(&key(KeyCode::Right), &tx);
105 assert_eq!(section.autosave_interval_secs, 15);
106 }
107
108 #[test]
109 fn left_decreases_interval_by_step() {
110 let (tx, _rx) = tokio::sync::mpsc::unbounded_channel();
111 let mut section = EditorSection::new(10);
112 section.handle_input(&key(KeyCode::Left), &tx);
113 assert_eq!(section.autosave_interval_secs, 5);
114 }
115
116 #[test]
117 fn left_clamps_at_min() {
118 let (tx, _rx) = tokio::sync::mpsc::unbounded_channel();
119 let mut section = EditorSection::new(5);
120 section.handle_input(&key(KeyCode::Left), &tx);
121 assert_eq!(section.autosave_interval_secs, MIN_AUTOSAVE_SECS);
122 }
123
124 #[test]
125 fn right_clamps_at_max() {
126 let (tx, _rx) = tokio::sync::mpsc::unbounded_channel();
127 let mut section = EditorSection::new(298);
128 section.handle_input(&key(KeyCode::Right), &tx);
129 assert_eq!(section.autosave_interval_secs, MAX_AUTOSAVE_SECS);
130 }
131
132 #[test]
133 fn l_key_increases_interval() {
134 let (tx, _rx) = tokio::sync::mpsc::unbounded_channel();
135 let mut section = EditorSection::new(10);
136 section.handle_input(&key(KeyCode::Char('l')), &tx);
137 assert_eq!(section.autosave_interval_secs, 15);
138 }
139
140 #[test]
141 fn h_key_decreases_interval() {
142 let (tx, _rx) = tokio::sync::mpsc::unbounded_channel();
143 let mut section = EditorSection::new(10);
144 section.handle_input(&key(KeyCode::Char('h')), &tx);
145 assert_eq!(section.autosave_interval_secs, 5);
146 }
147}