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