use crate::model::event::{BufferId, Event};
use crate::services::plugins::hooks::{HookArgs, HookRegistry};
use std::sync::RwLock;
pub trait EventHooks {
fn before_hook(&self, buffer_id: BufferId) -> Option<HookArgs>;
fn after_hook(&self, buffer_id: BufferId) -> Option<HookArgs>;
}
impl EventHooks for Event {
fn before_hook(&self, buffer_id: BufferId) -> Option<HookArgs> {
match self {
Event::Insert {
position,
text,
cursor_id: _,
} => Some(HookArgs::BeforeInsert {
buffer_id,
position: *position,
text: text.clone(),
}),
Event::Delete { range, .. } => Some(HookArgs::BeforeDelete {
buffer_id,
range: range.clone(),
}),
_ => None, }
}
fn after_hook(&self, buffer_id: BufferId) -> Option<HookArgs> {
match self {
Event::Insert {
position,
text,
cursor_id: _,
} => Some(HookArgs::AfterInsert {
buffer_id,
position: *position,
text: text.clone(),
affected_start: *position,
affected_end: *position + text.len(),
start_line: 0,
end_line: 0,
lines_added: 0,
}),
Event::Delete {
range,
deleted_text,
..
} => Some(HookArgs::AfterDelete {
buffer_id,
range: range.clone(),
deleted_text: deleted_text.clone(),
affected_start: range.start,
deleted_len: deleted_text.len(),
start_line: 0,
end_line: 0,
lines_removed: 0,
}),
Event::MoveCursor {
cursor_id,
old_position,
new_position,
..
} => Some(HookArgs::CursorMoved {
buffer_id,
cursor_id: *cursor_id,
old_position: *old_position,
new_position: *new_position,
line: 0,
}),
_ => None,
}
}
}
pub fn apply_event_with_hooks(
state: &mut crate::state::EditorState,
event: &Event,
buffer_id: BufferId,
hook_registry: &RwLock<HookRegistry>,
) -> bool {
if let Some(before_args) = event.before_hook(buffer_id) {
let registry = hook_registry.read().unwrap();
let hook_name = match &before_args {
HookArgs::BeforeInsert { .. } => "before_insert",
HookArgs::BeforeDelete { .. } => "before_delete",
_ => "",
};
if !hook_name.is_empty() {
let should_continue = registry.run_hooks(hook_name, &before_args);
if !should_continue {
return false;
}
}
}
state.apply(event);
if let Some(mut after_args) = event.after_hook(buffer_id) {
if let HookArgs::CursorMoved {
new_position,
ref mut line,
..
} = after_args
{
*line = state.buffer.get_line_number(new_position) + 1;
}
let registry = hook_registry.read().unwrap();
let hook_name = match &after_args {
HookArgs::AfterInsert { .. } => "after_insert",
HookArgs::AfterDelete { .. } => "after_delete",
HookArgs::CursorMoved { .. } => "cursor_moved",
_ => "",
};
if !hook_name.is_empty() {
registry.run_hooks(hook_name, &after_args);
}
}
true }
#[cfg(test)]
mod tests {
use super::*;
use crate::model::event::CursorId;
use crate::services::plugins::hooks::HookRegistry;
#[test]
fn test_insert_event_has_hooks() {
let event = Event::Insert {
position: 0,
text: "test".to_string(),
cursor_id: CursorId(0),
};
let buffer_id = BufferId(1);
assert!(event.before_hook(buffer_id).is_some());
assert!(event.after_hook(buffer_id).is_some());
}
#[test]
fn test_delete_event_has_hooks() {
let event = Event::Delete {
range: 0..5,
deleted_text: "test".to_string(),
cursor_id: CursorId(0),
};
let buffer_id = BufferId(1);
assert!(event.before_hook(buffer_id).is_some());
assert!(event.after_hook(buffer_id).is_some());
}
#[test]
fn test_overlay_event_no_hooks() {
let event = Event::AddOverlay {
namespace: Some(crate::view::overlay::OverlayNamespace::from_string(
"test".to_string(),
)),
range: 0..5,
face: crate::model::event::OverlayFace::Background { color: (255, 0, 0) },
priority: 10,
message: None,
};
let buffer_id = BufferId(1);
assert!(event.before_hook(buffer_id).is_none());
assert!(event.after_hook(buffer_id).is_none());
}
#[test]
fn test_hooks_can_cancel() {
use crate::state::EditorState;
use std::sync::RwLock;
let mut state =
EditorState::new(80, 24, crate::config::LARGE_FILE_THRESHOLD_BYTES as usize);
let hook_registry = RwLock::new(HookRegistry::new());
{
let mut registry = hook_registry.write().unwrap();
registry.add_hook("before_insert", Box::new(|_| false)); }
let event = Event::Insert {
position: 0,
text: "test".to_string(),
cursor_id: CursorId(0),
};
let buffer_id = BufferId(0);
let was_applied = apply_event_with_hooks(&mut state, &event, buffer_id, &hook_registry);
assert!(!was_applied);
assert_eq!(state.buffer.len(), 0); }
#[test]
fn test_hooks_allow_event() {
use crate::state::EditorState;
use std::sync::RwLock;
let mut state =
EditorState::new(80, 24, crate::config::LARGE_FILE_THRESHOLD_BYTES as usize);
let hook_registry = RwLock::new(HookRegistry::new());
{
let mut registry = hook_registry.write().unwrap();
registry.add_hook("before_insert", Box::new(|_| true)); }
let event = Event::Insert {
position: 0,
text: "test".to_string(),
cursor_id: CursorId(0),
};
let buffer_id = BufferId(0);
let was_applied = apply_event_with_hooks(&mut state, &event, buffer_id, &hook_registry);
assert!(was_applied);
assert_eq!(state.buffer.to_string().unwrap(), "test");
}
}