Skip to main content

text_document/
events.rs

1//! Document event types and subscription handle.
2
3use std::sync::Arc;
4use std::sync::atomic::AtomicBool;
5
6use crate::inner::{CallbackEntry, TextDocumentInner};
7
8/// Events emitted by a [`TextDocument`](crate::TextDocument).
9///
10/// Subscribe via [`TextDocument::on_change`](crate::TextDocument::on_change) (callback-based)
11/// or poll via [`TextDocument::poll_events`](crate::TextDocument::poll_events) (frame-loop).
12///
13/// These events carry enough information for a UI to do incremental updates —
14/// repaint only the affected region, not the entire document.
15#[derive(Debug, Clone, PartialEq)]
16pub enum DocumentEvent {
17    /// Text content changed at a specific region.
18    ///
19    /// Emitted by: `insert_text`, `delete_char`, `delete_previous_char`,
20    /// `remove_selected_text`, `insert_formatted_text`, `insert_block`,
21    /// `insert_html`, `insert_markdown`, `insert_fragment`, `insert_image`.
22    ContentsChanged {
23        position: usize,
24        chars_removed: usize,
25        chars_added: usize,
26        blocks_affected: usize,
27    },
28
29    /// Formatting changed without text content change.
30    FormatChanged {
31        position: usize,
32        length: usize,
33        /// Distinguishes block-level changes (relayout needed) from
34        /// character-level changes (reshaping only).
35        kind: crate::flow::FormatChangeKind,
36    },
37
38    /// Block count changed. Carries the new count.
39    BlockCountChanged(usize),
40
41    /// Flow elements were inserted at the given index in the main
42    /// frame's `child_order`.
43    ///
44    /// This is a performance optimization — the layout engine can
45    /// update incrementally instead of re-querying
46    /// [`TextDocument::flow()`](crate::TextDocument::flow).
47    FlowElementsInserted { flow_index: usize, count: usize },
48
49    /// Flow elements were removed starting at the given index in the
50    /// main frame's `child_order`.
51    FlowElementsRemoved { flow_index: usize, count: usize },
52
53    /// The document was completely replaced (import, clear).
54    DocumentReset,
55
56    /// Undo/redo was performed or availability changed.
57    UndoRedoChanged { can_undo: bool, can_redo: bool },
58
59    /// The modified flag changed.
60    ModificationChanged(bool),
61
62    /// A long operation progressed.
63    LongOperationProgress {
64        operation_id: String,
65        percent: f64,
66        message: String,
67    },
68
69    /// A long operation completed or failed.
70    LongOperationFinished {
71        operation_id: String,
72        success: bool,
73        error: Option<String>,
74    },
75}
76
77/// Handle to a document event subscription.
78///
79/// Events are delivered as long as this handle is alive.
80/// Drop it to unsubscribe. No explicit unsubscribe method needed.
81pub struct Subscription {
82    alive: Arc<AtomicBool>,
83}
84
85impl Drop for Subscription {
86    fn drop(&mut self) {
87        self.alive
88            .store(false, std::sync::atomic::Ordering::Relaxed);
89    }
90}
91
92/// Register a callback with the document inner, returning a Subscription handle.
93pub(crate) fn subscribe_inner<F>(inner: &mut TextDocumentInner, callback: F) -> Subscription
94where
95    F: Fn(DocumentEvent) + Send + Sync + 'static,
96{
97    let alive = Arc::new(AtomicBool::new(true));
98    inner.callbacks.push(CallbackEntry {
99        alive: Arc::downgrade(&alive),
100        callback: Arc::new(callback),
101    });
102    Subscription { alive }
103}