strop_engine/editor/remote/
mod.rs1mod chooser;
4mod commands;
5mod controls;
6mod directory;
7pub(super) mod follow;
8mod history;
9pub(crate) mod save;
10#[cfg(test)]
11mod tests;
12pub mod view;
13
14use super::document::DocumentSource;
15use super::io::Opened;
16use super::{Document, Editor};
17use std::collections::HashMap;
18use strop_core::id::{BufferRevision, DocumentId};
19use strop_core::worker::{Completion, Ticket, WorkerId};
20use strop_remote::{ConnectionLease, ReadLimit, RemoteClient};
21use strop_workspace::{RemoteEndpoint, RemoteFile};
22pub use view::RemoteView;
23
24pub(crate) struct RemoteState {
25 client: RemoteClient,
26 following: HashMap<DocumentId, FollowOwner>,
27 controls: HashMap<WorkerId, ControlKey>,
28 pins: HashMap<RemoteEndpoint, ConnectionLease>,
29 filters: HashMap<WorkerId, DirectoryFilterKey>,
30 choices: Option<Ticket<super::picker::PickerId>>,
31 destination_write: Option<Ticket<RemoteFile>>,
32 destination_queue: Vec<RemoteFile>,
33 writes: save::WriteState,
34}
35impl Default for RemoteState {
36 fn default() -> Self {
37 Self {
38 client: RemoteClient::new(),
39 following: HashMap::new(),
40 controls: HashMap::new(),
41 pins: HashMap::new(),
42 filters: HashMap::new(),
43 choices: None,
44 destination_write: None,
45 destination_queue: Vec::new(),
46 writes: save::WriteState::default(),
47 }
48 }
49}
50struct FollowOwner {
51 ticket: Ticket<FollowKey>,
52 read: Option<WorkerId>,
53 limit: ReadLimit,
54}
55#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
56pub struct FollowKey {
57 pub document: DocumentId,
58}
59#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
60pub struct FollowReadKey {
61 pub document: DocumentId,
62 pub owner: WorkerId,
63 pub revision: BufferRevision,
64}
65#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
66pub enum FollowChange {
67 Appended,
68 Reset,
69 Shrank,
70}
71#[derive(serde::Serialize, serde::Deserialize)]
72pub enum FollowUpdate {
73 Unchanged,
74 Window {
75 opened: Box<Opened>,
76 change: FollowChange,
77 },
78}
79#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
80pub enum RemoteControl {
81 Connect(RemoteEndpoint),
82 Disconnect(RemoteEndpoint),
83 DisconnectAll,
84 Connections,
85}
86#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
87pub struct ControlKey {
88 pub document: DocumentId,
89 pub focus: u64,
90 pub operation: RemoteControl,
91}
92#[derive(serde::Serialize, serde::Deserialize)]
93pub enum ControlResult {
94 Connected {
95 endpoint: RemoteEndpoint,
96 #[serde(skip)]
97 lease: Option<ConnectionLease>,
98 },
99 Disconnected,
100 Listing(String),
101}
102#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
103pub struct DirectoryFilterKey {
104 pub document: DocumentId,
105 pub revision: BufferRevision,
106 pub query: String,
107}
108#[derive(serde::Serialize, serde::Deserialize)]
109pub enum RemoteEvent {
110 Tick(Ticket<FollowKey>),
111 Timer(Completion<FollowKey, ()>),
112 Read(Box<Completion<FollowReadKey, FollowUpdate>>),
113 Control(Completion<ControlKey, ControlResult>),
114 Filter(Box<Completion<DirectoryFilterKey, Opened>>),
115 Choices(Completion<super::picker::PickerId, chooser::RemoteChoices>),
116 DestinationWritten(Completion<RemoteFile, ()>),
117 Write(Box<Completion<save::RemoteWriteKey, save::RemoteWriteResult>>),
118}
119
120impl Editor {
121 pub(crate) fn remote_client(&self) -> RemoteClient {
122 self.remote.client.clone()
123 }
124 pub fn remote_file(&self) -> Option<&RemoteFile> {
125 match &self.cur().source {
126 DocumentSource::Remote(source) => Some(&source.file),
127 DocumentSource::RemoteDirectory(source) => Some(&source.directory),
128 _ => None,
129 }
130 }
131 pub(crate) fn remote_endpoint(&self) -> Option<&RemoteEndpoint> {
132 self.remote_file().map(RemoteFile::endpoint).or_else(|| {
133 self.cur()
134 .git_context()
135 .and_then(strop_git::GitContext::endpoint)
136 })
137 }
138 pub(crate) fn remote_window_complete(&self) -> bool {
139 self.cur()
140 .remote_metadata()
141 .is_some_and(|source| source.window.is_complete())
142 && !self.remote_following(self.current())
143 }
144 pub(crate) fn remote_directory(&self) -> Option<&super::document::RemoteDirectory> {
145 self.cur().directory_metadata_ref()
146 }
147 pub fn remote_following(&self, document: DocumentId) -> bool {
148 self.remote.following.contains_key(&document)
149 }
150 pub(crate) fn remote_work_pending(&self) -> bool {
151 !self.remote.controls.is_empty()
152 || !self.remote.filters.is_empty()
153 || self.remote.choices.is_some()
154 || self.remote.destination_write.is_some()
155 || !self.remote.destination_queue.is_empty()
156 || self.remote.writes.pending()
157 || self
158 .remote
159 .following
160 .values()
161 .any(|owner| owner.read.is_some())
162 }
163 pub(crate) fn handle_remote_event(&mut self, event: RemoteEvent) {
164 match event {
165 RemoteEvent::Write(completion) => self.remote_write_done(*completion),
166 RemoteEvent::Tick(ticket) => self.remote_follow_tick(ticket),
167 RemoteEvent::Timer(completion) => self.remote_follow_timer_done(completion),
168 RemoteEvent::Read(completion) => self.remote_follow_read(*completion),
169 RemoteEvent::Control(completion) => self.remote_control_done(completion),
170 RemoteEvent::Filter(completion) => self.remote_filter_done(*completion),
171 RemoteEvent::Choices(completion) => self.remote_choices_done(completion),
172 RemoteEvent::DestinationWritten(completion) => {
173 self.remote_destination_written(completion)
174 }
175 }
176 }
177 pub(crate) fn stop_remote_work(&mut self) {
178 let documents: Vec<_> = self.remote.following.keys().copied().collect();
179 for document in documents {
180 self.stop_remote_follow(document);
181 }
182 self.remote.pins.clear();
183 }
184}