reovim_module_vim/resolvers/
case.rs1#![allow(clippy::missing_panics_doc)]
4
5use std::sync::RwLock;
16
17use {
18 reovim_driver_input::{
19 ExtensionMap, KeyEvent, KeySequence, ModeKeyResolver, ModeState, ModeTransition,
20 ResolveContext, ResolveInput, ResolveResult, SessionApiDyn,
21 },
22 reovim_driver_session::OperatorPendingState,
23 reovim_kernel::api::v1::{ModeId, Position},
24};
25
26use {
27 super::operator_common::{
28 KeymapAction, OperatorState, OperatorType, apply_keymap_policy, build_cancelled,
29 build_operator_execute, is_count_digit, is_escape, is_inclusive_motion,
30 is_line_operator_key, is_linewise_motion,
31 },
32 crate::{modes::VimMode, session_state::PendingMotion},
33};
34
35pub struct VimCaseResolver {
40 operator_type: OperatorType,
42 mode_id: ModeId,
44 parent_mode_id: ModeId,
46 pub state: RwLock<OperatorState>,
48}
49
50impl VimCaseResolver {
51 #[must_use]
53 pub fn new(operator_type: OperatorType) -> Self {
54 let mode_id = operator_type.mode_id();
55 Self {
56 operator_type,
57 mode_id,
58 parent_mode_id: VimMode::NORMAL_ID,
59 state: RwLock::new(OperatorState::new(operator_type)),
60 }
61 }
62
63 #[cfg(test)]
65 pub fn state(&self) -> OperatorState {
66 self.state.read().expect("lock poisoned").clone()
67 }
68
69 fn clear_state(&self) {
71 self.state.write().expect("lock poisoned").reset();
72 }
73}
74
75#[cfg_attr(coverage_nightly, coverage(off))]
76impl ModeKeyResolver for VimCaseResolver {
77 fn resolve_with_keymap(
78 &self,
79 key: &KeyEvent,
80 _state: &mut ModeState,
81 input: &ResolveInput<'_>,
82 ) -> ResolveResult {
83 if is_escape(key) {
84 self.clear_state();
85 return ResolveResult::ModeTransition(build_cancelled());
86 }
87
88 let mut state = self.state.write().expect("lock poisoned");
89
90 if is_count_digit(key, state.has_motion_count()) {
91 state.accumulate_motion_count(key);
92 return ResolveResult::Pending;
93 }
94
95 if is_line_operator_key(key, self.operator_type) {
97 let count = state.operator_count;
98 let motion_count = state.take_motion_count().unwrap_or(1);
99 let register = state.register;
100 state.clear_keys();
101 drop(state);
102
103 return ResolveResult::ModeTransition(ModeTransition::Pop {
104 result: Some(build_operator_execute(
105 self.operator_type,
106 Position::new(0, 0),
107 Position::new(0, 0),
108 true,
109 Some(count.unwrap_or(1) * motion_count),
110 register,
111 )),
112 });
113 }
114
115 state.push_key(*key);
116 let keys = state.keys();
117
118 let lookup_state = {
119 let state = input.keymap.query(input.mode, &keys);
120 if matches!(state, reovim_driver_input::KeyLookupState::NotFound) {
121 input.keymap.query(&self.parent_mode_id, &keys)
122 } else {
123 state
124 }
125 };
126
127 match apply_keymap_policy(&lookup_state) {
128 KeymapAction::Execute(cmd) => {
129 let explicit_count = state.explicit_count();
130 let _motion_count = state.take_motion_count();
131 state.clear_keys();
132 drop(state);
133
134 let ctx = ResolveContext {
135 count: explicit_count,
136 register: None,
137 keys,
138 metadata: std::collections::HashMap::new(),
139 };
140
141 ResolveResult::Execute(cmd, ctx)
142 }
143 KeymapAction::Pending => {
144 drop(state);
145 ResolveResult::Pending
146 }
147 KeymapAction::Cancel => {
148 state.clear_keys();
149 drop(state);
150 self.clear_state();
151 ResolveResult::ModeTransition(build_cancelled())
152 }
153 }
154 }
155
156 #[allow(clippy::too_many_lines)]
157 #[cfg_attr(coverage_nightly, coverage(off))]
158 fn resolve_with_session(
159 &self,
160 key: &KeyEvent,
161 _mstate: &mut ModeState,
162 input: &ResolveInput<'_>,
163 session: &mut dyn SessionApiDyn,
164 _shared_extensions: &mut ExtensionMap,
165 client_extensions: &mut ExtensionMap,
166 ) -> ResolveResult {
167 tracing::debug!(key = ?key, operator = ?self.operator_type, "case resolver: resolve_with_session");
168
169 if is_escape(key) {
170 self.clear_state();
171 return ResolveResult::ModeTransition(build_cancelled());
172 }
173
174 let mut state = self.state.write().expect("lock poisoned");
175
176 if !state.initialized {
178 if let Some(vim) = client_extensions.get_mut::<crate::VimSessionState>() {
179 state.operator_count = vim.pending_count.take();
180 state.register = vim.pending_register.take();
181 }
182 state.initialized = true;
183 }
184
185 if is_count_digit(key, state.has_motion_count()) {
186 state.accumulate_motion_count(key);
187 return ResolveResult::Pending;
188 }
189
190 if is_line_operator_key(key, self.operator_type) {
192 let count = state.operator_count;
193 let motion_count = state.take_motion_count().unwrap_or(1);
194 let register = state.register;
195 state.clear_keys();
196 drop(state);
197
198 let (start, end) = session.cursor_position().map_or_else(
199 || (Position::new(0, 0), Position::new(0, 0)),
200 |cursor_pos| {
201 let start = Position::new(cursor_pos.line, 0);
202 let total_count = count.unwrap_or(1) * motion_count;
203 let end_line = cursor_pos.line + total_count - 1;
204 let end = Position::new(end_line, 0);
205 (start, end)
206 },
207 );
208
209 return ResolveResult::ModeTransition(ModeTransition::Pop {
210 result: Some(build_operator_execute(
211 self.operator_type,
212 start,
213 end,
214 true,
215 Some(count.unwrap_or(1) * motion_count),
216 register,
217 )),
218 });
219 }
220
221 state.push_key(*key);
222 let keys = state.keys();
223
224 let lookup_state = {
225 let state = input.keymap.query(input.mode, &keys);
226 if matches!(state, reovim_driver_input::KeyLookupState::NotFound) {
227 input.keymap.query(&self.parent_mode_id, &keys)
228 } else {
229 state
230 }
231 };
232
233 match apply_keymap_policy(&lookup_state) {
234 KeymapAction::Execute(cmd) => {
235 let linewise = is_linewise_motion(&cmd);
236 let explicit_count = state.explicit_count();
237 let _motion_count = state.take_motion_count();
238 state.clear_keys();
239
240 if let Some(start_pos) = session.cursor_position() {
241 state.set_start_position(start_pos);
242 }
243
244 let inclusive = !linewise && is_inclusive_motion(&cmd);
245 if let Some(vim) = client_extensions.get_mut::<crate::VimSessionState>() {
246 vim.pending_motion = Some(PendingMotion::new(linewise, inclusive, false));
247 }
248
249 drop(state);
250
251 let ctx = ResolveContext {
252 count: explicit_count,
253 register: None,
254 keys,
255 metadata: std::collections::HashMap::new(),
256 };
257
258 ResolveResult::Execute(cmd, ctx)
259 }
260 KeymapAction::Pending => {
261 drop(state);
262 ResolveResult::Pending
263 }
264 KeymapAction::Cancel => {
265 state.clear_keys();
266 drop(state);
267 self.clear_state();
268 ResolveResult::ModeTransition(build_cancelled())
269 }
270 }
271 }
272
273 #[cfg_attr(coverage_nightly, coverage(off))]
274 fn on_command_complete(
275 &self,
276 session: &mut dyn SessionApiDyn,
277 _shared_extensions: &mut ExtensionMap,
278 client_extensions: &mut ExtensionMap,
279 ) -> Option<ModeTransition> {
280 let state = self.state.read().expect("lock poisoned");
281 let count = state.operator_count;
282 let register = state.register;
283 drop(state);
284
285 if let Some(op_state) = client_extensions.get_mut::<OperatorPendingState>()
287 && let Some(textobj_range) = op_state.take_textobj_range()
288 {
289 self.clear_state();
290 return Some(ModeTransition::Pop {
291 result: Some(build_operator_execute(
292 self.operator_type,
293 textobj_range.start,
294 textobj_range.end,
295 textobj_range.is_linewise,
296 count,
297 register,
298 )),
299 });
300 }
301
302 let vim = client_extensions.get_mut::<crate::VimSessionState>()?;
304
305 let _ = vim.pending_motion.as_ref()?;
308
309 let state = self.state.read().expect("lock poisoned");
310 let start_pos = state.start_position?;
311 drop(state);
312
313 let end_pos = session.cursor_position()?;
314
315 if start_pos == end_pos {
316 return None;
317 }
318
319 let motion = vim.pending_motion.take()?;
320
321 let (range_start, range_end) = {
322 let (start, end) = if start_pos <= end_pos {
323 (start_pos, end_pos)
324 } else {
325 (end_pos, start_pos)
326 };
327
328 if !motion.linewise && motion.inclusive {
329 (start, Position::new(end.line, end.column + 1))
330 } else {
331 (start, end)
332 }
333 };
334
335 self.clear_state();
336
337 Some(ModeTransition::Pop {
338 result: Some(build_operator_execute(
339 self.operator_type,
340 range_start,
341 range_end,
342 motion.linewise,
343 count,
344 register,
345 )),
346 })
347 }
348
349 fn mode_id(&self) -> &ModeId {
350 &self.mode_id
351 }
352
353 fn inherits_from(&self) -> Option<&ModeId> {
354 Some(&self.parent_mode_id)
355 }
356
357 fn pending_keys(&self) -> KeySequence {
358 self.state.read().expect("lock poisoned").keys()
359 }
360
361 fn reset(&mut self) {
362 self.state.write().expect("lock poisoned").reset();
363 }
364}