strop_engine/editor/io/
codec.rs1use 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}