strop_engine/editor/document/
remote.rs1use super::{Document, DocumentSource, JumpRecord};
3use std::fmt::Write;
4use strop_core::Buffer;
5use strop_remote::{
6 ConnectionLease, ReadSelection, RemoteDirectorySnapshot, RemoteEntry, RemoteEntryKind,
7 RemoteSnapshot, RemoteWindow,
8};
9use strop_workspace::RemoteFile;
10
11#[derive(Clone)]
12pub struct RemoteDocument {
13 pub file: RemoteFile,
14 pub window: RemoteWindow,
15 pub selection: ReadSelection,
16 pub connection: Option<ConnectionLease>,
17 pub return_to: Option<JumpRecord>,
18 pub(crate) write: Option<crate::editor::remote::save::WritePermit>,
19}
20impl std::fmt::Debug for RemoteDocument {
21 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 formatter
23 .debug_struct("RemoteDocument")
24 .field("file", &self.file)
25 .field("window", &self.window)
26 .field("selection", &self.selection)
27 .field("connected", &self.connection.is_some())
28 .field("write_authorized", &self.write.is_some())
29 .finish()
30 }
31}
32
33#[derive(Clone)]
34pub struct RemoteDirectory {
35 pub directory: RemoteFile,
36 pub entries: std::sync::Arc<[RemoteEntry]>,
37 pub visible: Vec<usize>,
38 pub filter: String,
39 pub connection: Option<ConnectionLease>,
40 pub return_to: Option<JumpRecord>,
41}
42impl std::fmt::Debug for RemoteDirectory {
43 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 formatter
45 .debug_struct("RemoteDirectory")
46 .field("directory", &self.directory)
47 .field("entries", &self.entries.len())
48 .field("filter", &self.filter)
49 .field("connected", &self.connection.is_some())
50 .finish()
51 }
52}
53impl RemoteDirectory {
54 pub fn text(&self) -> String {
55 let size_width = self
56 .visible
57 .iter()
58 .filter_map(|&index| self.entries[index].size)
59 .map(|size| size.get().checked_ilog10().unwrap_or(0) as usize + 1)
60 .max()
61 .unwrap_or(1)
62 .max(5);
63 let mut text = String::new();
64 let _ = writeln!(text, "d????????? {:>size_width$} ../", "?");
66 for &index in &self.visible {
67 let entry = &self.entries[index];
68 text.push(entry.kind.marker());
69 match entry.permissions {
70 Some(permissions) => {
71 let _ = write!(text, "{permissions}");
72 }
73 None => text.push_str("?????????"),
74 }
75 match entry.size {
76 Some(size) => {
77 let _ = write!(text, " {:>size_width$} ", size.get());
78 }
79 None => {
80 let _ = write!(text, " {:>size_width$} ", "?");
81 }
82 }
83 let name = entry
84 .file
85 .path()
86 .file_name()
87 .unwrap_or_default()
88 .to_string_lossy();
89 text.push_str(&strop_core::layout::printable_text(name));
90 if entry.kind == RemoteEntryKind::Directory {
91 text.push('/');
92 }
93 if entry.kind == RemoteEntryKind::SymbolicLink {
94 text.push('@');
95 }
96 text.push('\n');
97 }
98 text
99 }
100 pub fn parent(&self) -> Result<Option<RemoteFile>, strop_workspace::AddressError> {
101 self.directory
102 .path()
103 .parent()
104 .map(|parent| self.directory.with_path(parent.to_path_buf()))
105 .transpose()
106 }
107 pub fn entry(&self, line: strop_core::id::LineIndex) -> Option<&RemoteEntry> {
108 line.get()
109 .checked_sub(1)
110 .and_then(|line| self.visible.get(line))
111 .and_then(|&index| self.entries.get(index))
112 }
113 pub(crate) fn line_for(&self, file: &RemoteFile) -> Option<strop_core::id::LineIndex> {
116 if file.endpoint() != self.directory.endpoint() {
117 return None;
118 }
119 let split = self
120 .entries
121 .partition_point(|entry| entry.kind == RemoteEntryKind::Directory);
122 for (offset, entries) in [(0, &self.entries[..split]), (split, &self.entries[split..])] {
123 if let Ok(index) = entries.binary_search_by(|entry| {
124 entry.file.path().as_os_str().cmp(file.path().as_os_str())
125 }) {
126 return self
127 .visible
128 .binary_search(&(offset + index))
129 .ok()
130 .map(|row| strop_core::id::LineIndex::new(row + 1));
131 }
132 }
133 None
134 }
135}
136
137impl Document {
138 pub fn remote(mut buffer: Buffer, source: RemoteDocument) -> Self {
139 debug_assert!(
140 buffer.path.is_none(),
141 "remote identity cannot become a local file"
142 );
143 buffer.name = Some(source.file.to_string());
144 buffer.readonly = true;
145 let detection = Some(super::detect_indent(buffer.text()));
146 Self {
147 buf: buffer,
148 syntax_hint: None,
149 indent: super::Indent::default(),
150 indent_override: super::IndentOverride::default(),
151 detection,
152 source: DocumentSource::Remote(Box::new(source)),
153 }
154 }
155 pub(crate) fn remote_snapshot(snapshot: RemoteSnapshot, selection: ReadSelection) -> Self {
156 Self::remote(
157 snapshot.buffer,
158 RemoteDocument {
159 file: snapshot.file,
160 window: snapshot.window,
161 selection,
162 connection: Some(snapshot.connection),
163 return_to: None,
164 write: None,
165 },
166 )
167 }
168 pub(crate) fn remote_directory(snapshot: RemoteDirectorySnapshot) -> Self {
169 let mut entries = snapshot.entries;
170 entries.sort_by(|left, right| {
171 let left_directory = left.kind == RemoteEntryKind::Directory;
172 let right_directory = right.kind == RemoteEntryKind::Directory;
173 right_directory.cmp(&left_directory).then_with(|| {
174 left.file
175 .path()
176 .as_os_str()
177 .cmp(right.file.path().as_os_str())
178 })
179 });
180 let directory = RemoteDirectory {
181 directory: snapshot.directory,
182 visible: (0..entries.len()).collect(),
183 entries: entries.into(),
184 filter: String::new(),
185 connection: Some(snapshot.connection),
186 return_to: None,
187 };
188 let buffer = Buffer::from_text(&directory.text());
189 Self::directory(buffer, directory)
190 }
191 pub fn directory(mut buffer: Buffer, source: RemoteDirectory) -> Self {
192 buffer.name = Some(source.directory.to_string());
193 buffer.readonly = true;
194 Self {
195 buf: buffer,
196 syntax_hint: None,
197 indent: super::Indent::default(),
198 indent_override: super::IndentOverride::default(),
199 detection: None,
200 source: DocumentSource::RemoteDirectory(Box::new(source)),
201 }
202 }
203 pub fn remote_metadata(&self) -> Option<&RemoteDocument> {
204 match &self.source {
205 DocumentSource::Remote(source) => Some(source),
206 _ => None,
207 }
208 }
209 pub fn directory_metadata_ref(&self) -> Option<&RemoteDirectory> {
210 match &self.source {
211 DocumentSource::RemoteDirectory(source) => Some(source),
212 _ => None,
213 }
214 }
215}
216
217impl Document {
218 pub(crate) fn return_point(&self) -> Option<&JumpRecord> {
219 match &self.source {
220 DocumentSource::Surface(surface) => surface.content.return_point(),
221 DocumentSource::Remote(source) => source.return_to.as_ref(),
222 DocumentSource::RemoteDirectory(source) => source.return_to.as_ref(),
223 DocumentSource::Output { return_to } => return_to.as_ref(),
224 _ => None,
225 }
226 }
227 pub(crate) fn return_point_mut(&mut self) -> Option<&mut JumpRecord> {
228 match &mut self.source {
229 DocumentSource::Surface(surface) => surface.content.return_slot().as_mut(),
230 DocumentSource::Remote(source) => source.return_to.as_mut(),
231 DocumentSource::RemoteDirectory(source) => source.return_to.as_mut(),
232 DocumentSource::Output { return_to } => return_to.as_mut(),
233 _ => None,
234 }
235 }
236 pub(crate) fn set_return_point(&mut self, point: JumpRecord) {
237 match &mut self.source {
238 DocumentSource::Surface(surface) => surface.content.set_return_point(point),
239 DocumentSource::Remote(source) => source.return_to = Some(point),
240 DocumentSource::RemoteDirectory(source) => source.return_to = Some(point),
241 DocumentSource::Output { return_to } => *return_to = Some(point),
242 _ => {}
243 }
244 }
245}