rsvim_core/state/fsm/
normal.rs

1//! The normal mode.
2
3use 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)]
16/// The finite-state-machine for normal mode.
17pub 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            // KeyCode::Char('/') => Some(Operation::GotoCommandLineSearchForwardMode),
53            // KeyCode::Char('?') => Some(Operation::GotoCommandLineSearchBackwardMode),
54            _ => 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::GotoCommandLineSearchForwardMode => {
89      //   self.goto_command_line_search_forward_mode(&data_access)
90      // }
91      // Operation::GotoCommandLineSearchBackwardMode => {
92      //   self.goto_command_line_search_backward_mode(&data_access)
93      // }
94      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    // Remove cursor from current window
114    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    // Insert to command-line
126    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  /// Cursor move in current window, with buffer scroll.
221  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  // Returns `(target_cursor_char, target_cursor_line, viewport_search_direction)`.
252  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}