basalt_tui/note_editor/
mod.rs

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 ratatui::{
14    crossterm::event::{KeyCode, KeyEvent, KeyModifiers},
15    layout::Size,
16};
17
18use crate::{
19    app::{calc_scroll_amount, ActivePane, Message as AppMessage, ScrollAmount},
20    explorer,
21    note_editor::state::{EditMode, NoteEditorState, View},
22    outline,
23};
24
25#[derive(Clone, Debug, PartialEq)]
26pub enum Message {
27    Save,
28    SwitchPaneNext,
29    SwitchPanePrevious,
30    ToggleExplorer,
31    ToggleOutline,
32    ToggleView,
33    EditView,
34    ReadView,
35    Exit,
36    KeyEvent(KeyEvent),
37    CursorUp,
38    CursorLeft,
39    CursorRight,
40    CursorWordForward,
41    CursorWordBackward,
42    CursorDown,
43    ScrollUp(ScrollAmount),
44    ScrollDown(ScrollAmount),
45    JumpToBlock(usize),
46    Delete,
47}
48
49// FIXME: Add resize message to handle resize related updates like cursor positioning
50pub fn update<'a>(
51    message: &Message,
52    screen_size: Size,
53    state: &mut NoteEditorState,
54) -> Option<AppMessage<'a>> {
55    match message {
56        Message::CursorLeft => state.cursor_left(1),
57        Message::CursorRight => state.cursor_right(1),
58        Message::JumpToBlock(idx) => state.cursor_jump(*idx),
59        Message::CursorUp => {
60            state.cursor_up(1);
61            return Some(AppMessage::Outline(outline::Message::SelectAt(
62                state.current_block(),
63            )));
64        }
65        Message::CursorDown => {
66            state.cursor_down(1);
67            return Some(AppMessage::Outline(outline::Message::SelectAt(
68                state.current_block(),
69            )));
70        }
71        Message::ScrollUp(scroll_amount) => {
72            state.cursor_up(calc_scroll_amount(scroll_amount, screen_size.height.into()));
73            return Some(AppMessage::Outline(outline::Message::SelectAt(
74                state.current_block(),
75            )));
76        }
77        Message::ScrollDown(scroll_amount) => {
78            state.cursor_down(calc_scroll_amount(scroll_amount, screen_size.height.into()));
79            return Some(AppMessage::Outline(outline::Message::SelectAt(
80                state.current_block(),
81            )));
82        }
83        _ => {}
84    };
85
86    match state.view {
87        View::Edit(..) => match message {
88            Message::CursorWordForward => state.cursor_word_forward(),
89            Message::CursorWordBackward => state.cursor_word_backward(),
90            Message::ToggleView => state.set_view(View::Read),
91            Message::KeyEvent(key) => {
92                match key.code {
93                    KeyCode::Char(c) => {
94                        state.insert_char(c);
95                    }
96                    KeyCode::Enter => {
97                        state.insert_char('\n');
98                    }
99                    _ => {}
100                }
101
102                return Some(AppMessage::UpdateSelectedNoteContent((
103                    state.content.to_string(),
104                    None,
105                )));
106            }
107            Message::Delete => {
108                state.delete_char();
109            }
110            Message::Exit => {
111                state.exit_insert();
112                state.set_view(View::Read);
113                return Some(AppMessage::UpdateSelectedNoteContent((
114                    state.content.to_string(),
115                    Some(state.ast_nodes.clone()),
116                )));
117            }
118            _ => {}
119        },
120        View::Read => match message {
121            Message::ToggleView => state.set_view(View::Edit(EditMode::Source)),
122            Message::EditView => state.set_view(View::Edit(EditMode::Source)),
123            Message::ReadView => state.set_view(View::Read),
124            Message::ToggleExplorer => {
125                return Some(AppMessage::Explorer(explorer::Message::Toggle));
126            }
127            Message::ToggleOutline => {
128                return Some(AppMessage::Outline(outline::Message::Toggle));
129            }
130            Message::SwitchPaneNext => {
131                state.set_active(false);
132                return Some(AppMessage::SetActivePane(ActivePane::Outline));
133            }
134            Message::SwitchPanePrevious => {
135                state.set_active(false);
136                return Some(AppMessage::SetActivePane(ActivePane::Explorer));
137            }
138            Message::Save => {
139                // FIXME: Implement proper error handling when toasts are available.
140                let _ = state.save_to_file();
141                return Some(AppMessage::UpdateSelectedNoteContent((
142                    state.content.to_string(),
143                    None,
144                )));
145            }
146            _ => {}
147        },
148    }
149
150    None
151}
152
153pub fn handle_editing_event(key: &KeyEvent) -> Option<Message> {
154    match key.code {
155        KeyCode::Up => Some(Message::CursorUp),
156        KeyCode::Down => Some(Message::CursorDown),
157        KeyCode::Char('f') if key.modifiers.contains(KeyModifiers::ALT) => {
158            Some(Message::CursorWordForward)
159        }
160        KeyCode::Char('b') if key.modifiers.contains(KeyModifiers::ALT) => {
161            Some(Message::CursorWordBackward)
162        }
163        KeyCode::Left => Some(Message::CursorLeft),
164        KeyCode::Right => Some(Message::CursorRight),
165        KeyCode::Esc => Some(Message::Exit),
166        KeyCode::Backspace => Some(Message::Delete),
167        KeyCode::Char('e') if key.modifiers.contains(KeyModifiers::CONTROL) => {
168            Some(Message::ToggleView)
169        }
170        _ => Some(Message::KeyEvent(*key)),
171    }
172}