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)]
19#[serde(rename_all = "snake_case")]
20pub(crate) enum MutationSource {
21    User,
22    System,
23}
24
25#[derive(Serialize)]
26struct Mutation<'a> {
27    buffer: BufferTraceId,
28    path: Option<&'a std::path::Path>,
29    source: MutationSource,
30    revision: u64,
31    start_byte: usize,
32    removed_bytes: usize,
33    inserted_bytes: usize,
34    #[serde(skip_serializing_if = "Option::is_none")]
35    inserted_text: Option<&'a str>,
36}
37
38impl Buffer {
39    pub fn trace_id(&self) -> BufferTraceId {
40        self.trace_identity
41    }
42
43    pub(crate) fn trace_edit(
44        &self,
45        source: MutationSource,
46        start_byte: usize,
47        removed_bytes: usize,
48        inserted: &str,
49    ) {
50        if !enabled() {
51            return;
52        }
53        // Path's serde conversion rejects non-UTF8 paths; preserve display plus
54        // byte identity in the editor's document record instead of failing here.
55        let path = self.path.as_deref().filter(|p| p.to_str().is_some());
56        record(
57            EventKind::Mutation,
58            &Mutation {
59                buffer: self.trace_id(),
60                path,
61                source,
62                revision: self.epoch,
63                start_byte,
64                removed_bytes,
65                inserted_bytes: inserted.len(),
66                inserted_text: capture_content().then_some(inserted),
67            },
68        );
69    }
70
71    pub(crate) fn trace_history(&self, edits: &[crate::history::Edit]) {
72        if !enabled() {
73            return;
74        }
75        #[derive(Serialize)]
76        struct HistoryEdit<'a> {
77            start_byte: usize,
78            bytes: usize,
79            kind: crate::history::EditKind,
80            #[serde(skip_serializing_if = "Option::is_none")]
81            text: Option<&'a str>,
82        }
83        #[derive(Serialize)]
84        struct AppliedHistory<'a> {
85            buffer: BufferTraceId,
86            revision: u64,
87            edits: Vec<HistoryEdit<'a>>,
88        }
89        record(
90            EventKind::History,
91            &AppliedHistory {
92                buffer: self.trace_id(),
93                revision: self.epoch,
94                edits: edits
95                    .iter()
96                    .map(|edit| HistoryEdit {
97                        start_byte: edit.at,
98                        bytes: edit.text.len(),
99                        kind: edit.kind,
100                        text: capture_content().then_some(edit.text.as_str()),
101                    })
102                    .collect(),
103            },
104        );
105    }
106}