1pub mod ast;
2mod cursor;
3pub mod editor;
4pub mod parser;
5mod render;
6mod rich_text;
7pub mod state;
8mod text_buffer;
9mod text_wrap;
10mod viewport;
11mod virtual_document;
12
13use std::time::Duration;
14
15use ratatui::{
16 crossterm::event::{KeyCode, KeyEvent, KeyModifiers},
17 layout::Size,
18};
19
20use crate::{
21 app::{calc_scroll_amount, ActivePane, Message as AppMessage, ScrollAmount},
22 explorer,
23 note_editor::state::{EditMode, NoteEditorState, View},
24 outline, toast,
25};
26
27#[derive(Clone, Debug, PartialEq)]
28pub enum Message {
29 Save,
30 SwitchPaneNext,
31 SwitchPanePrevious,
32 ToggleExplorer,
33 ToggleOutline,
34 ToggleView,
35 EditView,
36 ReadView,
37 Exit,
38 KeyEvent(KeyEvent),
39 CursorUp,
40 CursorLeft,
41 CursorRight,
42 CursorWordForward,
43 CursorWordBackward,
44 CursorDown,
45 ScrollUp(ScrollAmount),
46 ScrollDown(ScrollAmount),
47 ScrollToTop,
48 ScrollToBottom,
49 JumpToBlock(usize),
50 Delete,
51 InsertMode,
52}
53
54pub fn update<'a>(
56 message: Message,
57 screen_size: Size,
58 state: &mut NoteEditorState,
59) -> Option<AppMessage<'a>> {
60 let vim_mode = state.vim_mode();
61 match message {
62 Message::CursorLeft => state.cursor_left(1),
63 Message::CursorRight => state.cursor_right(1),
64 Message::JumpToBlock(idx) => state.cursor_jump(idx),
65 Message::CursorUp => {
66 state.cursor_up(1);
67 return Some(AppMessage::Outline(outline::Message::SelectAt(
68 state.current_block(),
69 )));
70 }
71 Message::CursorDown => {
72 state.cursor_down(1);
73 return Some(AppMessage::Outline(outline::Message::SelectAt(
74 state.current_block(),
75 )));
76 }
77 Message::ScrollUp(scroll_amount) => {
78 state.cursor_up(calc_scroll_amount(
79 &scroll_amount,
80 screen_size.height.into(),
81 ));
82 return Some(AppMessage::Outline(outline::Message::SelectAt(
83 state.current_block(),
84 )));
85 }
86 Message::ScrollDown(scroll_amount) => {
87 state.cursor_down(calc_scroll_amount(
88 &scroll_amount,
89 screen_size.height.into(),
90 ));
91 return Some(AppMessage::Outline(outline::Message::SelectAt(
92 state.current_block(),
93 )));
94 }
95 Message::ScrollToTop => {
96 state.cursor_up(usize::MAX);
97 return Some(AppMessage::Outline(outline::Message::SelectAt(
98 state.current_block(),
99 )));
100 }
101 Message::ScrollToBottom => {
102 state.cursor_to_end();
103 return Some(AppMessage::Outline(outline::Message::SelectAt(
104 state.current_block(),
105 )));
106 }
107 _ => {}
108 };
109
110 match state.view {
111 View::Edit(..) if state.insert_mode() => match message {
112 Message::CursorWordForward => state.cursor_word_forward(),
113 Message::CursorWordBackward => state.cursor_word_backward(),
114 Message::ToggleView | Message::ReadView => {
115 state.set_insert_mode(false);
116 state.exit_insert();
117 state.set_view(View::Read);
118 return Some(AppMessage::UpdateSelectedNoteContent((
119 state.content.to_string(),
120 Some(state.ast_nodes.clone()),
121 )));
122 }
123 Message::KeyEvent(key) => {
124 match key.code {
125 KeyCode::Char(c) => {
126 state.insert_char(c);
127 }
128 KeyCode::Enter => {
129 state.insert_char('\n');
130 }
131 _ => {}
132 }
133
134 return Some(AppMessage::UpdateSelectedNoteContent((
135 state.content.to_string(),
136 None,
137 )));
138 }
139 Message::Delete => {
140 state.delete_char();
141 }
142 Message::Exit => {
143 state.set_insert_mode(false);
144 state.exit_insert();
145 if !vim_mode {
146 state.set_view(View::Read);
147 }
148 return Some(AppMessage::UpdateSelectedNoteContent((
149 state.content.to_string(),
150 Some(state.ast_nodes.clone()),
151 )));
152 }
153 _ => {}
154 },
155 View::Edit(..) => match message {
156 Message::CursorWordForward => state.cursor_word_forward(),
158 Message::CursorWordBackward => state.cursor_word_backward(),
159 Message::InsertMode | Message::EditView => state.set_insert_mode(true),
160 Message::ToggleView | Message::ReadView => {
161 state.exit_insert();
162 state.set_view(View::Read);
163 return Some(AppMessage::UpdateSelectedNoteContent((
164 state.content.to_string(),
165 Some(state.ast_nodes.clone()),
166 )));
167 }
168 Message::ToggleExplorer => {
169 return Some(AppMessage::Explorer(explorer::Message::Toggle));
170 }
171 Message::ToggleOutline => {
172 return Some(AppMessage::Outline(outline::Message::Toggle));
173 }
174 Message::SwitchPaneNext => {
175 state.set_active(false);
176 return Some(AppMessage::SetActivePane(ActivePane::Outline));
177 }
178 Message::SwitchPanePrevious => {
179 state.set_active(false);
180 return Some(AppMessage::SetActivePane(ActivePane::Explorer));
181 }
182 Message::Save => {
183 let modified = state.modified();
184 match state.save_to_file() {
185 Ok(_) if modified => {
186 return Some(AppMessage::Batch(vec![
187 AppMessage::UpdateSelectedNoteContent((
188 state.content.to_string(),
189 None,
190 )),
191 AppMessage::Toast(toast::Message::Create(toast::Toast::success(
192 "File saved",
193 Duration::from_secs(2),
194 ))),
195 ]))
196 }
197 Err(_) => {
198 return Some(AppMessage::Toast(toast::Message::Create(
199 toast::Toast::error("Failed to save file", Duration::from_secs(2)),
200 )))
201 }
202 _ => {}
203 }
204 }
205 _ => {}
206 },
207 View::Read => match message {
208 Message::ToggleView if state.editor_enabled() => {
209 state.set_view(View::Edit(EditMode::Source))
210 }
211 Message::EditView | Message::InsertMode if state.editor_enabled() => {
212 state.set_view(View::Edit(EditMode::Source));
213 state.set_insert_mode(true);
214 }
215 Message::ReadView => state.set_view(View::Read),
216 Message::ToggleExplorer => {
217 return Some(AppMessage::Explorer(explorer::Message::Toggle));
218 }
219 Message::ToggleOutline => {
220 return Some(AppMessage::Outline(outline::Message::Toggle));
221 }
222 Message::SwitchPaneNext => {
223 state.set_active(false);
224 return Some(AppMessage::SetActivePane(ActivePane::Outline));
225 }
226 Message::SwitchPanePrevious => {
227 state.set_active(false);
228 return Some(AppMessage::SetActivePane(ActivePane::Explorer));
229 }
230 Message::Save => {
231 let modified = state.modified();
232 match state.save_to_file() {
233 Ok(_) if modified => {
234 return Some(AppMessage::Batch(vec![
235 AppMessage::UpdateSelectedNoteContent((
236 state.content.to_string(),
237 None,
238 )),
239 AppMessage::Toast(toast::Message::Create(toast::Toast::success(
240 "File saved",
241 Duration::from_secs(2),
242 ))),
243 ]))
244 }
245 Err(_) => {
246 return Some(AppMessage::Toast(toast::Message::Create(
247 toast::Toast::error("Failed to save file", Duration::from_secs(2)),
248 )))
249 }
250 _ => {}
251 }
252 }
253 _ => {}
254 },
255 }
256
257 None
258}
259
260pub fn handle_editing_event(key: KeyEvent) -> Option<Message> {
261 match key.code {
262 KeyCode::Up => Some(Message::CursorUp),
263 KeyCode::Down => Some(Message::CursorDown),
264 KeyCode::Char('f') if key.modifiers.contains(KeyModifiers::ALT) => {
265 Some(Message::CursorWordForward)
266 }
267 KeyCode::Char('b') if key.modifiers.contains(KeyModifiers::ALT) => {
268 Some(Message::CursorWordBackward)
269 }
270 KeyCode::Left => Some(Message::CursorLeft),
271 KeyCode::Right => Some(Message::CursorRight),
272 KeyCode::Esc => Some(Message::Exit),
273 KeyCode::Backspace => Some(Message::Delete),
274 KeyCode::Char('e') if key.modifiers.contains(KeyModifiers::CONTROL) => {
275 Some(Message::ToggleView)
276 }
277 _ => Some(Message::KeyEvent(key)),
278 }
279}