strop_engine/editor/io/
codec.rs1use super::Opened;
3use crate::editor::document::{DocumentSource, RemoteDirectory, RemoteDocument, ReturnPoint};
4use crate::editor::Document;
5use crate::files::FileTarget;
6use serde::{Deserialize, Serialize};
7use strop_remote::{ReadSelection, RemoteEntry, RemoteWindow};
8
9#[derive(Deserialize)]
10#[serde(tag = "kind")]
11enum RemoteRecord {
12 File {
13 window: RemoteWindow,
14 selection: ReadSelection,
15 return_to: Option<ReturnPoint>,
16 },
17 Directory {
18 entries: Vec<RemoteEntry>,
19 visible: Vec<usize>,
20 filter: String,
21 return_to: Option<ReturnPoint>,
22 },
23}
24#[derive(Deserialize)]
25struct Record {
26 buffer: strop_core::BufferSeed,
27 canonical: FileTarget,
28 #[serde(default, skip_serializing_if = "Option::is_none")]
29 remote: Option<RemoteRecord>,
30}
31
32#[derive(Serialize)]
33#[serde(tag = "kind")]
34enum RemoteRecordRef<'a> {
35 File {
36 window: RemoteWindow,
37 selection: ReadSelection,
38 return_to: &'a Option<ReturnPoint>,
39 },
40 Directory {
41 entries: &'a [RemoteEntry],
42 visible: &'a [usize],
43 filter: &'a str,
44 return_to: &'a Option<ReturnPoint>,
45 },
46}
47#[derive(Serialize)]
48struct RecordRef<'a> {
49 buffer: strop_core::BufferSeed,
50 canonical: &'a FileTarget,
51 #[serde(skip_serializing_if = "Option::is_none")]
52 remote: Option<RemoteRecordRef<'a>>,
53}
54impl Serialize for Opened {
55 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
56 let remote = match &self.document.source {
57 DocumentSource::Remote(source) => Some(RemoteRecordRef::File {
58 window: source.window,
59 selection: source.selection,
60 return_to: &source.return_to,
61 }),
62 DocumentSource::RemoteDirectory(source) => Some(RemoteRecordRef::Directory {
63 entries: &source.entries,
64 visible: &source.visible,
65 filter: &source.filter,
66 return_to: &source.return_to,
67 }),
68 _ => None,
69 };
70 RecordRef {
71 buffer: self.document.buf.seed(),
72 canonical: &self.canonical,
73 remote,
74 }
75 .serialize(serializer)
76 }
77}
78impl<'de> Deserialize<'de> for Opened {
79 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
80 let record = Record::deserialize(deserializer)?;
81 let buffer = record
82 .buffer
83 .into_buffer()
84 .map_err(serde::de::Error::custom)?;
85 let document = match (&record.canonical, record.remote) {
86 (FileTarget::Local(_), None) => Document::new(buffer),
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 a canonical absolute identity",
96 )
97 })?;
98 match metadata {
99 RemoteRecord::File {
100 window,
101 selection,
102 return_to,
103 } => {
104 if window.length().get() != buffer.len_bytes() as u64
105 || window
106 .start()
107 .get()
108 .checked_add(window.length().get())
109 .is_none_or(|end| end > window.file_size().get())
110 {
111 return Err(serde::de::Error::custom(
112 "remote window does not match buffer bytes",
113 ));
114 }
115 Document::remote(
116 buffer,
117 RemoteDocument {
118 file,
119 window,
120 selection,
121 connection: None,
122 return_to,
123 write: None,
124 },
125 )
126 }
127 RemoteRecord::Directory {
128 entries,
129 visible,
130 filter,
131 return_to,
132 } => {
133 let mut indices = std::collections::HashSet::new();
134 if visible
135 .iter()
136 .any(|&index| index >= entries.len() || !indices.insert(index))
137 || entries.iter().any(|entry| {
138 entry.file.endpoint() != file.endpoint()
139 || entry.file.path().parent() != Some(file.path())
140 })
141 {
142 return Err(serde::de::Error::custom(
143 "invalid remote directory entry mapping",
144 ));
145 }
146 let source = RemoteDirectory {
147 directory: file,
148 entries: entries.into(),
149 visible,
150 filter,
151 connection: None,
152 return_to,
153 };
154 if buffer.text() != source.text().as_str() {
155 return Err(serde::de::Error::custom(
156 "directory rows disagree with their native targets",
157 ));
158 }
159 Document::directory(buffer, source)
160 }
161 }
162 }
163 _ => {
164 return Err(serde::de::Error::custom(
165 "open completion source metadata mismatch; use the recording version",
166 ))
167 }
168 };
169 Ok(Self {
170 document,
171 canonical: record.canonical,
172 })
173 }
174}