Skip to main content

strop_core/
diagnostics.rs

1//! Mutation diagnostics at the mechanics boundary, including open undo groups.
2use crate::Buffer;
3use serde::Serialize;
4use std::sync::atomic::{AtomicU64, Ordering};
5use strop_trace::{capture_content, enabled, record, EventKind};
6
7static NEXT_BUFFER: AtomicU64 = AtomicU64::new(1);
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
10#[serde(transparent)]
11pub struct BufferTraceId(u64);
12impl BufferTraceId {
13    pub(crate) fn next() -> Self {
14        Self(NEXT_BUFFER.fetch_add(1, Ordering::Relaxed))
15    }
16}
17
18#[derive(Serialize)]
19struct Mutation {
20    buffer: BufferTraceId,
21    source: crate::ChangeOrigin,
22    revision: u64,
23    start_byte: usize,
24    removed_bytes: usize,
25    inserted_bytes: usize,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    inserted_text: Option<String>,
28    #[serde(skip_serializing_if = "std::ops::Not::not")]
29    text_truncated: bool,
30}
31
32impl Buffer {
33    pub fn trace_id(&self) -> BufferTraceId {
34        self.trace_identity
35    }
36
37    pub(crate) fn trace_edit(
38        &self,
39        source: crate::ChangeOrigin,
40        start_byte: usize,
41        removed_bytes: usize,
42        inserted: &str,
43    ) {
44        if !enabled() {
45            return;
46        }
47        // True size always recorded; the text itself rides as a bounded
48        // excerpt so no edit can outgrow the per-record cap.
49        let (text, text_truncated) = strop_trace::excerpt(inserted);
50        record(
51            EventKind::Mutation,
52            &Mutation {
53                buffer: self.trace_id(),
54                source,
55                revision: self.revision().get(),
56                start_byte,
57                removed_bytes,
58                inserted_bytes: inserted.len(),
59                inserted_text: capture_content().then(|| text.to_owned()),
60                text_truncated: text_truncated && capture_content(),
61            },
62        );
63    }
64
65    pub(crate) fn trace_snapshot(&self, removed_bytes: usize) {
66        if !enabled() {
67            return;
68        }
69        let (text, text_truncated) = capture_content()
70            .then(|| self.text_excerpt())
71            .map_or((None, false), |(text, truncated)| (Some(text), truncated));
72        record(
73            EventKind::Mutation,
74            &Mutation {
75                buffer: self.trace_id(),
76                source: crate::ChangeOrigin::System,
77                revision: self.revision().get(),
78                start_byte: 0,
79                removed_bytes,
80                inserted_bytes: self.len_bytes(),
81                inserted_text: text,
82                text_truncated,
83            },
84        );
85    }
86
87    pub(crate) fn trace_history(&self, edits: &[crate::history::Edit]) {
88        if !enabled() {
89            return;
90        }
91        #[derive(Serialize)]
92        struct HistoryEdit<'a> {
93            start_byte: usize,
94            bytes: usize,
95            kind: crate::history::EditKind,
96            #[serde(skip_serializing_if = "Option::is_none")]
97            text: Option<&'a str>,
98            #[serde(skip_serializing_if = "std::ops::Not::not")]
99            text_truncated: bool,
100        }
101        #[derive(Serialize)]
102        struct AppliedHistory<'a> {
103            buffer: BufferTraceId,
104            revision: u64,
105            edits: Vec<HistoryEdit<'a>>,
106        }
107        record(
108            EventKind::History,
109            &AppliedHistory {
110                buffer: self.trace_id(),
111                revision: self.revision().get(),
112                edits: edits
113                    .iter()
114                    .map(|edit| {
115                        let (text, text_truncated) = strop_trace::excerpt(&edit.text);
116                        HistoryEdit {
117                            start_byte: edit.at,
118                            bytes: edit.text.len(),
119                            kind: edit.kind,
120                            text: capture_content().then_some(text),
121                            text_truncated: text_truncated && capture_content(),
122                        }
123                    })
124                    .collect(),
125            },
126        );
127    }
128}