tui_canvas/canvas/actions/
dispatch.rs1use super::types::{ActionResult, CanvasAction};
5use crate::DataProvider;
6use crate::editor::EditorCore;
7use std::fmt::Display;
8
9impl<D: DataProvider> EditorCore<D> {
10 fn into_action_result<T, E: Display>(result: Result<T, E>) -> ActionResult {
11 match result {
12 Ok(_) => ActionResult::Success,
13 Err(err) => ActionResult::Error(err.to_string()),
14 }
15 }
16
17 pub fn execute(&mut self, action: CanvasAction) -> ActionResult {
19 use CanvasAction::*;
20 match action {
21 EnterEditMode => {
23 self.enter_edit_mode();
24 ActionResult::Success
25 }
26 EnterEditModeAfter => {
27 self.enter_append_mode();
28 ActionResult::Success
29 }
30 ExitEditMode => Self::into_action_result(self.exit_edit_mode()),
31 EnterHighlightMode => {
32 self.enter_highlight_mode();
33 ActionResult::Success
34 }
35 EnterHighlightModeLinewise => {
36 self.enter_highlight_line_mode();
37 ActionResult::Success
38 }
39 ExitHighlightMode => {
40 self.exit_highlight_mode();
41 ActionResult::Success
42 }
43
44 MoveLeft => {
46 if self.is_highlight_mode() {
47 self.move_left_with_selection();
48 ActionResult::Success
49 } else {
50 Self::into_action_result(self.move_left())
51 }
52 }
53 MoveRight => {
54 if self.is_highlight_mode() {
55 self.move_right_with_selection();
56 ActionResult::Success
57 } else {
58 Self::into_action_result(self.move_right())
59 }
60 }
61 MoveUp => {
62 if self.is_highlight_mode() {
63 self.move_up_with_selection();
64 } else {
65 self.move_up();
66 }
67 ActionResult::Success
68 }
69 MoveDown => {
70 if self.is_highlight_mode() {
71 self.move_down_with_selection();
72 } else {
73 self.move_down();
74 }
75 ActionResult::Success
76 }
77 MoveWordNext => {
78 if self.is_highlight_mode() {
79 self.move_word_next_with_selection();
80 } else {
81 self.move_word_next();
82 }
83 ActionResult::Success
84 }
85 MoveWordPrev => {
86 if self.is_highlight_mode() {
87 self.move_word_prev_with_selection();
88 } else {
89 self.move_word_prev();
90 }
91 ActionResult::Success
92 }
93 MoveWordEnd => {
94 if self.is_highlight_mode() {
95 self.move_word_end_with_selection();
96 } else {
97 self.move_word_end();
98 }
99 ActionResult::Success
100 }
101 MoveWordEndPrev => {
102 if self.is_highlight_mode() {
103 self.move_word_end_prev_with_selection();
104 } else {
105 self.move_word_end_prev();
106 }
107 ActionResult::Success
108 }
109 MoveBigWordNext => {
110 if self.is_highlight_mode() {
111 self.move_big_word_next_with_selection();
112 } else {
113 self.move_big_word_next();
114 }
115 ActionResult::Success
116 }
117 MoveBigWordPrev => {
118 if self.is_highlight_mode() {
119 self.move_big_word_prev_with_selection();
120 } else {
121 self.move_big_word_prev();
122 }
123 ActionResult::Success
124 }
125 MoveBigWordEnd => {
126 if self.is_highlight_mode() {
127 self.move_big_word_end_with_selection();
128 } else {
129 self.move_big_word_end();
130 }
131 ActionResult::Success
132 }
133 MoveBigWordEndPrev => {
134 if self.is_highlight_mode() {
135 self.move_big_word_end_prev_with_selection();
136 } else {
137 self.move_big_word_end_prev();
138 }
139 ActionResult::Success
140 }
141 MoveFirstLine => Self::into_action_result(self.move_first_line()),
142 MoveLastLine => Self::into_action_result(self.move_last_line()),
143 MoveLineStart => {
144 if self.is_highlight_mode() {
145 self.move_line_start_with_selection();
146 } else {
147 self.move_line_start();
148 }
149 ActionResult::Success
150 }
151 MoveLineEnd => {
152 if self.is_highlight_mode() {
153 self.move_line_end_with_selection();
154 } else {
155 self.move_line_end();
156 }
157 ActionResult::Success
158 }
159 NextField => {
160 self.next_field();
161 ActionResult::Success
162 }
163 PrevField => {
164 self.prev_field();
165 ActionResult::Success
166 }
167
168 DeleteBackward => Self::into_action_result(self.delete_backward()),
170 DeleteForward => Self::into_action_result(self.delete_forward()),
171 Undo => {
172 self.undo();
173 ActionResult::Success
174 }
175 Redo => {
176 self.redo();
177 ActionResult::Success
178 }
179 OpenLineBelow => Self::into_action_result(self.open_line_below()),
180 OpenLineAbove => Self::into_action_result(self.open_line_above()),
181
182 #[cfg(feature = "suggestions")]
184 TriggerSuggestions => {
185 let _ = self.trigger_suggestions().map(|(idx, query)| {
186 let items = self.data_provider.fetch_suggestions_sync(idx, &query);
187 if items.is_empty() {
188 self.dismiss_suggestions();
189 } else {
190 self.apply_suggestions(items);
191 }
192 });
193 ActionResult::Success
194 }
195 #[cfg(feature = "suggestions")]
196 SuggestionUp => {
197 self.suggestions_prev();
198 ActionResult::Success
199 }
200 #[cfg(feature = "suggestions")]
201 SuggestionDown => {
202 self.suggestions_next();
203 ActionResult::Success
204 }
205 #[cfg(feature = "suggestions")]
206 SelectSuggestion => {
207 let _ = self.apply_suggestion();
208 ActionResult::Success
209 }
210 #[cfg(feature = "suggestions")]
211 ExitSuggestions => {
212 self.dismiss_suggestions();
213 ActionResult::Success
214 }
215 #[cfg(not(feature = "suggestions"))]
216 TriggerSuggestions | SuggestionUp | SuggestionDown | SelectSuggestion
217 | ExitSuggestions => ActionResult::Message("suggestions feature is disabled".into()),
218
219 InsertChar(c) => Self::into_action_result(self.insert_char(c)),
221
222 Custom(name) => ActionResult::Message(format!("Unhandled custom action: {name}")),
224 }
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use crate::canvas::state::SelectionState;
232
233 #[derive(Clone)]
234 struct TestProvider {
235 fields: Vec<(&'static str, String)>,
236 }
237
238 impl TestProvider {
239 fn new(values: &[&'static str]) -> Self {
240 Self {
241 fields: values
242 .iter()
243 .enumerate()
244 .map(|(i, value)| {
245 let name = match i {
246 0 => "a",
247 1 => "b",
248 _ => "c",
249 };
250 (name, (*value).to_string())
251 })
252 .collect(),
253 }
254 }
255 }
256
257 impl DataProvider for TestProvider {
258 fn field_count(&self) -> usize {
259 self.fields.len()
260 }
261
262 fn field_name(&self, index: usize) -> &str {
263 self.fields[index].0
264 }
265
266 fn field_value(&self, index: usize) -> &str {
267 &self.fields[index].1
268 }
269
270 fn set_field_value(&mut self, index: usize, value: String) {
271 self.fields[index].1 = value;
272 }
273 }
274
275 #[test]
276 fn dispatch_extends_visual_selection_without_reanchoring() {
277 let mut editor = EditorCore::new(TestProvider::new(&["alpha", "beta"]));
278
279 assert!(
280 editor
281 .execute(CanvasAction::EnterHighlightMode)
282 .is_success()
283 );
284 assert!(editor.execute(CanvasAction::MoveRight).is_success());
285 assert_eq!(editor.current_field(), 0);
286 assert_eq!(editor.cursor_position(), 1);
287 assert!(matches!(
288 editor.selection_state(),
289 SelectionState::Characterwise { anchor: (0, 0) }
290 ));
291
292 assert!(editor.execute(CanvasAction::MoveDown).is_success());
293 assert_eq!(editor.current_field(), 1);
294 assert!(matches!(
295 editor.selection_state(),
296 SelectionState::Characterwise { anchor: (0, 0) }
297 ));
298 }
299}