1use crate::model::event::Event;
7use crate::services::plugins::hooks::{HookArgs, HookRegistry};
8use fresh_core::BufferId;
9use std::sync::RwLock;
10
11pub trait EventHooks {
13 fn before_hook(&self, buffer_id: BufferId) -> Option<HookArgs>;
15
16 fn after_hook(&self, buffer_id: BufferId) -> Option<HookArgs>;
18}
19
20impl EventHooks for Event {
21 fn before_hook(&self, buffer_id: BufferId) -> Option<HookArgs> {
22 match self {
23 Self::Insert {
24 position,
25 text,
26 cursor_id: _,
27 } => Some(HookArgs::BeforeInsert {
28 buffer_id,
29 position: *position,
30 text: text.clone(),
31 }),
32 Self::Delete { range, .. } => Some(HookArgs::BeforeDelete {
33 buffer_id,
34 range: range.clone(),
35 }),
36 _ => None, }
38 }
39
40 fn after_hook(&self, buffer_id: BufferId) -> Option<HookArgs> {
41 match self {
42 Self::Insert {
43 position,
44 text,
45 cursor_id: _,
46 } => Some(HookArgs::AfterInsert {
47 buffer_id,
48 position: *position,
49 text: text.clone(),
50 affected_start: *position,
51 affected_end: *position + text.len(),
52 start_line: 0,
54 end_line: 0,
55 lines_added: 0,
56 }),
57 Self::Delete {
58 range,
59 deleted_text,
60 ..
61 } => Some(HookArgs::AfterDelete {
62 buffer_id,
63 range: range.clone(),
64 deleted_text: deleted_text.clone(),
65 affected_start: range.start,
66 deleted_len: deleted_text.len(),
67 start_line: 0,
69 end_line: 0,
70 lines_removed: 0,
71 }),
72 Self::MoveCursor {
73 cursor_id,
74 old_position,
75 new_position,
76 ..
77 } => Some(HookArgs::CursorMoved {
78 buffer_id,
79 cursor_id: *cursor_id,
80 old_position: *old_position,
81 new_position: *new_position,
82 line: 0,
84 text_properties: Vec::new(),
85 }),
86 _ => None,
87 }
88 }
89}
90
91pub fn apply_event_with_hooks(
93 state: &mut crate::state::EditorState,
94 cursors: &mut crate::model::cursor::Cursors,
95 event: &Event,
96 buffer_id: BufferId,
97 hook_registry: &RwLock<HookRegistry>,
98) -> bool {
99 if let Some(before_args) = event.before_hook(buffer_id) {
101 let registry = hook_registry.read().unwrap();
102 let hook_name = match &before_args {
103 HookArgs::BeforeInsert { .. } => "before_insert",
104 HookArgs::BeforeDelete { .. } => "before_delete",
105 _ => "",
106 };
107
108 if !hook_name.is_empty() {
109 let should_continue = registry.run_hooks(hook_name, &before_args);
110 if !should_continue {
111 return false;
113 }
114 }
115 }
116
117 state.apply(cursors, event);
119
120 if let Some(mut after_args) = event.after_hook(buffer_id) {
122 if let HookArgs::CursorMoved {
124 new_position,
125 ref mut line,
126 ref mut text_properties,
127 ..
128 } = after_args
129 {
130 *line = state.buffer.get_line_number(new_position) + 1;
133 *text_properties = state
135 .text_properties
136 .get_at(new_position)
137 .into_iter()
138 .map(|tp| tp.properties.clone())
139 .collect();
140 }
141
142 let registry = hook_registry.read().unwrap();
143 let hook_name = match &after_args {
144 HookArgs::AfterInsert { .. } => "after_insert",
145 HookArgs::AfterDelete { .. } => "after_delete",
146 HookArgs::CursorMoved { .. } => "cursor_moved",
147 _ => "",
148 };
149
150 if !hook_name.is_empty() {
151 registry.run_hooks(hook_name, &after_args);
152 }
153 }
154
155 true }
157
158#[cfg(test)]
159mod tests {
160 use crate::model::filesystem::StdFileSystem;
161 use std::sync::Arc;
162
163 fn test_fs() -> Arc<dyn crate::model::filesystem::FileSystem + Send + Sync> {
164 Arc::new(StdFileSystem)
165 }
166 use super::*;
167 use crate::services::plugins::hooks::HookRegistry;
168 use fresh_core::CursorId;
169
170 #[test]
171 fn test_insert_event_has_hooks() {
172 let event = Event::Insert {
173 position: 0,
174 text: "test".to_string(),
175 cursor_id: CursorId(0),
176 };
177
178 let buffer_id = BufferId(1);
179
180 assert!(event.before_hook(buffer_id).is_some());
182 assert!(event.after_hook(buffer_id).is_some());
183 }
184
185 #[test]
186 fn test_delete_event_has_hooks() {
187 let event = Event::Delete {
188 range: 0..5,
189 deleted_text: "test".to_string(),
190 cursor_id: CursorId(0),
191 };
192
193 let buffer_id = BufferId(1);
194
195 assert!(event.before_hook(buffer_id).is_some());
196 assert!(event.after_hook(buffer_id).is_some());
197 }
198
199 #[test]
200 fn test_overlay_event_no_hooks() {
201 let event = Event::AddOverlay {
202 range: 0..5,
203 face: crate::model::event::OverlayFace::Background { color: (255, 0, 0) },
204 priority: 10,
205 message: None,
206 extend_to_line_end: false,
207 namespace: None,
208 url: None,
209 };
210
211 let buffer_id = BufferId(1);
212
213 assert!(event.before_hook(buffer_id).is_none());
215 assert!(event.after_hook(buffer_id).is_none());
216 }
217
218 #[test]
219 fn test_hooks_can_cancel() {
220 use crate::state::EditorState;
221 use std::sync::RwLock;
222
223 let mut state = EditorState::new(
224 80,
225 24,
226 crate::config::LARGE_FILE_THRESHOLD_BYTES as usize,
227 test_fs(),
228 );
229 let hook_registry = RwLock::new(HookRegistry::new());
230
231 {
233 let mut registry = hook_registry.write().unwrap();
234 registry.add_hook("before_insert", Box::new(|_| false)); }
236
237 let event = Event::Insert {
238 position: 0,
239 text: "test".to_string(),
240 cursor_id: CursorId(0),
241 };
242
243 let buffer_id = BufferId(0);
244 let mut cursors = crate::model::cursor::Cursors::new();
245 let was_applied =
246 apply_event_with_hooks(&mut state, &mut cursors, &event, buffer_id, &hook_registry);
247
248 assert!(!was_applied);
250 assert_eq!(state.buffer.len(), 0); }
252
253 #[test]
254 fn test_hooks_allow_event() {
255 use crate::state::EditorState;
256 use std::sync::RwLock;
257
258 let mut state = EditorState::new(
259 80,
260 24,
261 crate::config::LARGE_FILE_THRESHOLD_BYTES as usize,
262 test_fs(),
263 );
264 let hook_registry = RwLock::new(HookRegistry::new());
265
266 {
268 let mut registry = hook_registry.write().unwrap();
269 registry.add_hook("before_insert", Box::new(|_| true)); }
271
272 let event = Event::Insert {
273 position: 0,
274 text: "test".to_string(),
275 cursor_id: CursorId(0),
276 };
277
278 let buffer_id = BufferId(0);
279 let mut cursors = crate::model::cursor::Cursors::new();
280 let was_applied =
281 apply_event_with_hooks(&mut state, &mut cursors, &event, buffer_id, &hook_registry);
282
283 assert!(was_applied);
285 assert_eq!(state.buffer.to_string().unwrap(), "test");
286 }
287}