1use crate::prelude::*;
4use crate::state::ops::cursor_ops;
5use crate::state::ops::{GotoInsertModeVariant, Operation};
6use crate::state::{StateDataAccess, StateMachine, Stateful};
7use crate::ui::canvas::CursorStyle;
8use crate::ui::tree::*;
9use crate::ui::widget::command_line::indicator::IndicatorSymbol;
10use crate::ui::widget::window::WindowNode;
11
12use compact_str::CompactString;
13use crossterm::event::{Event, KeyCode, KeyEventKind};
14
15#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)]
16pub struct NormalStateful {}
18
19impl NormalStateful {
20 fn get_operation(&self, event: &Event) -> Option<Operation> {
21 match event {
22 Event::FocusGained => None,
23 Event::FocusLost => None,
24 Event::Key(key_event) => match key_event.kind {
25 KeyEventKind::Press => {
26 trace!("Event::key:{:?}", key_event);
27 match key_event.code {
28 KeyCode::Up | KeyCode::Char('k') => {
29 Some(Operation::CursorMoveUpBy(1))
30 }
31 KeyCode::Down | KeyCode::Char('j') => {
32 Some(Operation::CursorMoveDownBy(1))
33 }
34 KeyCode::Left | KeyCode::Char('h') => {
35 Some(Operation::CursorMoveLeftBy(1))
36 }
37 KeyCode::Right | KeyCode::Char('l') => {
38 Some(Operation::CursorMoveRightBy(1))
39 }
40 KeyCode::Home => Some(Operation::CursorMoveLeftBy(usize::MAX)),
41 KeyCode::End => Some(Operation::CursorMoveRightBy(usize::MAX)),
42 KeyCode::Char('i') => {
43 Some(Operation::GotoInsertMode(GotoInsertModeVariant::Keep))
44 }
45 KeyCode::Char('a') => {
46 Some(Operation::GotoInsertMode(GotoInsertModeVariant::Append))
47 }
48 KeyCode::Char('o') => {
49 Some(Operation::GotoInsertMode(GotoInsertModeVariant::NewLine))
50 }
51 KeyCode::Char(':') => Some(Operation::GotoCommandLineExMode),
52 _ => None,
55 }
56 }
57 KeyEventKind::Repeat => None,
58 KeyEventKind::Release => None,
59 },
60 Event::Mouse(_mouse_event) => None,
61 Event::Paste(_paste_string) => None,
62 Event::Resize(_columns, _rows) => None,
63 }
64 }
65}
66
67impl Stateful for NormalStateful {
68 fn handle(&self, data_access: StateDataAccess, event: Event) -> StateMachine {
69 if let Some(op) = self.get_operation(&event) {
70 return self.handle_op(data_access, op);
71 }
72
73 StateMachine::NormalMode(NormalStateful::default())
74 }
75
76 fn handle_op(
77 &self,
78 data_access: StateDataAccess,
79 op: Operation,
80 ) -> StateMachine {
81 match op {
82 Operation::GotoInsertMode(insert_motion) => {
83 self.goto_insert_mode(&data_access, insert_motion)
84 }
85 Operation::GotoCommandLineExMode => {
86 self.goto_command_line_ex_mode(&data_access)
87 }
88 Operation::CursorMoveBy((_, _))
95 | Operation::CursorMoveUpBy(_)
96 | Operation::CursorMoveDownBy(_)
97 | Operation::CursorMoveLeftBy(_)
98 | Operation::CursorMoveRightBy(_)
99 | Operation::CursorMoveTo((_, _)) => self.cursor_move(&data_access, op),
100 _ => unreachable!(),
101 }
102 }
103}
104
105impl NormalStateful {
106 pub fn goto_command_line_ex_mode(
107 &self,
108 data_access: &StateDataAccess,
109 ) -> StateMachine {
110 let tree = data_access.tree.clone();
111 let mut tree = lock!(tree);
112
113 let current_window = tree.current_window_mut().unwrap();
115 debug_assert!(current_window.cursor_id().is_some());
116 let cursor = match current_window.remove_cursor().unwrap() {
117 WindowNode::Cursor(mut cursor) => {
118 cursor.set_style(&CursorStyle::SteadyBar);
119 cursor
120 }
121 _ => unreachable!(),
122 };
123 debug_assert!(current_window.cursor_id().is_none());
124
125 debug_assert!(tree.command_line_mut().is_some());
127 let cmdline = tree.command_line_mut().unwrap();
128
129 cmdline.show_input();
130
131 let _previous_cursor = cmdline.insert_cursor(cursor);
132 debug_assert!(_previous_cursor.is_none());
133 cmdline.move_cursor_to(0, 0);
134 cmdline.indicator_mut().set_symbol(IndicatorSymbol::Ex);
135
136 StateMachine::CommandLineExMode(super::CommandLineExStateful::default())
137 }
138}
139
140impl NormalStateful {
141 fn _goto_command_line_search_forward_mode(
142 &self,
143 _data_access: &StateDataAccess,
144 ) -> StateMachine {
145 StateMachine::CommandLineSearchForwardMode(
146 super::CommandLineSearchForwardStateful::default(),
147 )
148 }
149}
150
151impl NormalStateful {
152 fn _goto_command_line_search_backward_mode(
153 &self,
154 _data_access: &StateDataAccess,
155 ) -> StateMachine {
156 StateMachine::CommandLineSearchBackwardMode(
157 super::CommandLineSearchBackwardStateful::default(),
158 )
159 }
160}
161
162impl NormalStateful {
163 pub fn goto_insert_mode(
164 &self,
165 data_access: &StateDataAccess,
166 insert_motion: GotoInsertModeVariant,
167 ) -> StateMachine {
168 let tree = data_access.tree.clone();
169 let mut tree = lock!(tree);
170
171 match insert_motion {
172 GotoInsertModeVariant::Keep => {}
173 GotoInsertModeVariant::Append => {
174 let current_window = tree.current_window_mut().unwrap();
175 let current_window_id = current_window.id();
176 let buffer = current_window.buffer().upgrade().unwrap();
177 let buffer = lock!(buffer);
178 let op = Operation::CursorMoveRightBy(1);
179 cursor_ops::cursor_move(
180 &mut tree,
181 current_window_id,
182 buffer.text(),
183 op,
184 true,
185 );
186 }
187 GotoInsertModeVariant::NewLine => {
188 let current_window = tree.current_window_mut().unwrap();
189 let current_window_id = current_window.id();
190 let buffer = current_window.buffer().upgrade().unwrap();
191 let mut buffer = lock!(buffer);
192 let op = Operation::CursorMoveRightBy(usize::MAX);
193 cursor_ops::cursor_move(
194 &mut tree,
195 current_window_id,
196 buffer.text(),
197 op,
198 true,
199 );
200 let eol =
201 CompactString::new(format!("{}", buffer.options().end_of_line()));
202 cursor_ops::cursor_insert(
203 &mut tree,
204 current_window_id,
205 buffer.text_mut(),
206 eol,
207 );
208 }
209 };
210
211 let current_window = tree.current_window_mut().unwrap();
212 let cursor = current_window.cursor_mut().unwrap();
213 cursor.set_style(&CursorStyle::SteadyBar);
214
215 StateMachine::InsertMode(super::InsertStateful::default())
216 }
217}
218
219impl NormalStateful {
220 pub fn cursor_move(
222 &self,
223 data_access: &StateDataAccess,
224 op: Operation,
225 ) -> StateMachine {
226 let tree = data_access.tree.clone();
227 let mut tree = lock!(tree);
228 let current_window = tree.current_window_mut().unwrap();
229 let current_window_id = current_window.id();
230 let buffer = current_window.buffer().upgrade().unwrap();
231 let buffer = lock!(buffer);
232
233 cursor_ops::cursor_move(
234 &mut tree,
235 current_window_id,
236 buffer.text(),
237 op,
238 false,
239 );
240 StateMachine::NormalMode(NormalStateful::default())
241 }
242}
243
244#[cfg(test)]
245use crate::buf::text::Text;
246#[cfg(test)]
247use crate::ui::viewport::{CursorViewport, ViewportSearchDirection};
248
249impl NormalStateful {
250 #[cfg(test)]
251 pub fn _target_cursor_exclude_eol(
253 &self,
254 cursor_viewport: &CursorViewport,
255 text: &Text,
256 op: Operation,
257 ) -> (usize, usize, ViewportSearchDirection) {
258 use crate::state::ops::cursor_ops::CursorMoveDirection;
259
260 let (target_cursor_char, target_cursor_line, move_direction) =
261 cursor_ops::normalize_to_cursor_move_to_exclude_eol(
262 text,
263 op,
264 cursor_viewport.char_idx(),
265 cursor_viewport.line_idx(),
266 );
267
268 let search_direction = match move_direction {
269 CursorMoveDirection::Up => ViewportSearchDirection::Up,
270 CursorMoveDirection::Down => ViewportSearchDirection::Down,
271 CursorMoveDirection::Left => ViewportSearchDirection::Left,
272 CursorMoveDirection::Right => ViewportSearchDirection::Right,
273 };
274 (target_cursor_char, target_cursor_line, search_direction)
275 }
276
277 #[cfg(test)]
278 pub fn _test_raw_cursor_move(
279 &self,
280 data_access: &StateDataAccess,
281 op: Operation,
282 ) {
283 let tree = data_access.tree.clone();
284 let mut tree = lock!(tree);
285
286 let (buffer, viewport, cursor_viewport, current_window_id) = {
287 let current_window = tree.current_window_mut().unwrap();
288 let buffer = current_window.buffer().upgrade().unwrap();
289 let viewport = current_window.viewport();
290 let cursor_viewport = current_window.cursor_viewport();
291 (buffer, viewport, cursor_viewport, current_window.id())
292 };
293 let buffer = lock!(buffer);
294
295 let (target_cursor_char, target_cursor_line, _search_direction) =
296 self._target_cursor_exclude_eol(&cursor_viewport, buffer.text(), op);
297
298 let vnode =
299 cursor_ops::editable_tree_node_mut(&mut tree, current_window_id);
300 let new_cursor_viewport = cursor_ops::raw_cursor_viewport_move_to(
301 vnode,
302 &viewport,
303 buffer.text(),
304 Operation::CursorMoveTo((target_cursor_char, target_cursor_line)),
305 );
306
307 tree.current_window_mut().unwrap().move_cursor_to(
308 new_cursor_viewport.column_idx() as isize,
309 new_cursor_viewport.row_idx() as isize,
310 );
311 }
312
313 #[cfg(test)]
314 pub fn _test_raw_window_scroll(
315 &self,
316 data_access: &StateDataAccess,
317 op: Operation,
318 ) {
319 let tree = data_access.tree.clone();
320 let mut tree = lock!(tree);
321 let (buffer, viewport, current_window_id) = {
322 let current_window = tree.current_window_mut().unwrap();
323 let buffer = current_window.buffer().upgrade().unwrap();
324 let viewport = current_window.viewport();
325 (buffer, viewport, current_window.id())
326 };
327 let buffer = lock!(buffer);
328
329 let (start_column, start_line) = cursor_ops::normalize_to_window_scroll_to(
330 op,
331 viewport.start_column_idx(),
332 viewport.start_line_idx(),
333 );
334
335 let vnode =
336 cursor_ops::editable_tree_node_mut(&mut tree, current_window_id);
337 cursor_ops::raw_viewport_scroll_to(
338 vnode,
339 &viewport,
340 buffer.text(),
341 Operation::WindowScrollTo((start_column, start_line)),
342 );
343 }
344}