Skip to main content

strop_engine/editor/trace/
services.rs

1//! Shared service-handler producers: TUI deliveries and headless drains both
2//! reach these handlers. Never record raw clipboard or shell-output payloads.
3//!
4//! `NativePath` is the tape's path-carrying argument wrapper: native paths
5//! serialize through strop-core's versioned encoding so replay never depends
6//! on lossy UTF-8 conversion.
7use crate::editor::git_memory::GitJob;
8use crate::editor::{ClipboardResult, ShellResult};
9use serde_json::json;
10use strop_trace::{record_with, EventKind};
11
12/// A path crossing the tape boundary. Reusable where a tuple carries a
13/// path; structs with path fields use `#[serde(with=...)]` directly.
14#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
15pub struct NativePath(#[serde(with = "strop_core::path_serde")] pub std::path::PathBuf);
16
17pub fn rejected(service: &'static str, reason: &str) {
18    record_with(
19        EventKind::JobRejected,
20        || json!({"service":service,"reason":reason}),
21    );
22}
23fn outcome<T>(value: &strop_core::worker::Outcome<T>) -> serde_json::Value {
24    use strop_core::worker::Outcome;
25    match value {
26        Outcome::Success(_) => json!({"kind":"success"}),
27        Outcome::Failed { failure, partial } => {
28            json!({"kind":"failed","failure":failure,"partial":partial.is_some()})
29        }
30        Outcome::Cancelled(reason) => json!({"kind":"cancelled","reason":reason}),
31    }
32}
33
34fn completion<K: serde::Serialize, T>(
35    service: &str,
36    result: &str,
37    value: &strop_core::worker::Completion<K, T>,
38) -> serde_json::Value {
39    json!({"service":service,"result":result,"ticket":value.ticket,"outcome":outcome(&value.outcome)})
40}
41
42pub fn git(job: &GitJob) {
43    record_with(EventKind::JobFinished, || match job {
44        GitJob::Context(value) => completion("git", "context", value),
45        GitJob::Hunks(value) => completion("git", "hunks", value),
46        GitJob::Mutation(value) => completion("git", "mutation", value),
47        GitJob::Log(value) => completion("git", "log", value),
48        GitJob::Gutter(value) => completion("git", "gutter", value),
49        GitJob::Card(value) => completion("git", "blame_card", value),
50        GitJob::Dive(value) => completion("git", "dive", value),
51    });
52}
53
54pub fn io(event: &crate::editor::io::IoEvent) {
55    use crate::editor::io::IoEvent;
56    record_with(EventKind::JobFinished, || match event {
57        IoEvent::Open(value) => completion("io", "open", value),
58        IoEvent::Save(value) => completion("io", "save", value),
59        IoEvent::Native(value) => completion("io", "native", value),
60        IoEvent::Review(value) => completion("search", "review", value),
61        IoEvent::Remote(event) => {
62            use crate::editor::remote::RemoteEvent;
63            match event {
64                RemoteEvent::Tick(ticket) => {
65                    json!({"service":"remote","result":"follow_tick","ticket":ticket})
66                }
67                RemoteEvent::Timer(value) => completion("remote", "follow_clock", value),
68                RemoteEvent::Read(value) => completion("remote", "follow_read", value),
69                RemoteEvent::Control(value) => completion("remote", "control", value),
70                RemoteEvent::Filter(value) => completion("remote", "directory_filter", value),
71                RemoteEvent::Choices(value) => completion("remote", "destinations", value),
72                RemoteEvent::Write(value) => {
73                    let mut record = completion("remote", "write", value);
74                    if let strop_core::worker::Outcome::Success(
75                        crate::editor::remote::save::RemoteWriteResult::Refused(error),
76                    ) = &value.outcome
77                    {
78                        record["outcome"] = outcome(&strop_core::worker::Outcome::<()>::failed(
79                            strop_core::worker::FailureKind::Io,
80                            error.to_string(),
81                        ));
82                    }
83                    record
84                }
85                RemoteEvent::DestinationWritten(value) => {
86                    completion("remote", "destination_write", value)
87                }
88            }
89        }
90        IoEvent::Session {
91            request,
92            outcome: value,
93        } => json!({"service":"io","result":"session","request":request,"outcome":outcome(value)}),
94    });
95}
96
97pub fn shell(result: &ShellResult) {
98    record_with(EventKind::JobFinished, || {
99        let key = match &result.ticket.key {
100            crate::editor::ShellKey::Display {
101                origin, revision, ..
102            } => json!({
103                "kind":"display","document":super::snapshot::document_id(*origin),
104                "revision":revision,
105            }),
106            crate::editor::ShellKey::Pipe {
107                document,
108                revision,
109                start,
110                end,
111            } => json!({
112                "kind":"pipe","document":super::snapshot::document_id(*document),
113                "revision":revision,"start_byte":start,"end_byte":end,
114            }),
115        };
116        let outcome = match &result.outcome {
117            strop_core::worker::Outcome::Success(output) => json!({
118                "stdout_bytes":output.stdout.len(),"stderr_bytes":output.stderr.len(),
119            }),
120            strop_core::worker::Outcome::Failed { failure, .. } => {
121                json!({"error": failure.message})
122            }
123            _ => json!({"cancelled": true}),
124        };
125        json!({"service":"shell","request":result.ticket.request.get(),
126            "key":key,"outcome":outcome})
127    });
128}
129
130pub fn clipboard(result: &ClipboardResult) {
131    record_with(EventKind::JobFinished, || {
132        let outcome = match &result.outcome {
133            strop_core::worker::Outcome::Success(text) => json!({"bytes": text.len()}),
134            strop_core::worker::Outcome::Failed { failure, .. } => {
135                json!({"error": failure.message})
136            }
137            _ => json!({"cancelled": true}),
138        };
139        json!({"service":"clipboard","request":result.ticket.request.get(),
140            "document":super::snapshot::document_id(result.ticket.key.document),
141            "outcome":outcome})
142    });
143}
144
145pub fn lsp(event: &strop_lsp::LspEvent) {
146    use strop_lsp::LspEvent;
147    record_with(EventKind::JobFinished, || {
148        if strop_trace::capture_content() {
149            return json!({"service":"lsp", "event":event});
150        }
151        match event {
152            LspEvent::Ready { server, name } => {
153                json!({"service":"lsp","result":"ready","server":server,"name":name})
154            }
155            LspEvent::Failed { server, name, hint } => {
156                // The hint carries the executable and the fix (0033 §3)
157                json!({"service":"lsp","result":"failed","server":server,"name":name,"hint":hint})
158            }
159            LspEvent::ServerMessage { server, name, text } => {
160                json!({"service":"lsp","result":"message","server":server,"name":name,"bytes":text.len()})
161            }
162            LspEvent::Diagnostics { context, diags, .. } => json!({
163                "service":"lsp","result":"diagnostics","context":context,"count":diags.len(),
164            }),
165            LspEvent::Edits { context, edits } => json!({
166                "service":"lsp","result":"edits","context":context,"count":edits.len(),
167            }),
168            LspEvent::WorkspaceEdits { context, edits } => json!({
169                "service":"lsp","result":"workspace_edits","context":context,"targets":edits.len(),
170            }),
171            LspEvent::Symbols { context, symbols } => json!({
172                "service":"lsp","result":"symbols","context":context,"count":symbols.len(),
173            }),
174            LspEvent::ActionList { context, actions } => json!({
175                "service":"lsp","result":"code_actions","context":context,"count":actions.len(),
176            }),
177            LspEvent::HoverText { context, text } => json!({
178                "service":"lsp","result":"hover","context":context,"bytes":text.len(),
179            }),
180            LspEvent::Note { context, text } => json!({
181                "service":"lsp","result":"note","context":context,"bytes":text.len(),
182            }),
183            LspEvent::GotoLocation { context, .. } => json!({
184                "service":"lsp","result":"goto","context":context,
185            }),
186            LspEvent::Locations {
187                context,
188                kind,
189                items,
190            } => json!({
191                "service":"lsp","result":kind.label(),"context":context,"count":items.len(),
192            }),
193        }
194    });
195}
196
197pub fn picker(event: &crate::editor::picker::PickerEvent) {
198    use strop_picker::PickerMsg;
199    record_with(EventKind::JobFinished, || {
200        let outcome = match &event.msg {
201            PickerMsg::Items(items) => json!({"result":"items","count":items.len()}),
202            PickerMsg::Warning(warning) => json!({"result":"warning","bytes":warning.len()}),
203            PickerMsg::QueryError(diagnostic) => {
204                json!({"result":"query-error","range":diagnostic.range})
205            }
206            PickerMsg::Finished(outcome) => match outcome {
207                strop_core::worker::Outcome::Success(()) => json!({"result":"finished"}),
208                strop_core::worker::Outcome::Failed { failure, .. } => {
209                    json!({"result":"error","error":failure.message})
210                }
211                _ => json!({"result":"cancelled"}),
212            },
213        };
214        json!({"service":"picker","request":event.ticket.request.get(),
215            "outcome":outcome})
216    });
217}