Skip to main content

strop_engine/editor/io/
codec.rs

1//! Pure completion data crosses replay; parsers, clients and live leases never do.
2use super::Opened;
3use crate::editor::document::{Directory, DocumentSource, JumpRecord, RemoteDocument};
4use crate::editor::Document;
5use crate::files::FileTarget;
6use serde::{Deserialize, Serialize};
7use strop_remote::{ReadSelection, RemoteWindow};
8
9#[derive(Deserialize)]
10struct RemoteRecord {
11    window: RemoteWindow,
12    selection: ReadSelection,
13    return_to: Option<JumpRecord>,
14}
15#[derive(Deserialize)]
16struct Record {
17    buffer: strop_core::BufferSeed,
18    canonical: FileTarget,
19    remote: Option<RemoteRecord>,
20    directory: Option<Directory>,
21}
22#[derive(Serialize)]
23struct RemoteRecordRef<'a> {
24    window: RemoteWindow,
25    selection: ReadSelection,
26    return_to: &'a Option<JumpRecord>,
27}
28#[derive(Serialize)]
29struct RecordRef<'a> {
30    buffer: strop_core::BufferSeed,
31    canonical: &'a FileTarget,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    remote: Option<RemoteRecordRef<'a>>,
34    #[serde(skip_serializing_if = "Option::is_none")]
35    directory: Option<&'a Directory>,
36}
37impl Serialize for Opened {
38    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
39        let remote = self
40            .document
41            .remote_metadata()
42            .map(|source| RemoteRecordRef {
43                window: source.window,
44                selection: source.selection,
45                return_to: &source.return_to,
46            });
47        RecordRef {
48            buffer: self.document.buf.seed(),
49            canonical: &self.canonical,
50            remote,
51            directory: self.document.directory_metadata_ref(),
52        }
53        .serialize(serializer)
54    }
55}
56impl<'de> Deserialize<'de> for Opened {
57    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
58        let record = Record::deserialize(deserializer)?;
59        let buffer = record
60            .buffer
61            .into_buffer()
62            .map_err(serde::de::Error::custom)?;
63        let document = if let Some(source) = record.directory {
64            if record.remote.is_some()
65                || buffer.path.is_some()
66                || buffer.dirty
67                || !source.validate()
68                || !record.canonical.matches_location(&source.location)
69                || buffer.text() != source.text().as_str()
70            {
71                return Err(serde::de::Error::custom(
72                    "directory completion rows or resource identity are inconsistent",
73                ));
74            }
75            Document::directory(buffer, source)
76        } else {
77            match (&record.canonical, record.remote) {
78                (FileTarget::Local(_), None) => Document::new(buffer),
79                (FileTarget::Container { container, path }, None) => {
80                    if buffer.path.is_some() || buffer.dirty {
81                        return Err(serde::de::Error::custom(
82                            "container completion has a local write binding",
83                        ));
84                    }
85                    Document::container_file(buffer, container.clone(), path.clone())
86                }
87                (FileTarget::Remote(location), Some(metadata)) => {
88                    if buffer.path.is_some() || buffer.dirty {
89                        return Err(serde::de::Error::custom(
90                            "remote completion has local or dirty content",
91                        ));
92                    }
93                    let file = location.absolute_file().cloned().ok_or_else(|| {
94                        serde::de::Error::custom(
95                            "remote completion must have canonical absolute identity",
96                        )
97                    })?;
98                    if metadata.window.length().get() != buffer.len_bytes() as u64
99                        || metadata
100                            .window
101                            .start()
102                            .get()
103                            .checked_add(metadata.window.length().get())
104                            .is_none_or(|end| end > metadata.window.file_size().get())
105                    {
106                        return Err(serde::de::Error::custom(
107                            "remote window does not match buffer bytes",
108                        ));
109                    }
110                    Document::remote(
111                        buffer,
112                        RemoteDocument {
113                            file,
114                            window: metadata.window,
115                            selection: metadata.selection,
116                            connection: None,
117                            return_to: metadata.return_to,
118                            write: None,
119                        },
120                    )
121                }
122                _ => {
123                    return Err(serde::de::Error::custom(
124                        "open completion source metadata mismatch; use the recording version",
125                    ))
126                }
127            }
128        };
129        debug_assert!(matches!(
130            document.source,
131            DocumentSource::File
132                | DocumentSource::Scratch
133                | DocumentSource::Directory(_)
134                | DocumentSource::Remote(_)
135                | DocumentSource::Container { .. }
136        ));
137        Ok(Self {
138            document,
139            canonical: record.canonical,
140        })
141    }
142}