editor_core/delta.rs
1//! Structured text change deltas.
2//!
3//! `editor-core` historically exposed state changes as a coarse event
4//! ([`crate::StateChangeType::DocumentModified`]) plus a best-effort affected region.
5//! For a full-featured editor, incremental consumers (LSP sync, incremental parsing, indexing,
6//! match highlighting, etc.) typically need **structured edits** without diffing old/new text.
7//!
8//! This module defines a small, UI-agnostic delta format expressed in **character offsets**
9//! (Unicode scalar values).
10
11/// A single text edit expressed in character offsets.
12///
13/// Semantics:
14/// - `start` is a character offset in the document **at the time this edit is applied**.
15/// - The deleted range is defined by the length (in `char`s) of `deleted_text`.
16/// - Edits inside a [`TextDelta`] must be applied **in order** to transform the "before" document
17/// into the "after" document.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct TextDeltaEdit {
20 /// Start character offset of the edit.
21 pub start: usize,
22 /// Exact deleted text (may be empty).
23 pub deleted_text: String,
24 /// Exact inserted text (may be empty).
25 pub inserted_text: String,
26}
27
28impl TextDeltaEdit {
29 /// Length of `deleted_text` in characters.
30 pub fn deleted_len(&self) -> usize {
31 self.deleted_text.chars().count()
32 }
33
34 /// Length of `inserted_text` in characters.
35 pub fn inserted_len(&self) -> usize {
36 self.inserted_text.chars().count()
37 }
38
39 /// Exclusive end character offset in the pre-edit document.
40 pub fn end(&self) -> usize {
41 self.start.saturating_add(self.deleted_len())
42 }
43}
44
45/// A structured description of a document text change.
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct TextDelta {
48 /// Character count before applying `edits`.
49 pub before_char_count: usize,
50 /// Character count after applying `edits`.
51 pub after_char_count: usize,
52 /// Ordered list of edits that transforms the "before" document into the "after" document.
53 pub edits: Vec<TextDeltaEdit>,
54 /// If known, the undo group id associated with this change.
55 pub undo_group_id: Option<usize>,
56}
57
58impl TextDelta {
59 /// Returns `true` if this delta contains no edits.
60 pub fn is_empty(&self) -> bool {
61 self.edits.is_empty()
62 }
63}