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    /// Only paint-level highlight attributes changed (colors, underline
39    /// decorations) on a paint-only highlighter. The shaping input
40    /// (`fragments`) is unchanged, so the layout engine can recolor the
41    /// cached layout without reshaping or reflowing.
42    ///
43    /// `position` / `length` are document-absolute character offsets bounding
44    /// the extent that changed, so a view may recolor just the blocks they
45    /// cover rather than re-snapshotting the whole document.
46    ///
47    /// **A `length` of `0` means "unknown — assume the whole document"**, and
48    /// is what the genuinely document-wide operations send: installing or
49    /// retiring a highlighter, and a full rehighlight. `set_session_ranges`
50    /// knows its own before/after ranges and reports their union exactly.
51    /// A receiver that does not care may keep treating every one of these as
52    /// whole-document; that is the safe reading of both cases.
53    HighlightPaintChanged { position: usize, length: usize },
54
55    /// Block count changed. Carries the new count.
56    BlockCountChanged(usize),
57
58    /// Flow elements were inserted at the given index in the main
59    /// frame's `child_order`.
60    ///
61    /// This is a performance optimization — the layout engine can
62    /// update incrementally instead of re-querying
63    /// [`TextDocument::flow()`](crate::TextDocument::flow).
64    FlowElementsInserted { flow_index: usize, count: usize },
65
66    /// Flow elements were removed starting at the given index in the
67    /// main frame's `child_order`.
68    FlowElementsRemoved { flow_index: usize, count: usize },
69
70    /// The document was completely replaced (import, clear).
71    DocumentReset,
72
73    /// Undo/redo was performed or availability changed.
74    UndoRedoChanged { can_undo: bool, can_redo: bool },
75
76    /// The modified flag changed.
77    ModificationChanged(bool),
78
79    /// A long operation progressed.
80    LongOperationProgress {
81        operation_id: String,
82        percent: f64,
83        message: String,
84    },
85
86    /// A long operation completed or failed.
87    LongOperationFinished {
88        operation_id: String,
89        success: bool,
90        error: Option<String>,
91    },
92}
93
94/// Handle to a document event subscription.
95///
96/// Events are delivered as long as this handle is alive.
97/// Drop it to unsubscribe. No explicit unsubscribe method needed.
98pub struct Subscription {
99    alive: Arc<AtomicBool>,
100}
101
102impl Drop for Subscription {
103    fn drop(&mut self) {
104        self.alive
105            .store(false, std::sync::atomic::Ordering::Relaxed);
106    }
107}
108
109/// Register a callback with the document inner, returning a Subscription handle.
110pub(crate) fn subscribe_inner<F>(inner: &mut TextDocumentInner, callback: F) -> Subscription
111where
112    F: Fn(DocumentEvent) + Send + Sync + 'static,
113{
114    let alive = Arc::new(AtomicBool::new(true));
115    inner.callbacks.push(CallbackEntry {
116        alive: Arc::downgrade(&alive),
117        callback: Arc::new(callback),
118    });
119    Subscription { alive }
120}