use std::collections::HashMap;
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, Weak};
use parking_lot::Mutex;
pub(crate) type QueuedEvents = Vec<(
crate::events::DocumentEvent,
Vec<Arc<dyn Fn(crate::events::DocumentEvent) + Send + Sync>>,
)>;
use anyhow::Result;
use frontend::AppContext;
use frontend::EventHubClient;
use frontend::common::types::EntityId;
use crate::DocumentEvent;
use crate::highlight::HighlightData;
pub(crate) struct CursorData {
pub position: usize,
pub anchor: usize,
pub cell_selection_override: Option<crate::flow::CellRange>,
}
pub(crate) struct CallbackEntry {
pub alive: Weak<AtomicBool>,
pub callback: Arc<dyn Fn(DocumentEvent) + Send + Sync>,
}
pub(crate) struct TextDocumentInner {
pub ctx: AppContext,
pub event_client: EventHubClient,
pub stack_id: u64,
#[allow(dead_code)] pub root_id: EntityId,
pub document_id: EntityId,
pub modified: bool,
pub cursors: Vec<Weak<Mutex<CursorData>>>,
pub pending_events: Vec<DocumentEvent>,
pub callbacks: Vec<CallbackEntry>,
pub callback_cursor: usize,
pub poll_cursor: usize,
pub resource_cache: HashMap<String, u64>,
pub plain_text_cache: Option<String>,
pub last_block_count: usize,
pub last_child_order: Vec<i64>,
pub highlight: Option<HighlightData>,
}
impl TextDocumentInner {
pub fn prune_dead_cursors(&mut self) {
self.cursors.retain(|w| w.strong_count() > 0);
}
pub fn adjust_cursors(&mut self, edit_pos: usize, removed: usize, added: usize) {
self.prune_dead_cursors();
for weak in &self.cursors {
if let Some(cursor) = weak.upgrade() {
let mut data = cursor.lock();
data.position = adjust_offset(data.position, edit_pos, removed, added);
data.anchor = adjust_offset(data.anchor, edit_pos, removed, added);
data.cell_selection_override = None;
}
}
}
pub fn register_cursor(&mut self, position: usize) -> Arc<Mutex<CursorData>> {
self.prune_dead_cursors();
let data = Arc::new(Mutex::new(CursorData {
position,
anchor: position,
cell_selection_override: None,
}));
self.cursors.push(Arc::downgrade(&data));
data
}
pub fn queue_event(&mut self, event: DocumentEvent) {
self.pending_events.push(event);
}
pub fn drain_poll_events(&mut self) -> Vec<DocumentEvent> {
if self.poll_cursor >= self.pending_events.len() {
self.trim_delivered_events();
return Vec::new();
}
let events = self.pending_events[self.poll_cursor..].to_vec();
self.poll_cursor = self.pending_events.len();
self.trim_delivered_events();
events
}
pub fn take_queued_events(&mut self) -> QueuedEvents {
if self.callback_cursor >= self.pending_events.len() {
return Vec::new();
}
self.callbacks
.retain(|entry| entry.alive.strong_count() > 0);
let live_callbacks: Vec<Arc<dyn Fn(DocumentEvent) + Send + Sync>> = self
.callbacks
.iter()
.filter_map(|entry| {
let alive = entry.alive.upgrade()?;
if alive.load(std::sync::atomic::Ordering::Relaxed) {
Some(Arc::clone(&entry.callback))
} else {
None
}
})
.collect();
if live_callbacks.is_empty() {
self.callback_cursor = self.pending_events.len();
return Vec::new();
}
let new_events: Vec<DocumentEvent> = self.pending_events[self.callback_cursor..].to_vec();
self.callback_cursor = self.pending_events.len();
new_events
.into_iter()
.map(|e| (e, live_callbacks.clone()))
.collect()
}
fn trim_delivered_events(&mut self) {
let min_cursor = self.callback_cursor.min(self.poll_cursor);
if min_cursor > 0 {
self.pending_events.drain(..min_cursor);
self.callback_cursor -= min_cursor;
self.poll_cursor -= min_cursor;
}
}
pub fn invalidate_text_cache(&mut self) {
self.plain_text_cache = None;
}
pub fn check_block_count_changed(&mut self) {
let doc = frontend::commands::document_commands::get_document(&self.ctx, &self.document_id);
if let Ok(Some(doc)) = doc {
let new_count = doc.block_count as usize;
if new_count != self.last_block_count {
self.last_block_count = new_count;
self.queue_event(crate::DocumentEvent::BlockCountChanged(new_count));
}
}
}
pub fn check_flow_changed(&mut self) {
let current = self.main_frame_child_order();
if current == self.last_child_order {
return;
}
let old = &self.last_child_order;
let new = ¤t;
let prefix_len = old
.iter()
.zip(new.iter())
.take_while(|(a, b)| a == b)
.count();
let old_remaining = old.len() - prefix_len;
let new_remaining = new.len() - prefix_len;
let suffix_len = old[prefix_len..]
.iter()
.rev()
.zip(new[prefix_len..].iter().rev())
.take_while(|(a, b)| a == b)
.count();
let removed_count = old_remaining - suffix_len;
let inserted_count = new_remaining - suffix_len;
if removed_count > 0 {
self.queue_event(crate::DocumentEvent::FlowElementsRemoved {
flow_index: prefix_len,
count: removed_count,
});
}
if inserted_count > 0 {
self.queue_event(crate::DocumentEvent::FlowElementsInserted {
flow_index: prefix_len,
count: inserted_count,
});
}
self.last_child_order = current;
}
pub fn reset_cached_child_order(&mut self) {
self.last_child_order = self.main_frame_child_order();
}
fn main_frame_child_order(&self) -> Vec<i64> {
let frames = frontend::commands::document_commands::get_document_relationship(
&self.ctx,
&self.document_id,
&frontend::document::dtos::DocumentRelationshipField::Frames,
)
.unwrap_or_default();
let main_frame_id = match frames.first() {
Some(&id) => id,
None => return Vec::new(),
};
frontend::commands::frame_commands::get_frame(&self.ctx, &(main_frame_id as u64))
.ok()
.flatten()
.map(|f| f.child_order)
.unwrap_or_default()
}
pub fn plain_text(&mut self) -> Result<&str> {
if self.plain_text_cache.is_none() {
let dto = frontend::commands::document_io_commands::export_plain_text(&self.ctx)?;
self.plain_text_cache = Some(dto.plain_text);
}
Ok(self.plain_text_cache.as_deref().unwrap())
}
pub fn initialize(ctx: AppContext) -> Result<Self> {
use frontend::block::dtos::CreateBlockDto;
use frontend::commands::{
block_commands, document_commands, frame_commands, inline_element_commands,
root_commands, undo_redo_commands,
};
use frontend::document::dtos::CreateDocumentDto;
use frontend::frame::dtos::CreateFrameDto;
use frontend::inline_element::dtos::CreateInlineElementDto;
use frontend::root::dtos::CreateRootDto;
let event_client = EventHubClient::new(&ctx.event_hub);
event_client.start(ctx.quit_signal.clone());
let stack_id = undo_redo_commands::create_new_stack(&ctx);
let root = root_commands::create_orphan_root(&ctx, &CreateRootDto::default())?;
let doc = document_commands::create_document(
&ctx,
Some(stack_id),
&CreateDocumentDto::default(),
root.id,
-1,
)?;
let frame = frame_commands::create_frame(
&ctx,
Some(stack_id),
&CreateFrameDto::default(),
doc.id,
-1,
)?;
let block = block_commands::create_block(
&ctx,
Some(stack_id),
&CreateBlockDto::default(),
frame.id,
-1,
)?;
let _element = inline_element_commands::create_inline_element(
&ctx,
Some(stack_id),
&CreateInlineElementDto::default(),
block.id,
-1,
)?;
let mut frame_update: frontend::frame::dtos::UpdateFrameDto = frame.into();
frame_update.child_order = vec![block.id as i64];
frame_commands::update_frame(&ctx, Some(stack_id), &frame_update)?;
undo_redo_commands::clear_stack(&ctx, stack_id);
Ok(Self {
ctx,
event_client,
stack_id,
root_id: root.id,
document_id: doc.id,
modified: false,
cursors: Vec::new(),
pending_events: Vec::new(),
callbacks: Vec::new(),
callback_cursor: 0,
poll_cursor: 0,
resource_cache: HashMap::new(),
plain_text_cache: None,
last_block_count: 1, last_child_order: vec![block.id as i64],
highlight: None,
})
}
}
impl Drop for TextDocumentInner {
fn drop(&mut self) {
self.ctx.shutdown();
}
}
pub(crate) fn dispatch_queued_events(queued: QueuedEvents) {
for (event, callbacks) in queued {
for cb in &callbacks {
cb(event.clone());
}
}
}
pub(crate) fn adjust_offset(offset: usize, edit_pos: usize, removed: usize, added: usize) -> usize {
if offset <= edit_pos {
offset
} else if offset <= edit_pos + removed {
edit_pos + added
} else {
offset - removed + added
}
}