#[cfg(not(feature = "std"))]
extern crate alloc;
use crate::core::{Position, Range, Result};
#[cfg(feature = "std")]
use std::collections::HashMap;
#[cfg(not(feature = "std"))]
use alloc::collections::BTreeMap as HashMap;
#[cfg(not(feature = "std"))]
use alloc::{
boxed::Box,
format,
string::{String, ToString},
vec::Vec,
};
#[cfg(feature = "multi-thread")]
use std::sync::{Arc, RwLock};
#[cfg(not(feature = "multi-thread"))]
use core::cell::RefCell;
#[cfg(all(not(feature = "multi-thread"), not(feature = "std")))]
use alloc::rc::Rc;
#[cfg(all(not(feature = "multi-thread"), feature = "std"))]
use std::rc::Rc;
#[cfg(feature = "async")]
use futures::channel::mpsc;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DocumentEvent {
TextInserted {
position: Position,
text: String,
length: usize,
},
TextDeleted {
range: Range,
deleted_text: String,
},
TextReplaced {
range: Range,
old_text: String,
new_text: String,
},
SelectionChanged {
old_selection: Option<Range>,
new_selection: Option<Range>,
},
CursorMoved {
old_position: Position,
new_position: Position,
},
DocumentSaved {
file_path: String,
save_as: bool,
},
DocumentLoaded {
file_path: String,
size: usize,
},
UndoPerformed {
action_description: String,
changes_count: usize,
},
RedoPerformed {
action_description: String,
changes_count: usize,
},
ValidationCompleted {
issues_count: usize,
error_count: usize,
warning_count: usize,
validation_time_ms: u64,
},
SearchCompleted {
pattern: String,
matches_count: usize,
hit_limit: bool,
search_time_us: u64,
},
ParsingCompleted {
success: bool,
sections_count: usize,
parse_time_ms: u64,
error_message: Option<String>,
},
ExtensionChanged {
extension_name: String,
loaded: bool,
},
ConfigChanged {
key: String,
old_value: Option<String>,
new_value: String,
},
CustomEvent {
event_type: String,
data: HashMap<String, String>,
},
}
impl DocumentEvent {
pub fn description(&self) -> String {
match self {
Self::TextInserted { length, .. } => format!("Inserted {length} bytes of text"),
Self::TextDeleted { range, .. } => format!("Deleted text from {range}"),
Self::TextReplaced { range, .. } => format!("Replaced text in {range}"),
Self::SelectionChanged { .. } => "Selection changed".to_string(),
Self::CursorMoved { .. } => "Cursor moved".to_string(),
Self::DocumentSaved { file_path, save_as } => {
if *save_as {
format!("Saved document as '{file_path}'")
} else {
format!("Saved document to '{file_path}'")
}
}
Self::DocumentLoaded { file_path, size } => {
format!("Loaded document '{file_path}' ({size} bytes)")
}
Self::UndoPerformed {
action_description,
changes_count,
} => {
format!("Undid '{action_description}' ({changes_count} changes)")
}
Self::RedoPerformed {
action_description,
changes_count,
} => {
format!("Redid '{action_description}' ({changes_count} changes)")
}
Self::ValidationCompleted {
issues_count,
validation_time_ms,
..
} => {
format!(
"Validation completed: {issues_count} issues found in {validation_time_ms}ms"
)
}
Self::SearchCompleted {
pattern,
matches_count,
search_time_us,
..
} => {
format!(
"Search for '{pattern}' found {matches_count} matches in {search_time_us}μs"
)
}
Self::ParsingCompleted {
success,
sections_count,
parse_time_ms,
..
} => {
if *success {
format!("Parsed {sections_count} sections in {parse_time_ms}ms")
} else {
format!("Parsing failed after {parse_time_ms}ms")
}
}
Self::ExtensionChanged {
extension_name,
loaded,
} => {
if *loaded {
format!("Loaded extension '{extension_name}'")
} else {
format!("Unloaded extension '{extension_name}'")
}
}
Self::ConfigChanged { key, .. } => format!("Configuration '{key}' changed"),
Self::CustomEvent { event_type, .. } => format!("Custom event: {event_type}"),
}
}
pub fn is_modification(&self) -> bool {
matches!(
self,
Self::TextInserted { .. }
| Self::TextDeleted { .. }
| Self::TextReplaced { .. }
| Self::UndoPerformed { .. }
| Self::RedoPerformed { .. }
)
}
pub fn affects_text(&self) -> bool {
matches!(
self,
Self::TextInserted { .. }
| Self::TextDeleted { .. }
| Self::TextReplaced { .. }
| Self::UndoPerformed { .. }
| Self::RedoPerformed { .. }
| Self::DocumentLoaded { .. }
)
}
pub fn affected_range(&self) -> Option<Range> {
match self {
Self::TextInserted {
position, length, ..
} => Some(Range::new(*position, position.advance(*length))),
Self::TextDeleted { range, .. } | Self::TextReplaced { range, .. } => Some(*range),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct EventFilter {
include_types: Vec<String>,
exclude_types: Vec<String>,
include_modifications: Option<bool>,
include_cursor_events: Option<bool>,
}
impl EventFilter {
pub fn new() -> Self {
Self {
include_types: Vec::new(),
exclude_types: Vec::new(),
include_modifications: None,
include_cursor_events: None,
}
}
pub fn include_types(mut self, types: Vec<String>) -> Self {
self.include_types = types;
self
}
pub fn exclude_types(mut self, types: Vec<String>) -> Self {
self.exclude_types = types;
self
}
pub fn include_modifications(mut self, include: bool) -> Self {
self.include_modifications = Some(include);
self
}
pub fn include_cursor_events(mut self, include: bool) -> Self {
self.include_cursor_events = Some(include);
self
}
pub fn matches(&self, event: &DocumentEvent) -> bool {
let event_type = event.event_type_name();
if self.exclude_types.contains(&event_type) {
return false;
}
if !self.include_types.is_empty() && !self.include_types.contains(&event_type) {
return false;
}
if let Some(include_mods) = self.include_modifications {
if event.is_modification() != include_mods {
return false;
}
}
if let Some(include_cursor) = self.include_cursor_events {
let is_cursor_event = matches!(
event,
DocumentEvent::CursorMoved { .. } | DocumentEvent::SelectionChanged { .. }
);
if is_cursor_event != include_cursor {
return false;
}
}
true
}
}
impl Default for EventFilter {
fn default() -> Self {
Self::new()
}
}
impl DocumentEvent {
fn event_type_name(&self) -> String {
match self {
Self::TextInserted { .. } => "TextInserted".to_string(),
Self::TextDeleted { .. } => "TextDeleted".to_string(),
Self::TextReplaced { .. } => "TextReplaced".to_string(),
Self::SelectionChanged { .. } => "SelectionChanged".to_string(),
Self::CursorMoved { .. } => "CursorMoved".to_string(),
Self::DocumentSaved { .. } => "DocumentSaved".to_string(),
Self::DocumentLoaded { .. } => "DocumentLoaded".to_string(),
Self::UndoPerformed { .. } => "UndoPerformed".to_string(),
Self::RedoPerformed { .. } => "RedoPerformed".to_string(),
Self::ValidationCompleted { .. } => "ValidationCompleted".to_string(),
Self::SearchCompleted { .. } => "SearchCompleted".to_string(),
Self::ParsingCompleted { .. } => "ParsingCompleted".to_string(),
Self::ExtensionChanged { .. } => "ExtensionChanged".to_string(),
Self::ConfigChanged { .. } => "ConfigChanged".to_string(),
Self::CustomEvent { event_type, .. } => event_type.clone(),
}
}
}
pub trait EventHandler: Send + Sync {
fn handle_event(&mut self, event: &DocumentEvent) -> Result<()>;
fn event_filter(&self) -> EventFilter {
EventFilter::new()
}
fn priority(&self) -> i32 {
0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EventStats {
pub events_dispatched: usize,
pub handlers_count: usize,
pub events_filtered: usize,
pub async_events_queued: usize,
pub avg_processing_time_us: u64,
}
#[derive(Debug, Clone)]
pub struct EventChannelConfig {
pub max_handlers: usize,
pub max_async_queue_size: usize,
pub enable_batching: bool,
pub max_batch_size: usize,
pub enable_logging: bool,
}
impl Default for EventChannelConfig {
fn default() -> Self {
Self {
max_handlers: 100,
max_async_queue_size: 1000,
enable_batching: false,
max_batch_size: 10,
enable_logging: false,
}
}
}
#[derive(Debug)]
pub struct EventChannel {
config: EventChannelConfig,
#[cfg(feature = "multi-thread")]
handlers: Arc<RwLock<Vec<HandlerInfo>>>,
#[cfg(not(feature = "multi-thread"))]
handlers: Rc<RefCell<Vec<HandlerInfo>>>,
stats: EventStats,
#[cfg(feature = "async")]
async_sender: Option<mpsc::UnboundedSender<DocumentEvent>>,
next_handler_id: usize,
}
struct HandlerInfo {
id: usize,
handler: Box<dyn EventHandler>,
filter: EventFilter,
priority: i32,
events_processed: usize,
}
impl core::fmt::Debug for HandlerInfo {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("HandlerInfo")
.field("id", &self.id)
.field("filter", &self.filter)
.field("priority", &self.priority)
.field("events_processed", &self.events_processed)
.field("handler", &"<EventHandler>")
.finish()
}
}
impl EventChannel {
pub fn new() -> Self {
Self::with_config(EventChannelConfig::default())
}
pub fn with_config(config: EventChannelConfig) -> Self {
Self {
config,
#[cfg(feature = "multi-thread")]
handlers: Arc::new(RwLock::new(Vec::new())),
#[cfg(not(feature = "multi-thread"))]
handlers: Rc::new(RefCell::new(Vec::new())),
stats: EventStats {
events_dispatched: 0,
handlers_count: 0,
events_filtered: 0,
async_events_queued: 0,
avg_processing_time_us: 0,
},
#[cfg(feature = "async")]
async_sender: None,
next_handler_id: 0,
}
}
pub fn register_handler(&mut self, handler: Box<dyn EventHandler>) -> Result<usize> {
let handler_id = self.next_handler_id;
self.next_handler_id += 1;
let filter = handler.event_filter();
let priority = handler.priority();
let handler_info = HandlerInfo {
id: handler_id,
handler,
filter,
priority,
events_processed: 0,
};
#[cfg(feature = "multi-thread")]
{
let mut handlers =
self.handlers
.write()
.map_err(|_| crate::core::EditorError::ThreadSafetyError {
message: "Failed to acquire write lock for handlers".to_string(),
})?;
if handlers.len() >= self.config.max_handlers {
return Err(crate::core::EditorError::CommandFailed {
message: format!("Handler limit reached: {}", self.config.max_handlers),
});
}
handlers.push(handler_info);
handlers.sort_by(|a, b| b.priority.cmp(&a.priority));
}
#[cfg(not(feature = "multi-thread"))]
{
let mut handlers = self.handlers.borrow_mut();
if handlers.len() >= self.config.max_handlers {
return Err(crate::core::EditorError::CommandFailed {
message: format!("Handler limit reached: {}", self.config.max_handlers),
});
}
handlers.push(handler_info);
handlers.sort_by(|a, b| b.priority.cmp(&a.priority));
}
self.stats.handlers_count += 1;
Ok(handler_id)
}
pub fn unregister_handler(&mut self, handler_id: usize) -> Result<bool> {
#[cfg(feature = "multi-thread")]
{
let mut handlers =
self.handlers
.write()
.map_err(|_| crate::core::EditorError::ThreadSafetyError {
message: "Failed to acquire write lock for handlers".to_string(),
})?;
if let Some(pos) = handlers.iter().position(|h| h.id == handler_id) {
handlers.remove(pos);
self.stats.handlers_count -= 1;
Ok(true)
} else {
Ok(false)
}
}
#[cfg(not(feature = "multi-thread"))]
{
let mut handlers = self.handlers.borrow_mut();
if let Some(pos) = handlers.iter().position(|h| h.id == handler_id) {
handlers.remove(pos);
self.stats.handlers_count -= 1;
Ok(true)
} else {
Ok(false)
}
}
}
pub fn dispatch(&mut self, event: DocumentEvent) -> Result<()> {
#[cfg(feature = "std")]
let start_time = std::time::Instant::now();
self.stats.events_dispatched += 1;
let mut filtered_count = 0;
#[allow(unused_variables, unused_assignments)]
let mut processed_count = 0;
#[cfg(feature = "multi-thread")]
{
let mut handlers =
self.handlers
.write()
.map_err(|_| crate::core::EditorError::ThreadSafetyError {
message: "Failed to acquire write lock for handlers".to_string(),
})?;
for handler_info in handlers.iter_mut() {
if handler_info.filter.matches(&event) {
handler_info.handler.handle_event(&event)?;
handler_info.events_processed += 1;
processed_count += 1;
} else {
filtered_count += 1;
}
}
}
#[cfg(not(feature = "multi-thread"))]
{
let mut handlers = self.handlers.borrow_mut();
for handler_info in handlers.iter_mut() {
if handler_info.filter.matches(&event) {
handler_info.handler.handle_event(&event)?;
handler_info.events_processed += 1;
processed_count += 1;
} else {
filtered_count += 1;
}
}
}
self.stats.events_filtered += filtered_count;
#[cfg(feature = "std")]
{
let processing_time = start_time.elapsed().as_micros() as u64;
if self.stats.events_dispatched == 1 {
self.stats.avg_processing_time_us = processing_time;
} else {
self.stats.avg_processing_time_us =
(self.stats.avg_processing_time_us + processing_time) / 2;
}
}
if self.config.enable_logging {
#[cfg(feature = "std")]
eprintln!(
"Event dispatched: {} -> {} handlers (filtered: {})",
event.description(),
processed_count,
filtered_count
);
}
Ok(())
}
pub fn dispatch_batch(&mut self, events: Vec<DocumentEvent>) -> Result<()> {
if self.config.enable_batching && events.len() <= self.config.max_batch_size {
for event in events {
self.dispatch(event)?;
}
} else {
for event in events {
self.dispatch(event)?;
}
}
Ok(())
}
pub fn stats(&self) -> &EventStats {
&self.stats
}
pub fn clear_handlers(&mut self) -> Result<()> {
#[cfg(feature = "multi-thread")]
{
let mut handlers =
self.handlers
.write()
.map_err(|_| crate::core::EditorError::ThreadSafetyError {
message: "Failed to acquire write lock for handlers".to_string(),
})?;
handlers.clear();
}
#[cfg(not(feature = "multi-thread"))]
{
let mut handlers = self.handlers.borrow_mut();
handlers.clear();
}
self.stats.handlers_count = 0;
Ok(())
}
#[cfg(feature = "async")]
pub fn setup_async(&mut self) -> mpsc::UnboundedReceiver<DocumentEvent> {
let (sender, receiver) = mpsc::unbounded();
self.async_sender = Some(sender);
receiver
}
#[cfg(feature = "async")]
pub fn dispatch_async(&mut self, event: DocumentEvent) -> Result<()> {
if let Some(ref sender) = self.async_sender {
sender
.unbounded_send(event)
.map_err(|_| crate::core::EditorError::CommandFailed {
message: "Failed to send async event".to_string(),
})?;
self.stats.async_events_queued += 1;
} else {
return Err(crate::core::EditorError::FeatureNotEnabled {
feature: "async event processing".to_string(),
required_feature: "async".to_string(),
});
}
Ok(())
}
}
impl Default for EventChannel {
fn default() -> Self {
Self::new()
}
}
impl DocumentEvent {
pub fn text_inserted(position: Position, text: String) -> Self {
let length = text.len();
Self::TextInserted {
position,
text,
length,
}
}
pub fn text_deleted(range: Range, deleted_text: String) -> Self {
Self::TextDeleted {
range,
deleted_text,
}
}
pub fn text_replaced(range: Range, old_text: String, new_text: String) -> Self {
Self::TextReplaced {
range,
old_text,
new_text,
}
}
pub fn cursor_moved(old_position: Position, new_position: Position) -> Self {
Self::CursorMoved {
old_position,
new_position,
}
}
pub fn document_saved(file_path: String, save_as: bool) -> Self {
Self::DocumentSaved { file_path, save_as }
}
pub fn validation_completed(
issues_count: usize,
error_count: usize,
warning_count: usize,
validation_time_ms: u64,
) -> Self {
Self::ValidationCompleted {
issues_count,
error_count,
warning_count,
validation_time_ms,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(feature = "std"))]
use alloc::{string::ToString, vec};
#[test]
fn document_event_creation() {
let event = DocumentEvent::text_inserted(Position::new(0), "Hello".to_string());
match event {
DocumentEvent::TextInserted {
position,
text,
length,
} => {
assert_eq!(position.offset, 0);
assert_eq!(text, "Hello");
assert_eq!(length, 5);
}
_ => panic!("Expected TextInserted event"),
}
}
#[test]
fn document_event_description() {
let event = DocumentEvent::text_inserted(Position::new(0), "Hello".to_string());
assert_eq!(event.description(), "Inserted 5 bytes of text");
let event = DocumentEvent::cursor_moved(Position::new(0), Position::new(5));
assert_eq!(event.description(), "Cursor moved");
}
#[test]
fn document_event_modification_check() {
let insert_event = DocumentEvent::text_inserted(Position::new(0), "Hello".to_string());
assert!(insert_event.is_modification());
let cursor_event = DocumentEvent::cursor_moved(Position::new(0), Position::new(5));
assert!(!cursor_event.is_modification());
}
#[test]
fn document_event_affects_text() {
let insert_event = DocumentEvent::text_inserted(Position::new(0), "Hello".to_string());
assert!(insert_event.affects_text());
let config_event = DocumentEvent::ConfigChanged {
key: "font_size".to_string(),
old_value: Some("12".to_string()),
new_value: "14".to_string(),
};
assert!(!config_event.affects_text());
}
#[test]
fn document_event_affected_range() {
let insert_event = DocumentEvent::text_inserted(Position::new(10), "Hello".to_string());
let range = insert_event.affected_range().unwrap();
assert_eq!(range.start.offset, 10);
assert_eq!(range.end.offset, 15);
let cursor_event = DocumentEvent::cursor_moved(Position::new(0), Position::new(5));
assert!(cursor_event.affected_range().is_none());
}
#[test]
fn event_filter_creation() {
let filter = EventFilter::new()
.include_modifications(true)
.exclude_types(vec!["CursorMoved".to_string()]);
let insert_event = DocumentEvent::text_inserted(Position::new(0), "Hello".to_string());
assert!(filter.matches(&insert_event));
let cursor_event = DocumentEvent::cursor_moved(Position::new(0), Position::new(5));
assert!(!filter.matches(&cursor_event));
}
#[test]
fn event_filter_include_types() {
let filter = EventFilter::new()
.include_types(vec!["TextInserted".to_string(), "TextDeleted".to_string()]);
let insert_event = DocumentEvent::text_inserted(Position::new(0), "Hello".to_string());
assert!(filter.matches(&insert_event));
let cursor_event = DocumentEvent::cursor_moved(Position::new(0), Position::new(5));
assert!(!filter.matches(&cursor_event));
}
#[test]
fn event_channel_creation() {
let channel = EventChannel::new();
assert_eq!(channel.stats().handlers_count, 0);
assert_eq!(channel.stats().events_dispatched, 0);
}
#[test]
fn event_channel_config() {
let config = EventChannelConfig {
max_handlers: 50,
enable_logging: true,
..Default::default()
};
let channel = EventChannel::with_config(config);
assert_eq!(channel.config.max_handlers, 50);
assert!(channel.config.enable_logging);
}
struct TestHandler {
events_received: Vec<String>,
filter: EventFilter,
priority: i32,
}
impl TestHandler {
fn new() -> Self {
Self {
events_received: Vec::new(),
filter: EventFilter::new(),
priority: 0,
}
}
fn with_filter(filter: EventFilter) -> Self {
Self {
events_received: Vec::new(),
filter,
priority: 0,
}
}
fn with_priority(priority: i32) -> Self {
Self {
events_received: Vec::new(),
filter: EventFilter::new(),
priority,
}
}
}
impl EventHandler for TestHandler {
fn handle_event(&mut self, event: &DocumentEvent) -> Result<()> {
self.events_received.push(event.description());
Ok(())
}
fn event_filter(&self) -> EventFilter {
self.filter.clone()
}
fn priority(&self) -> i32 {
self.priority
}
}
#[test]
fn event_channel_handler_registration() {
let mut channel = EventChannel::new();
let handler = Box::new(TestHandler::new());
let handler_id = channel.register_handler(handler).unwrap();
assert_eq!(channel.stats().handlers_count, 1);
let removed = channel.unregister_handler(handler_id).unwrap();
assert!(removed);
assert_eq!(channel.stats().handlers_count, 0);
}
#[test]
fn event_channel_dispatch() {
let mut channel = EventChannel::new();
let handler = Box::new(TestHandler::new());
channel.register_handler(handler).unwrap();
let event = DocumentEvent::text_inserted(Position::new(0), "Hello".to_string());
channel.dispatch(event).unwrap();
assert_eq!(channel.stats().events_dispatched, 1);
}
#[test]
fn event_channel_filtering() {
let mut channel = EventChannel::new();
let filter = EventFilter::new().exclude_types(vec!["CursorMoved".to_string()]);
let handler = Box::new(TestHandler::with_filter(filter));
channel.register_handler(handler).unwrap();
let insert_event = DocumentEvent::text_inserted(Position::new(0), "Hello".to_string());
channel.dispatch(insert_event).unwrap();
let cursor_event = DocumentEvent::cursor_moved(Position::new(0), Position::new(5));
channel.dispatch(cursor_event).unwrap();
assert_eq!(channel.stats().events_dispatched, 2);
assert_eq!(channel.stats().events_filtered, 1);
}
#[test]
fn event_channel_priority_ordering() {
let mut channel = EventChannel::new();
let low_priority_handler = Box::new(TestHandler::with_priority(1));
let high_priority_handler = Box::new(TestHandler::with_priority(10));
channel.register_handler(low_priority_handler).unwrap();
channel.register_handler(high_priority_handler).unwrap();
assert_eq!(channel.stats().handlers_count, 2);
}
#[test]
fn event_channel_batch_dispatch() {
let mut channel = EventChannel::new();
let handler = Box::new(TestHandler::new());
channel.register_handler(handler).unwrap();
let events = vec![
DocumentEvent::text_inserted(Position::new(0), "Hello".to_string()),
DocumentEvent::text_inserted(Position::new(5), " World".to_string()),
];
channel.dispatch_batch(events).unwrap();
assert_eq!(channel.stats().events_dispatched, 2);
}
#[test]
fn event_channel_clear_handlers() {
let mut channel = EventChannel::new();
let handler = Box::new(TestHandler::new());
channel.register_handler(handler).unwrap();
assert_eq!(channel.stats().handlers_count, 1);
channel.clear_handlers().unwrap();
assert_eq!(channel.stats().handlers_count, 0);
}
}