1use super::{
4 CharSearch, CharSearchDirection, ColumnMotion, Counted, ModeSwitch, Motion, NormalCommand,
5 NormalGrammar, NormalGrammarOutput, SearchOutcome, VimCursor, VimMode, VimSearchState,
6 VimSelectionState, VimStatusLine, VisualMode, motion,
7};
8
9#[derive(Clone, Debug, Default)]
11pub struct NormalState {
12 grammar: NormalGrammar,
14 last_char_search: Option<CharSearch>,
16}
17
18impl NormalState {
19 pub const fn reset_grammar(&mut self) {
21 self.grammar.reset();
22 }
23
24 pub fn feed(
26 &mut self,
27 token: super::KeyToken,
28 context: NormalCommandContext<'_>,
29 ) -> NormalGrammarOutput {
30 match self.feed_command(token) {
31 NormalGrammarOutput::Command(command) => {
32 self.apply_command(command, context);
33 NormalGrammarOutput::Command(command)
34 }
35 output @ (NormalGrammarOutput::Pending | NormalGrammarOutput::Unmatched) => output,
36 }
37 }
38
39 pub fn feed_command(&mut self, token: super::KeyToken) -> NormalGrammarOutput {
41 self.grammar.feed(token)
42 }
43
44 pub fn apply_command(&mut self, command: NormalCommand, context: NormalCommandContext<'_>) {
46 let NormalCommandContext {
47 text,
48 cursor,
49 mode,
50 selection_state,
51 search_state,
52 command_state,
53 status_line,
54 } = context;
55
56 match command {
57 NormalCommand::Motion(motion) => {
58 self.apply_counted_motion(text, cursor, motion);
59 }
60 NormalCommand::ModeSwitch(ModeSwitch::VisualCharacterwise) => {
61 toggle_visual_mode(
62 text,
63 cursor,
64 mode,
65 selection_state,
66 VisualMode::Characterwise,
67 );
68 }
69 NormalCommand::ModeSwitch(ModeSwitch::VisualLinewise) => {
70 toggle_visual_mode(text, cursor, mode, selection_state, VisualMode::Linewise);
71 }
72 NormalCommand::ExCommandStart => {
73 status_line.clear();
74 command_state.start();
75 }
76 NormalCommand::SearchStart(direction) => {
77 status_line.clear();
78 search_state.start(direction);
79 }
80 NormalCommand::SearchRepeat(direction) => {
81 let outcome = search_state.repeat_relative(text, cursor.byte_index(), direction);
82 apply_search_outcome(text, cursor, status_line, outcome);
83 }
84 NormalCommand::ViewportPosition(_) | NormalCommand::Operator { .. } => {}
85 }
86 }
87
88 pub fn apply_counted_motion(
90 &mut self,
91 text: &str,
92 cursor: &mut VimCursor,
93 counted: Counted<Motion>,
94 ) {
95 match counted.item {
96 Motion::CharSearch(search) => {
97 self.last_char_search = Some(search);
98 for _step in 0..counted.count.get() {
99 cursor.apply_motion(text, Motion::CharSearch(search));
100 }
101 }
102 Motion::RepeatCharSearch => {
103 if let Some(search) = self.last_char_search {
104 for _step in 0..counted.count.get() {
105 cursor.apply_motion(text, Motion::CharSearch(search));
106 }
107 }
108 }
109 Motion::RepeatCharSearchReversed => {
110 if let Some(search) = self.last_char_search.map(reverse_char_search) {
111 for _step in 0..counted.count.get() {
112 cursor.apply_motion(text, Motion::CharSearch(search));
113 }
114 }
115 }
116 Motion::LineAddress(_) => cursor.apply_motion(text, counted.item),
117 Motion::Column(ColumnMotion::ScreenColumn) => {
118 cursor.set_byte_index(
119 text,
120 motion::apply_screen_column_motion(
121 text,
122 cursor.byte_index(),
123 counted.count.get(),
124 ),
125 );
126 }
127 motion => {
128 for _step in 0..counted.count.get() {
129 cursor.apply_motion(text, motion);
130 }
131 }
132 }
133 }
134}
135
136pub struct NormalCommandContext<'state> {
138 pub text: &'state str,
140 pub cursor: &'state mut VimCursor,
142 pub mode: &'state mut VimMode,
144 pub selection_state: &'state mut VimSelectionState,
146 pub search_state: &'state mut VimSearchState,
148 pub command_state: &'state mut super::VimCommandState,
150 pub status_line: &'state mut VimStatusLine,
152}
153
154fn toggle_visual_mode(
156 text: &str,
157 cursor: &VimCursor,
158 mode: &mut VimMode,
159 selection_state: &mut VimSelectionState,
160 visual_mode: VisualMode,
161) {
162 if *mode == VimMode::Visual(visual_mode) {
163 *mode = VimMode::Normal;
164 selection_state.clear();
165 } else {
166 *mode = VimMode::Visual(visual_mode);
167 selection_state.start(text, cursor.byte_index());
168 }
169}
170
171const fn reverse_char_search(search: CharSearch) -> CharSearch {
173 let direction = match search.direction {
174 CharSearchDirection::Backward => CharSearchDirection::Forward,
175 CharSearchDirection::Forward => CharSearchDirection::Backward,
176 };
177
178 CharSearch {
179 direction,
180 ..search
181 }
182}
183
184pub fn apply_search_outcome(
186 text: &str,
187 cursor: &mut VimCursor,
188 status_line: &mut VimStatusLine,
189 outcome: SearchOutcome,
190) {
191 match outcome {
192 SearchOutcome::Match { byte_index } => {
193 cursor.set_byte_index(text, byte_index);
194 status_line.clear();
195 }
196 SearchOutcome::Error(error) => status_line.set_error(error),
197 }
198}